Merge library and search into one fragment

closes #55
This commit is contained in:
Jannik 2022-12-26 16:10:38 +01:00
parent 8e397e13d2
commit e113a9c795
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
15 changed files with 374 additions and 203 deletions

View File

@ -40,8 +40,8 @@ import org.mosad.teapod.preferences.EncryptedPreferences
import org.mosad.teapod.preferences.Preferences
import org.mosad.teapod.ui.activity.main.fragments.AccountFragment
import org.mosad.teapod.ui.activity.main.fragments.HomeFragment
import org.mosad.teapod.ui.activity.main.fragments.MyListsFragment
import org.mosad.teapod.ui.activity.main.fragments.LibraryFragment
import org.mosad.teapod.ui.activity.main.fragments.SearchFragment
import org.mosad.teapod.ui.activity.onboarding.OnboardingActivity
import org.mosad.teapod.util.DataTypes
import org.mosad.teapod.util.metadb.MetaDBController
@ -100,12 +100,12 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen
activeBaseFragment = HomeFragment()
true
}
R.id.navigation_library -> {
activeBaseFragment = LibraryFragment()
R.id.navigation_my_lists -> {
activeBaseFragment = MyListsFragment()
true
}
R.id.navigation_search -> {
activeBaseFragment = SearchFragment()
R.id.navigation_library -> {
activeBaseFragment = LibraryFragment()
true
}
R.id.navigation_account -> {

View File

@ -1,30 +1,30 @@
package org.mosad.teapod.ui.activity.main.fragments
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch
import org.mosad.teapod.databinding.FragmentLibraryBinding
import org.mosad.teapod.parser.crunchyroll.Crunchyroll
import org.mosad.teapod.util.ItemMedia
import org.mosad.teapod.ui.activity.main.viewmodel.LibraryFragmentViewModel
import org.mosad.teapod.util.adapter.MediaItemListAdapter
import org.mosad.teapod.util.decoration.MediaItemDecoration
import org.mosad.teapod.util.showFragment
import org.mosad.teapod.util.toItemMediaList
class LibraryFragment : Fragment() {
private lateinit var binding: FragmentLibraryBinding
private lateinit var adapter: MediaItemListAdapter
private val itemList = arrayListOf<ItemMedia>()
private val pageSize = 30
private var nextItemIndex = 0
private val model: LibraryFragmentViewModel by viewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentLibraryBinding.inflate(inflater, container, false)
@ -36,48 +36,79 @@ class LibraryFragment : Fragment() {
// TODO replace with pagination3
// https://medium.com/swlh/paging3-recyclerview-pagination-made-easy-333c7dfa8797
binding.recyclerMediaLibrary.addOnScrollListener(PaginationScrollListener())
binding.recyclerMediaSearch.addOnScrollListener(PaginationScrollListener())
// init async
lifecycleScope.launch {
// create and set the adapter, needs context
context?.let {
itemList.addAll(Crunchyroll.browse(n = pageSize).toItemMediaList())
nextItemIndex += pageSize
adapter = MediaItemListAdapter(MediaItemListAdapter.OnClickListener {
binding.searchText.clearFocus()
activity?.showFragment(MediaFragment(it.id))
})
binding.recyclerMediaSearch.adapter = adapter
adapter = MediaItemListAdapter(MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id))
})
binding.recyclerMediaLibrary.adapter = adapter
adapter.submitList(itemList)
binding.searchText.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
println("onQueryTextSubmit: $query")
query?.let { model.search(it) }
return false // return false to dismiss the keyboard
}
}
}
override fun onQueryTextChange(newText: String?): Boolean {
println("onQueryTextChange: $newText")
newText?.let { model.search(it) }
return false // return false to dismiss the keyboard
}
})
inner class PaginationScrollListener: RecyclerView.OnScrollListener() {
private var isLoading = false
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as GridLayoutManager?
if (!isLoading) layoutManager?.let {
// itemList.size - 5 to start loading a bit earlier than the actual end
if (layoutManager.findLastCompletelyVisibleItemPosition() >= (itemList.size - 5)) {
// load new browse results async
isLoading = true
lifecycleScope.launch {
val firstNewItemIndex = itemList.lastIndex + 1
itemList.addAll(Crunchyroll.browse(start = nextItemIndex, n = pageSize).toItemMediaList())
nextItemIndex += pageSize
adapter.notifyItemRangeInserted(firstNewItemIndex, pageSize)
isLoading = false
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
model.onUiState(viewLifecycleOwner.lifecycleScope) { uiState ->
when (uiState) {
is LibraryFragmentViewModel.UiState.Browse -> bindUiStateBrowse(uiState)
is LibraryFragmentViewModel.UiState.Search -> bindUiStateSearch(uiState)
is LibraryFragmentViewModel.UiState.Loading -> bindUiStateLoading()
is LibraryFragmentViewModel.UiState.Error -> bindUiStateError(uiState)
}
}
}
}
}
}
private fun bindUiStateBrowse(uiState: LibraryFragmentViewModel.UiState.Browse) {
adapter.submitList(uiState.itemList)
}
@SuppressLint("NotifyDataSetChanged")
private fun bindUiStateSearch(uiState: LibraryFragmentViewModel.UiState.Search) {
adapter.submitList(uiState.itemList)
adapter.notifyDataSetChanged() // this is needed, else the adapter will not update
}
private fun bindUiStateLoading() {
// currently not used
}
private fun bindUiStateError(uiState: LibraryFragmentViewModel.UiState.Error) {
// currently not used
Log.e(javaClass.name, "A error occurred while loading a UiState: ${uiState.message}")
}
inner class PaginationScrollListener: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (!model.isLazyLoading) {
val layoutManager = recyclerView.layoutManager as? GridLayoutManager
layoutManager?.let {
// adapter.itemCount - 10 to start loading a bit earlier than the actual end
if (layoutManager.findLastCompletelyVisibleItemPosition() >= (adapter.itemCount - 10)) {
model.onLazyLoad().invokeOnCompletion {
adapter.notifyItemRangeInserted(adapter.itemCount, model.PAGESIZE)
}
}
}
}
}
}
}

