Quick search across genres in filter
This commit is contained in:
@@ -5,10 +5,15 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import androidx.appcompat.app.AppCompatDialog
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.koitharu.kotatsu.R
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||
|
||||
@@ -17,6 +22,9 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||
protected val binding: B
|
||||
get() = checkNotNull(viewBinding)
|
||||
|
||||
protected val behavior: BottomSheetBehavior<*>?
|
||||
get() = (dialog as? BottomSheetDialog)?.behavior
|
||||
|
||||
final override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
@@ -39,4 +47,17 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||
}
|
||||
|
||||
protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B
|
||||
|
||||
protected fun setExpanded(isExpanded: Boolean, isLocked: Boolean) {
|
||||
val b = behavior ?: return
|
||||
if (isExpanded) {
|
||||
b.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
b.isFitToContents = !isExpanded
|
||||
val rootView = dialog?.findViewById<View>(materialR.id.design_bottom_sheet)
|
||||
rootView?.updateLayoutParams {
|
||||
height = if (isExpanded) LayoutParams.MATCH_PARENT else LayoutParams.WRAP_CONTENT
|
||||
}
|
||||
b.isDraggable = !isLocked
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package org.koitharu.kotatsu.list.ui.filter
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.*
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import org.koin.androidx.viewmodel.ViewModelOwner.Companion.from
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
@@ -15,9 +14,11 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.databinding.SheetFilterBinding
|
||||
import org.koitharu.kotatsu.utils.BottomSheetToolbarController
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
class FilterBottomSheet : BaseBottomSheet<SheetFilterBinding>() {
|
||||
class FilterBottomSheet : BaseBottomSheet<SheetFilterBinding>(), MenuItem.OnActionExpandListener,
|
||||
SearchView.OnQueryTextListener, DialogInterface.OnKeyListener {
|
||||
|
||||
private val viewModel by sharedViewModel<FilterViewModel>(
|
||||
owner = { from(requireParentFragment(), requireParentFragment()) }
|
||||
@@ -33,6 +34,12 @@ class FilterBottomSheet : BaseBottomSheet<SheetFilterBinding>() {
|
||||
viewModel.updateState(state)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return super.onCreateDialog(savedInstanceState).also {
|
||||
it.setOnKeyListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetFilterBinding {
|
||||
return SheetFilterBinding.inflate(inflater, container, false)
|
||||
}
|
||||
@@ -40,6 +47,7 @@ class FilterBottomSheet : BaseBottomSheet<SheetFilterBinding>() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.toolbar.setNavigationOnClickListener { dismiss() }
|
||||
behavior?.addBottomSheetCallback(BottomSheetToolbarController(binding.toolbar))
|
||||
if (!resources.getBoolean(R.bool.is_tablet)) {
|
||||
binding.toolbar.navigationIcon = null
|
||||
}
|
||||
@@ -49,24 +57,49 @@ class FilterBottomSheet : BaseBottomSheet<SheetFilterBinding>() {
|
||||
viewModel.result.observe(viewLifecycleOwner) {
|
||||
parentFragmentManager.setFragmentResult(REQUEST_KEY, bundleOf(ARG_STATE to it))
|
||||
}
|
||||
initOptionsMenu()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?) = super.onCreateDialog(savedInstanceState).also {
|
||||
val behavior = (it as? BottomSheetDialog)?.behavior ?: return@also
|
||||
behavior.addBottomSheetCallback(
|
||||
object : BottomSheetBehavior.BottomSheetCallback() {
|
||||
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
|
||||
setExpanded(isExpanded = true, isLocked = true)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
val searchView = (item.actionView as? SearchView) ?: return false
|
||||
searchView.setQuery("", false)
|
||||
searchView.post { setExpanded(isExpanded = false, isLocked = false) }
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
binding.toolbar.setNavigationIcon(R.drawable.ic_cross)
|
||||
} else {
|
||||
binding.toolbar.navigationIcon = null
|
||||
}
|
||||
override fun onQueryTextSubmit(query: String?): Boolean = false
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
viewModel.performSearch(newText?.trim().orEmpty())
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onKey(dialog: DialogInterface?, keyCode: Int, event: KeyEvent?): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
val menuItem = binding.toolbar.menu.findItem(R.id.action_search) ?: return false
|
||||
if (menuItem.isActionViewExpanded) {
|
||||
if (event?.action == KeyEvent.ACTION_UP) {
|
||||
menuItem.collapseActionView()
|
||||
}
|
||||
return true
|
||||
}
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun initOptionsMenu() {
|
||||
binding.toolbar.inflateMenu(R.menu.opt_filter)
|
||||
val searchMenuItem = binding.toolbar.menu.findItem(R.id.action_search)
|
||||
searchMenuItem.setOnActionExpandListener(this)
|
||||
val searchView = searchMenuItem.actionView as SearchView
|
||||
searchView.setOnQueryTextListener(this)
|
||||
searchView.setIconifiedByDefault(false)
|
||||
searchView.queryHint = searchMenuItem.title
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -9,4 +9,23 @@ import org.koitharu.kotatsu.core.model.SortOrder
|
||||
class FilterState(
|
||||
val sortOrder: SortOrder?,
|
||||
val tags: Set<MangaTag>,
|
||||
) : Parcelable
|
||||
) : Parcelable {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as FilterState
|
||||
|
||||
if (sortOrder != other.sortOrder) return false
|
||||
if (tags != other.tags) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = sortOrder?.hashCode() ?: 0
|
||||
result = 31 * result + tags.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.list.ui.filter
|
||||
|
||||
import androidx.annotation.AnyThread
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.*
|
||||
@@ -24,6 +25,7 @@ class FilterViewModel(
|
||||
private var job: Job? = null
|
||||
private var selectedSortOrder: SortOrder? = repository.sortOrders.firstOrNull()
|
||||
private val selectedTags = HashSet<MangaTag>()
|
||||
private var searchQuery: String = ""
|
||||
private val localTagsDeferred = viewModelScope.async(Dispatchers.Default) {
|
||||
dataRepository.findTags(repository.source)
|
||||
}
|
||||
@@ -31,7 +33,7 @@ class FilterViewModel(
|
||||
|
||||
override fun onSortItemClick(item: FilterItem.Sort) {
|
||||
selectedSortOrder = item.order
|
||||
updateFilters()
|
||||
updateFilters(updateResults = true)
|
||||
}
|
||||
|
||||
override fun onTagItemClick(item: FilterItem.Tag) {
|
||||
@@ -41,7 +43,7 @@ class FilterViewModel(
|
||||
selectedTags.add(item.tag)
|
||||
}
|
||||
if (isModified) {
|
||||
updateFilters()
|
||||
updateFilters(updateResults = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,15 +55,27 @@ class FilterViewModel(
|
||||
if (job == null) {
|
||||
showFilter()
|
||||
} else {
|
||||
updateFilters()
|
||||
updateFilters(updateResults = false)
|
||||
}
|
||||
}
|
||||
|
||||
fun performSearch(query: String) {
|
||||
if (searchQuery != query) {
|
||||
searchQuery = query
|
||||
updateFilters(updateResults = false)
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
private fun updateFilters() {
|
||||
private fun updateFilters(updateResults: Boolean) {
|
||||
val previousJob = job
|
||||
val query = searchQuery
|
||||
job = launchJob(Dispatchers.Default) {
|
||||
previousJob?.cancelAndJoin()
|
||||
if (query.isNotEmpty()) {
|
||||
showFilteredTags(query)
|
||||
return@launchJob
|
||||
}
|
||||
val tags = tryLoadTags()
|
||||
val localTags = localTagsDeferred.await()
|
||||
val sortOrders = repository.sortOrders
|
||||
@@ -84,7 +98,9 @@ class FilterViewModel(
|
||||
ensureActive()
|
||||
filter.postValue(list)
|
||||
}
|
||||
result.postValue(FilterState(selectedSortOrder, selectedTags))
|
||||
if (updateResults) {
|
||||
result.postValue(FilterState(selectedSortOrder, selectedTags))
|
||||
}
|
||||
}
|
||||
|
||||
private fun showFilter() {
|
||||
@@ -103,10 +119,48 @@ class FilterViewModel(
|
||||
}
|
||||
list.add(FilterItem.Loading)
|
||||
filter.postValue(list)
|
||||
updateFilters()
|
||||
updateFilters(updateResults = false)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private suspend fun showFilteredTags(query: String) {
|
||||
val tags = tryLoadTags()
|
||||
val localTags = localTagsDeferred.await()
|
||||
val list = ArrayList<FilterItem>()
|
||||
val mappedTags = TreeSet<FilterItem.Tag>(compareBy({ !it.isChecked }, { it.tag.title }))
|
||||
localTags.mapNotNullTo(mappedTags) {
|
||||
if (it.title.contains(query, ignoreCase = true)) {
|
||||
FilterItem.Tag(it, isChecked = it in selectedTags)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
tags?.mapNotNullTo(mappedTags) {
|
||||
if (it.title.contains(query, ignoreCase = true)) {
|
||||
FilterItem.Tag(it, isChecked = it in selectedTags)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
selectedTags.mapNotNullTo(mappedTags) {
|
||||
if (it.title.contains(query, ignoreCase = true)) {
|
||||
FilterItem.Tag(it, isChecked = true)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
list.addAll(mappedTags)
|
||||
if (tags == null) {
|
||||
list.add(FilterItem.Error(R.string.filter_load_error))
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
list.add(FilterItem.Error(R.string.nothing_found))
|
||||
}
|
||||
currentCoroutineContext().ensureActive()
|
||||
filter.postValue(list)
|
||||
}
|
||||
|
||||
private suspend fun tryLoadTags(): Set<MangaTag>? {
|
||||
val shouldRetryOnError = availableTagsDeferred.isCompleted
|
||||
val result = availableTagsDeferred.await()
|
||||
|
||||
@@ -7,8 +7,6 @@ import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -20,6 +18,7 @@ import org.koitharu.kotatsu.databinding.SheetChaptersBinding
|
||||
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.details.ui.model.toListItem
|
||||
import org.koitharu.kotatsu.utils.BottomSheetToolbarController
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemClickListener<ChapterListItem> {
|
||||
@@ -31,6 +30,7 @@ class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemC
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.toolbar.setNavigationOnClickListener { dismiss() }
|
||||
behavior?.addBottomSheetCallback(BottomSheetToolbarController(binding.toolbar))
|
||||
if (!resources.getBoolean(R.bool.is_tablet)) {
|
||||
binding.toolbar.navigationIcon = null
|
||||
}
|
||||
@@ -65,24 +65,6 @@ class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemC
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?) = super.onCreateDialog(savedInstanceState).also {
|
||||
val behavior = (it as? BottomSheetDialog)?.behavior ?: return@also
|
||||
behavior.addBottomSheetCallback(
|
||||
object : BottomSheetBehavior.BottomSheetCallback() {
|
||||
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
|
||||
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
binding.toolbar.setNavigationIcon(R.drawable.ic_cross)
|
||||
} else {
|
||||
binding.toolbar.navigationIcon = null
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onItemClick(item: ChapterListItem, view: View) {
|
||||
((parentFragment as? OnChapterChangeListener) ?: (activity as? OnChapterChangeListener))?.let {
|
||||
dismiss()
|
||||
|
||||
@@ -4,11 +4,10 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
@@ -19,6 +18,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.SheetPagesBinding
|
||||
import org.koitharu.kotatsu.list.ui.MangaListSpanResolver
|
||||
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
|
||||
import org.koitharu.kotatsu.utils.BottomSheetToolbarController
|
||||
import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
@@ -59,6 +59,7 @@ class PagesThumbnailsSheet : BaseBottomSheet<SheetPagesBinding>(),
|
||||
binding.toolbar.title = title
|
||||
binding.toolbar.setNavigationOnClickListener { dismiss() }
|
||||
binding.toolbar.subtitle = null
|
||||
behavior?.addBottomSheetCallback(ToolbarController(binding.toolbar))
|
||||
|
||||
if (!resources.getBoolean(R.bool.is_tablet)) {
|
||||
binding.toolbar.navigationIcon = null
|
||||
@@ -93,29 +94,6 @@ class PagesThumbnailsSheet : BaseBottomSheet<SheetPagesBinding>(),
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?) =
|
||||
super.onCreateDialog(savedInstanceState).also {
|
||||
val behavior = (it as? BottomSheetDialog)?.behavior ?: return@also
|
||||
behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
|
||||
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
|
||||
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
binding.toolbar.setNavigationIcon(R.drawable.ic_cross)
|
||||
binding.toolbar.subtitle =
|
||||
resources.getQuantityString(R.plurals.pages,
|
||||
thumbnails.size,
|
||||
thumbnails.size)
|
||||
} else {
|
||||
binding.toolbar.navigationIcon = null
|
||||
binding.toolbar.subtitle = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
override fun onItemClick(item: MangaPage, view: View) {
|
||||
((parentFragment as? OnPageSelectListener)
|
||||
?: (activity as? OnPageSelectListener))?.run {
|
||||
@@ -124,6 +102,21 @@ class PagesThumbnailsSheet : BaseBottomSheet<SheetPagesBinding>(),
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ToolbarController(toolbar: Toolbar) : BottomSheetToolbarController(toolbar) {
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
super.onStateChanged(bottomSheet, newState)
|
||||
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
toolbar.subtitle = resources.getQuantityString(
|
||||
R.plurals.pages,
|
||||
thumbnails.size,
|
||||
thumbnails.size
|
||||
)
|
||||
} else {
|
||||
toolbar.subtitle = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARG_PAGES = "pages"
|
||||
|
||||
@@ -84,6 +84,9 @@ class RemoteListViewModel(
|
||||
}
|
||||
|
||||
fun applyFilter(newFilter: FilterState) {
|
||||
if (filter == newFilter) {
|
||||
return
|
||||
}
|
||||
filter = newFilter
|
||||
headerModel.value = ListHeader(repository.title, 0, newFilter.sortOrder)
|
||||
mangaList.value = null
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.koitharu.kotatsu.utils
|
||||
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
open class BottomSheetToolbarController(
|
||||
protected val toolbar: Toolbar,
|
||||
) : BottomSheetBehavior.BottomSheetCallback() {
|
||||
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
toolbar.setNavigationIcon(materialR.drawable.abc_ic_clear_material)
|
||||
} else {
|
||||
toolbar.navigationIcon = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.liveData
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import org.koitharu.kotatsu.utils.BufferedObserver
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
@@ -18,6 +17,16 @@ fun <T> LiveData<T?>.observeNotNull(owner: LifecycleOwner, observer: Observer<T>
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> LiveData<T>.observeDistinct(owner: LifecycleOwner, observer: Observer<T>) {
|
||||
var previousValue: T? = null
|
||||
this.observe(owner) {
|
||||
if (it != previousValue) {
|
||||
observer.onChanged(it)
|
||||
previousValue = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> LiveData<T>.observeWithPrevious(owner: LifecycleOwner, observer: BufferedObserver<T>) {
|
||||
var previous: T? = null
|
||||
this.observe(owner) {
|
||||
|
||||
13
app/src/main/res/menu/opt_filter.xml
Normal file
13
app/src/main/res/menu/opt_filter.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_search"
|
||||
android:icon="@drawable/ic_search"
|
||||
android:title="@string/find_genre"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="ifRoom|collapseActionView" />
|
||||
|
||||
</menu>
|
||||
@@ -265,4 +265,5 @@
|
||||
<string name="disabled">Disabled</string>
|
||||
<string name="filter_load_error">Unable to load genres list</string>
|
||||
<string name="reset_filter">Reset filter</string>
|
||||
<string name="find_genre">Find genre</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user