Quick search across manga sources
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
package org.koitharu.kotatsu.settings.sources
|
||||
|
||||
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.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
@@ -20,7 +19,7 @@ import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
|
||||
class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
SourceConfigListener {
|
||||
SourceConfigListener, SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener {
|
||||
|
||||
private var reorderHelper: ItemTouchHelper? = null
|
||||
private val viewModel by viewModel<SourcesSettingsViewModel>()
|
||||
@@ -61,6 +60,17 @@ class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.opt_sources, menu)
|
||||
val searchMenuItem = 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
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
binding.recyclerView.updatePadding(
|
||||
bottom = insets.bottom,
|
||||
@@ -85,6 +95,20 @@ class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
viewModel.expandOrCollapse(header.localeId)
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean = false
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
viewModel.performSearch(newText)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionExpand(item: MenuItem?): Boolean = true
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
(item.actionView as SearchView).setQuery("", false)
|
||||
return true
|
||||
}
|
||||
|
||||
private inner class SourcesReorderCallback : ItemTouchHelper.SimpleCallback(
|
||||
ItemTouchHelper.DOWN or ItemTouchHelper.UP,
|
||||
0,
|
||||
|
||||
@@ -21,6 +21,7 @@ class SourcesSettingsViewModel(
|
||||
|
||||
val items = MutableLiveData<List<SourceConfigItem>>(emptyList())
|
||||
private val expandedGroups = HashSet<String?>()
|
||||
private var searchQuery: String? = null
|
||||
|
||||
init {
|
||||
buildList()
|
||||
@@ -63,9 +64,30 @@ class SourcesSettingsViewModel(
|
||||
buildList()
|
||||
}
|
||||
|
||||
fun performSearch(query: String?) {
|
||||
searchQuery = query?.trim()
|
||||
buildList()
|
||||
}
|
||||
|
||||
private fun buildList() {
|
||||
val sources = MangaProviderFactory.getSources(settings, includeHidden = true)
|
||||
val hiddenSources = settings.hiddenSources
|
||||
val query = searchQuery
|
||||
if (!query.isNullOrEmpty()) {
|
||||
items.value = sources.mapNotNull {
|
||||
if (!it.title.contains(query, ignoreCase = true)) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
SourceConfigItem.SourceItem(
|
||||
source = it,
|
||||
isEnabled = it.name !in hiddenSources,
|
||||
isDraggable = false,
|
||||
)
|
||||
}.ifEmpty {
|
||||
listOf(SourceConfigItem.EmptySearchResult)
|
||||
}
|
||||
return
|
||||
}
|
||||
val map = sources.groupByTo(TreeMap(LocaleKeyComparator())) {
|
||||
if (it.name !in hiddenSources) {
|
||||
KEY_ENABLED
|
||||
|
||||
@@ -15,4 +15,5 @@ class SourceConfigAdapter(
|
||||
sourceConfigGroupDelegate(listener),
|
||||
sourceConfigItemDelegate(listener, coil, lifecycleOwner),
|
||||
sourceConfigDraggableItemDelegate(listener),
|
||||
sourceConfigEmptySearchDelegate(),
|
||||
)
|
||||
@@ -4,12 +4,11 @@ import android.annotation.SuppressLint
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePaddingRelative
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil.request.Disposable
|
||||
import coil.request.ImageRequest
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.databinding.ItemExpandableBinding
|
||||
@@ -53,25 +52,15 @@ fun sourceConfigItemDelegate(
|
||||
on = { item, _, _ -> item is SourceConfigItem.SourceItem && !item.isDraggable }
|
||||
) {
|
||||
|
||||
val eventListener = object : View.OnClickListener, CompoundButton.OnCheckedChangeListener {
|
||||
override fun onClick(v: View?) = listener.onItemSettingsClick(item)
|
||||
|
||||
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
|
||||
listener.onItemEnabledChanged(item, isChecked)
|
||||
}
|
||||
}
|
||||
var imageRequest: Disposable? = null
|
||||
|
||||
binding.imageViewConfig.setOnClickListener(eventListener)
|
||||
binding.switchToggle.setOnCheckedChangeListener(eventListener)
|
||||
binding.switchToggle.setOnCheckedChangeListener { _, isChecked ->
|
||||
listener.onItemEnabledChanged(item, isChecked)
|
||||
}
|
||||
|
||||
bind {
|
||||
binding.textViewTitle.text = item.source.title
|
||||
binding.switchToggle.isChecked = item.isEnabled
|
||||
binding.imageViewConfig.isVisible = item.isEnabled
|
||||
binding.root.updatePaddingRelative(
|
||||
end = if (item.isEnabled) 0 else binding.imageViewConfig.paddingEnd,
|
||||
)
|
||||
imageRequest = ImageRequest.Builder(context)
|
||||
.data(item.faviconUrl)
|
||||
.error(R.drawable.ic_favicon_fallback)
|
||||
@@ -119,9 +108,9 @@ fun sourceConfigDraggableItemDelegate(
|
||||
bind {
|
||||
binding.textViewTitle.text = item.source.title
|
||||
binding.switchToggle.isChecked = item.isEnabled
|
||||
binding.imageViewConfig.isVisible = item.isEnabled
|
||||
binding.root.updatePaddingRelative(
|
||||
end = if (item.isEnabled) 0 else binding.imageViewConfig.paddingEnd,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sourceConfigEmptySearchDelegate() = adapterDelegate<SourceConfigItem.EmptySearchResult, SourceConfigItem>(
|
||||
R.layout.item_sources_empty
|
||||
) { }
|
||||
@@ -2,21 +2,25 @@ package org.koitharu.kotatsu.settings.sources.adapter
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem.*
|
||||
|
||||
class SourceConfigDiffCallback : DiffUtil.ItemCallback<SourceConfigItem>() {
|
||||
|
||||
override fun areItemsTheSame(oldItem: SourceConfigItem, newItem: SourceConfigItem): Boolean {
|
||||
return when {
|
||||
oldItem.javaClass != newItem.javaClass -> false
|
||||
oldItem is SourceConfigItem.LocaleGroup && newItem is SourceConfigItem.LocaleGroup -> {
|
||||
oldItem is LocaleGroup && newItem is LocaleGroup -> {
|
||||
oldItem.localeId == newItem.localeId
|
||||
}
|
||||
oldItem is SourceConfigItem.SourceItem && newItem is SourceConfigItem.SourceItem -> {
|
||||
oldItem is SourceItem && newItem is SourceItem -> {
|
||||
oldItem.source == newItem.source
|
||||
}
|
||||
oldItem is SourceConfigItem.Header && newItem is SourceConfigItem.Header -> {
|
||||
oldItem is Header && newItem is Header -> {
|
||||
oldItem.titleResId == newItem.titleResId
|
||||
}
|
||||
oldItem == EmptySearchResult && newItem == EmptySearchResult -> {
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,4 +76,6 @@ sealed interface SourceConfigItem {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
object EmptySearchResult : SourceConfigItem
|
||||
}
|
||||
Reference in New Issue
Block a user