View File

@ -47,7 +47,7 @@ class MediaFragmentSimilar : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerMediaSimilar.adapter = MediaItemListAdapter(
MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id))

View File

@ -0,0 +1,82 @@
package org.mosad.teapod.ui.activity.main.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch
import org.mosad.teapod.databinding.FragmentMyListsBinding
import org.mosad.teapod.parser.crunchyroll.Crunchyroll
import org.mosad.teapod.util.ItemMedia
import org.mosad.teapod.util.adapter.MediaItemListAdapter
import org.mosad.teapod.util.showFragment
import org.mosad.teapod.util.toItemMediaList
class MyListsFragment : Fragment() {
private lateinit var binding: FragmentMyListsBinding
private lateinit var adapter: MediaItemListAdapter
private val itemList = arrayListOf<ItemMedia>()
private val pageSize = 30
private var nextItemIndex = 0
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentMyListsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// TODO replace with pagination3
// https://medium.com/swlh/paging3-recyclerview-pagination-made-easy-333c7dfa8797
binding.recyclerMediaLibrary.addOnScrollListener(PaginationScrollListener())
// init async
lifecycleScope.launch {
// create and set the adapter, needs context
context?.let {
itemList.addAll(Crunchyroll.browse(n = pageSize).toItemMediaList())
nextItemIndex += pageSize
adapter = MediaItemListAdapter(MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id))
})
binding.recyclerMediaLibrary.adapter = adapter
adapter.submitList(itemList)
}
}
}
inner class PaginationScrollListener: RecyclerView.OnScrollListener() {
private var isLoading = false
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as GridLayoutManager?
if (!isLoading) layoutManager?.let {
// itemList.size - 5 to start loading a bit earlier than the actual end
if (layoutManager.findLastCompletelyVisibleItemPosition() >= (itemList.size - 5)) {
// load new browse results async
isLoading = true
lifecycleScope.launch {
val firstNewItemIndex = itemList.lastIndex + 1
itemList.addAll(Crunchyroll.browse(start = nextItemIndex, n = pageSize).toItemMediaList())
nextItemIndex += pageSize
adapter.notifyItemRangeInserted(firstNewItemIndex, pageSize)
isLoading = false
}
}
}
}
}
}

View File

@ -1,112 +0,0 @@
package org.mosad.teapod.ui.activity.main.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import org.mosad.teapod.databinding.FragmentSearchBinding
import org.mosad.teapod.parser.crunchyroll.Crunchyroll
import org.mosad.teapod.util.ItemMedia
import org.mosad.teapod.util.adapter.MediaItemListAdapter
import org.mosad.teapod.util.decoration.MediaItemDecoration
import org.mosad.teapod.util.showFragment
import org.mosad.teapod.util.toItemMediaList
class SearchFragment : Fragment() {
private lateinit var binding: FragmentSearchBinding
private lateinit var adapter: MediaItemListAdapter
private val itemList = arrayListOf<ItemMedia>()
private var searchJob: Job? = null
private var oldSearchQuery = ""
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentSearchBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerMediaSearch.addItemDecoration(MediaItemDecoration(9))
adapter = MediaItemListAdapter(MediaItemListAdapter.OnClickListener {
binding.searchText.clearFocus()
activity?.showFragment(MediaFragment(it.id))
})
binding.recyclerMediaSearch.adapter = adapter
initActions()
}
private fun initActions() {
binding.searchText.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
query?.let { search(it) }
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
newText?.let { search(it) }
return false
}
})
}
/**
* Search for a query string at Crunchyroll and submit the results to the adapter.
*/
private fun search(query: String) {
// if the query hasn't changed since the last successful search, return
if (query == oldSearchQuery) return
// cancel search job if one is already running
if (searchJob?.isActive == true) searchJob?.cancel()
searchJob = lifecycleScope.async {
// TODO maybe wait a few ms (500ms?) before searching, if the user inputs any other chars
val results = Crunchyroll.search(query, 50)
itemList.clear() // TODO needs clean up
// TODO add top results first heading
adapter.submitList(
results.items.firstOrNull()?.items?.toItemMediaList() ?: listOf<ItemMedia>()
)
// TODO currently only tv shows are supported, hence only the first items array
// should be always present
// // TODO add tv shows heading
// if (results.items.size >= 2) {
// itemList.addAll(results.items[1].items.map { item ->
// ItemMedia(item.id, item.title, item.images.poster_wide[0][0].source)
// })
// }
//
// // TODO add movies heading
// if (results.items.size >= 3) {
// itemList.addAll(results.items[2].items.map { item ->
// ItemMedia(item.id, item.title, item.images.poster_wide[0][0].source)
// })
// }
//
// // TODO add episodes heading
// if (results.items.size >= 4) {
// itemList.addAll(results.items[3].items.map { item ->
// ItemMedia(item.id, item.title, item.images.poster_wide[0][0].source)
// })
// }
// after successfully searching the query term, add it as old query, to make sure we
// don't search again if the query hasn't changed
oldSearchQuery = query
}
}
}

View File

@ -0,0 +1,131 @@
package org.mosad.teapod.ui.activity.main.viewmodel
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.mosad.teapod.parser.crunchyroll.Crunchyroll
import org.mosad.teapod.util.ItemMedia
import org.mosad.teapod.util.toItemMediaList
class LibraryFragmentViewModel : ViewModel() {
val PAGESIZE = 50
private val uiState = MutableStateFlow<UiState>(UiState.Loading)
private var oldSearchQuery = ""
private var searchJob: Job? = null
var isLazyLoading = false
internal set
sealed class UiState {
object Loading : UiState()
data class Browse(
val itemList: MutableList<ItemMedia>
) : UiState()
data class Search(
val itemList: List<ItemMedia>
) : UiState()
data class Error(val message: String?) : UiState()
}
init {
load()
}
fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
scope.launch { uiState.collect { collector(it) } }
}
/**
* initially load the first n browsing items
*/
private fun load() {
viewModelScope.launch {
uiState.emit(UiState.Loading)
try {
initBrowse()
} catch (ex: Exception) {
uiState.emit(UiState.Error(ex.message))
}
}
}
/**
* Search for a query string at Crunchyroll and emit the new ui state.
*/
fun search(query: String) {
// return if nothing has changed
if (query == oldSearchQuery) return
// update the old query since it has changed
oldSearchQuery = query
viewModelScope.launch {
// always cancel a running search job
if (searchJob?.isActive == true) searchJob?.cancel()
// handle state change: browse <-> search
if (query.isEmpty()) {
// if the query is empty change back to browse state
initBrowse()
} else {
// TODO handle errors
// if the current ui state is not search, clear the recyclerview
if (uiState.value !is UiState.Search) {
uiState.emit(UiState.Search(emptyList()))
}
// create a new search job
searchJob = viewModelScope.async {
// wait for a few ms: if the user is typing the task will get canceled
delay(250)
val results = Crunchyroll.search(query, 50)
.items.firstOrNull()?.items?.toItemMediaList()
?: listOf()
uiState.emit(UiState.Search(results))
}
}
}
}
fun onLazyLoad() = viewModelScope.launch {
isLazyLoading = true
try {
uiState.update { currentUiState ->
if (currentUiState is UiState.Browse) {
val newBrowseItems = Crunchyroll.browse(start = currentUiState.itemList.size, n = PAGESIZE)
.toItemMediaList()
currentUiState.itemList.addAll(newBrowseItems)
}
currentUiState
}
} catch (ex: Exception) {
uiState.emit(UiState.Error(ex.message))
}
isLazyLoading = false
}
private suspend fun initBrowse() {
try {
val initialBrowseItems = Crunchyroll.browse(n = PAGESIZE)
.toItemMediaList()
.toMutableList()
uiState.emit(UiState.Browse(initialBrowseItems))
} catch (ex: Exception) {
uiState.emit(UiState.Error(ex.message))
}
}
}

View File

@ -0,0 +1,31 @@
package org.mosad.teapod.ui.components
import android.content.Context
import android.util.AttributeSet
import android.view.KeyEvent
import android.widget.TextView
import androidx.appcompat.R
import androidx.appcompat.widget.SearchView
// see https://stackoverflow.com/questions/30046201/android-searchview-empty-query-doesnt-work
class EmptySubmitSearchView : SearchView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun setOnQueryTextListener(listener: OnQueryTextListener?) {
super.setOnQueryTextListener(listener)
findViewById<SearchAutoComplete?>(R.id.search_src_text).setOnEditorActionListener { _: TextView?, _: Int, event: KeyEvent? ->
if (event != null && event.keyCode == KeyEvent.KEYCODE_ENTER) {
listener?.onQueryTextSubmit(query.toString())
} else {
listener?.onQueryTextSubmit(query.toString())
}
false
}
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17,3L7,3c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,18L7,5h10v13z"/>
</vector>

View File

@ -7,10 +7,23 @@
android:background="?themePrimary"
tools:context=".ui.activity.main.fragments.LibraryFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_media_library"
<org.mosad.teapod.ui.components.EmptySubmitSearchView
android:id="@+id/search_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
android:background="?themeSecondary"
android:elevation="8dp"
android:iconifiedByDefault="false"
android:paddingBottom="5dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</org.mosad.teapod.ui.components.EmptySubmitSearchView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_media_search"
android:layout_width="match_parent"
android:layout_height="0dp"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="3dp"
@ -18,8 +31,10 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search_text"
app:spanCount="@integer/item_media_columns"
tools:listitem="@layout/item_media" />
tools:listitem="@layout/item_media">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -5,28 +5,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?themePrimary"
tools:context=".ui.activity.main.fragments.SearchFragment">
<SearchView
android:id="@+id/search_text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?themeSecondary"
android:elevation="8dp"
android:iconifiedByDefault="false"
android:paddingBottom="5dp"
android:queryHint="@string/search_hint"
android:searchIcon="@drawable/ic_baseline_search_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</SearchView>
tools:context=".ui.activity.main.fragments.MyListsFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_media_search"
android:id="@+id/recycler_media_library"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="3dp"
@ -34,10 +18,8 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search_text"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="@integer/item_media_columns"
tools:listitem="@layout/item_media">
</androidx.recyclerview.widget.RecyclerView>
tools:listitem="@layout/item_media" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -6,15 +6,15 @@
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_my_lists"
android:icon="@drawable/ic_baseline_bookmark_border_24"
android:title="@string/title_my_lists" />
<item
android:id="@+id/navigation_library"
android:icon="@drawable/ic_baseline_video_library_24"
android:title="@string/title_library" />
<item
android:id="@+id/navigation_search"
android:icon="@drawable/ic_baseline_search_24"
android:title="@string/title_search" />
<item
android:id="@+id/navigation_account"
android:icon="@drawable/ic_baseline_account_box_24"

View File

@ -11,18 +11,18 @@
android:label="@string/title_home"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/navigation_my_lists"
android:name="org.mosad.teapod.ui.activity.main.fragments.MyListsFragment"
android:label="@string/title_my_lists"
tools:layout="@layout/fragment_my_lists" />
<fragment
android:id="@+id/navigation_library"
android:name="org.mosad.teapod.ui.activity.main.fragments.LibraryFragment"
android:label="@string/title_library"
tools:layout="@layout/fragment_library" />
<fragment
android:id="@+id/navigation_search"
android:name="org.mosad.teapod.ui.activity.main.fragments.SearchFragment"
android:label="@string/title_search"
tools:layout="@layout/fragment_search" />
<fragment
android:id="@+id/navigation_account"
android:name="org.mosad.teapod.ui.activity.main.fragments.AccountFragment"

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_home">Startseite</string>
<string name="title_my_lists">Meine Listen</string>
<string name="title_library">Übersicht</string>
<string name="title_search">Suche</string>
<string name="title_account">Account</string>
<!-- home fragment -->

View File

@ -1,8 +1,8 @@
<resources>
<string name="app_name" translatable="false">Teapod</string>
<string name="title_home">Home</string>
<string name="title_my_lists">My Lists</string>
<string name="title_library">Library</string>
<string name="title_search">Search</string>
<string name="title_account">Account</string>
<!-- home fragment -->

View File

@ -5,6 +5,7 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="popupMenuStyle">@style/Widget.App.PopupMenu</item>
<item name="searchViewStyle">@style/SearchViewStyle</item>
</style>
<style name="AppTheme.Light" parent="AppTheme">
@ -50,6 +51,13 @@
<item name="android:textColor">?textPrimary</item>
</style>
<!-- search view theme -->
<style name="SearchViewStyle" parent="Widget.AppCompat.SearchView.ActionBar">
<item name="iconifiedByDefault">false</item>
<item name="searchIcon">@drawable/ic_baseline_search_24</item>
<item name="queryHint">@string/search_hint</item>
</style>
<!-- player theme -->
<style name="PlayerTheme" parent="AppTheme">
<item name="android:windowNoTitle">true</item>
@ -74,7 +82,6 @@
<item name="postSplashScreenTheme">@style/AppTheme.Dark</item> # Required.
</style>
<!-- shapes -->
<style name="ShapeAppearance.Teapod.RoundedPoster" parent="ShapeAppearance.MaterialComponents.LargeComponent">
<item name="cornerFamily">rounded</item>
@ -95,5 +102,4 @@
<item name="android:windowTranslucentNavigation">true</item>
</style>
</resources>