Search through all sources in catalog

This commit is contained in:
Koitharu
2023-11-22 16:11:23 +02:00
parent b9fd2e100d
commit 95fbe496cb
8 changed files with 66 additions and 22 deletions

View File

@@ -8,7 +8,8 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
sealed interface SourceCatalogItem : ListModel { sealed interface SourceCatalogItem : ListModel {
data class Source( data class Source(
val source: MangaSource val source: MangaSource,
val showSummary: Boolean,
) : SourceCatalogItem { ) : SourceCatalogItem {
override fun areItemsTheSame(other: ListModel): Boolean { override fun areItemsTheSame(other: ListModel): Boolean {

View File

@@ -6,6 +6,7 @@ import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.getSummary
import org.koitharu.kotatsu.core.model.isNsfw import org.koitharu.kotatsu.core.model.isNsfw
import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
@@ -43,6 +44,12 @@ fun sourceCatalogItemSourceAD(
} else { } else {
item.source.title item.source.title
} }
if (item.showSummary) {
binding.textViewDescription.text = item.source.getSummary(context)
binding.textViewDescription.isVisible = true
} else {
binding.textViewDescription.isVisible = false
}
val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)
binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run {
crossfade(context) crossfade(context)

View File

@@ -1,9 +1,12 @@
package org.koitharu.kotatsu.settings.sources.catalog package org.koitharu.kotatsu.settings.sources.catalog
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem
import android.view.View import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.widget.SearchView
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import coil.ImageLoader import coil.ImageLoader
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
@@ -28,7 +31,7 @@ import javax.inject.Inject
class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(), class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
TabLayout.OnTabSelectedListener, TabLayout.OnTabSelectedListener,
OnListItemClickListener<SourceCatalogItem.Source>, OnListItemClickListener<SourceCatalogItem.Source>,
AppBarOwner { AppBarOwner, MenuItem.OnActionExpandListener {
@Inject @Inject
lateinit var coil: ImageLoader lateinit var coil: ImageLoader
@@ -56,7 +59,7 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
viewModel.locale.observe(this) { viewModel.locale.observe(this) {
supportActionBar?.subtitle = it.getLocaleDisplayName() supportActionBar?.subtitle = it.getLocaleDisplayName()
} }
addMenuProvider(SourcesCatalogMenuProvider(this, viewModel)) addMenuProvider(SourcesCatalogMenuProvider(this, viewModel, this))
} }
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
@@ -83,6 +86,19 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
viewBinding.recyclerView.firstVisibleItemPosition = 0 viewBinding.recyclerView.firstVisibleItemPosition = 0
} }
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
viewBinding.tabs.isVisible = false
val sq = (item.actionView as? SearchView)?.query?.trim()?.toString().orEmpty()
viewModel.performSearch(sq)
return true
}
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
viewBinding.tabs.isVisible = true
viewModel.performSearch(null)
return true
}
private fun initTabs() { private fun initTabs() {
val tabs = viewBinding.tabs val tabs = viewBinding.tabs
for (type in ContentType.entries) { for (type in ContentType.entries) {

View File

@@ -28,7 +28,7 @@ class SourcesCatalogListProducer @AssistedInject constructor(
) : InvalidationTracker.Observer(TABLE_SOURCES), RetainedLifecycle.OnClearedListener { ) : InvalidationTracker.Observer(TABLE_SOURCES), RetainedLifecycle.OnClearedListener {
private val scope = lifecycle.lifecycleScope private val scope = lifecycle.lifecycleScope
private var query: String = "" private var query: String? = null
val list = MutableStateFlow(emptyList<SourceCatalogItem>()) val list = MutableStateFlow(emptyList<SourceCatalogItem>())
private var job = scope.launch(Dispatchers.Default) { private var job = scope.launch(Dispatchers.Default) {
@@ -54,20 +54,21 @@ class SourcesCatalogListProducer @AssistedInject constructor(
} }
} }
fun setQuery(value: String) { fun setQuery(value: String?) {
this.query = value this.query = value
onInvalidated(emptySet()) onInvalidated(emptySet())
} }
private suspend fun buildList(): List<SourceCatalogItem> { private suspend fun buildList(): List<SourceCatalogItem> {
val sources = repository.getDisabledSources().toMutableList() val sources = repository.getDisabledSources().toMutableList()
sources.retainAll { it.contentType == contentType && it.locale == locale } when (val q = query) {
if (query.isNotEmpty()) { null -> sources.retainAll { it.contentType == contentType && it.locale == locale }
sources.retainAll { it.title.contains(query, ignoreCase = true) } "" -> return emptyList()
else -> sources.retainAll { it.title.contains(q, ignoreCase = true) }
} }
return if (sources.isEmpty()) { return if (sources.isEmpty()) {
listOf( listOf(
if (query.isEmpty()) { if (query == null) {
SourceCatalogItem.Hint( SourceCatalogItem.Hint(
icon = R.drawable.ic_empty_feed, icon = R.drawable.ic_empty_feed,
title = R.string.no_manga_sources, title = R.string.no_manga_sources,
@@ -86,6 +87,7 @@ class SourcesCatalogListProducer @AssistedInject constructor(
sources.map { sources.map {
SourceCatalogItem.Source( SourceCatalogItem.Source(
source = it, source = it,
showSummary = query != null,
) )
} }
} }

View File

@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.parsers.util.toTitleCase
class SourcesCatalogMenuProvider( class SourcesCatalogMenuProvider(
private val activity: Activity, private val activity: Activity,
private val viewModel: SourcesCatalogViewModel, private val viewModel: SourcesCatalogViewModel,
private val expandListener: MenuItem.OnActionExpandListener,
) : MenuProvider, ) : MenuProvider,
MenuItem.OnActionExpandListener, MenuItem.OnActionExpandListener,
SearchView.OnQueryTextListener { SearchView.OnQueryTextListener {
@@ -40,18 +41,18 @@ class SourcesCatalogMenuProvider(
override fun onMenuItemActionExpand(item: MenuItem): Boolean { override fun onMenuItemActionExpand(item: MenuItem): Boolean {
(activity as? AppBarOwner)?.appBar?.setExpanded(false, true) (activity as? AppBarOwner)?.appBar?.setExpanded(false, true)
return true return expandListener.onMenuItemActionExpand(item)
} }
override fun onMenuItemActionCollapse(item: MenuItem): Boolean { override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
(item.actionView as SearchView).setQuery("", false) (item.actionView as SearchView).setQuery("", false)
return true return expandListener.onMenuItemActionCollapse(item)
} }
override fun onQueryTextSubmit(query: String?): Boolean = false override fun onQueryTextSubmit(query: String?): Boolean = false
override fun onQueryTextChange(newText: String?): Boolean { override fun onQueryTextChange(newText: String?): Boolean {
viewModel.performSearch(newText.orEmpty()) viewModel.performSearch(newText?.trim().orEmpty())
return true return true
} }

View File

@@ -29,11 +29,11 @@ import javax.inject.Inject
class SourcesCatalogViewModel @Inject constructor( class SourcesCatalogViewModel @Inject constructor(
private val repository: MangaSourcesRepository, private val repository: MangaSourcesRepository,
private val listProducerFactory: SourcesCatalogListProducer.Factory, private val listProducerFactory: SourcesCatalogListProducer.Factory,
private val settings: AppSettings, settings: AppSettings,
) : BaseViewModel() { ) : BaseViewModel() {
private val lifecycle = RetainedLifecycleImpl() private val lifecycle = RetainedLifecycleImpl()
private var searchQuery: String = "" private var searchQuery: String? = null
val onActionDone = MutableEventFlow<ReversibleAction>() val onActionDone = MutableEventFlow<ReversibleAction>()
val contentType = MutableStateFlow(ContentType.entries.first()) val contentType = MutableStateFlow(ContentType.entries.first())
val locales = getLocalesImpl() val locales = getLocalesImpl()
@@ -59,7 +59,7 @@ class SourcesCatalogViewModel @Inject constructor(
lifecycle.dispatchOnCleared() lifecycle.dispatchOnCleared()
} }
fun performSearch(query: String) { fun performSearch(query: String?) {
searchQuery = query searchQuery = query
listProducer.value?.setQuery(query) listProducer.value?.setQuery(query)
} }

View File

@@ -23,17 +23,34 @@
app:shapeAppearance="?shapeAppearanceCornerSmall" app:shapeAppearance="?shapeAppearanceCornerSmall"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<TextView <LinearLayout
android:id="@+id/textView_title"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="?android:listPreferredItemPaddingStart" android:layout_marginStart="?android:listPreferredItemPaddingStart"
android:layout_marginEnd="?android:listPreferredItemPaddingEnd" android:layout_marginEnd="?android:listPreferredItemPaddingEnd"
android:layout_weight="1" android:layout_weight="1"
android:ellipsize="end" android:orientation="vertical">
android:singleLine="true"
android:textAppearance="?attr/textAppearanceTitleSmall" <TextView
tools:text="@tools:sample/lorem[15]" /> android:id="@+id/textView_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceTitleSmall"
tools:text="@tools:sample/lorem[15]" />
<TextView
android:id="@+id/textView_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
tools:text="English" />
</LinearLayout>
<ImageView <ImageView
android:id="@+id/imageView_add" android:id="@+id/imageView_add"

View File

@@ -519,7 +519,7 @@
<string name="source_summary_pattern">%1$s, %2$s</string> <string name="source_summary_pattern">%1$s, %2$s</string>
<string name="sources_catalog">Sources catalog</string> <string name="sources_catalog">Sources catalog</string>
<string name="source_enabled">Source enabled</string> <string name="source_enabled">Source enabled</string>
<string name="no_manga_sources_catalog_text">No available sources in this section yet. Stay tuned</string> <string name="no_manga_sources_catalog_text">There are no sources available in this section, or all of it might have been already added.\nStay tuned</string>
<string name="no_manga_sources_found">No available manga sources found by your query</string> <string name="no_manga_sources_found">No available manga sources found by your query</string>
<string name="catalog">Catalog</string> <string name="catalog">Catalog</string>
<string name="manage_sources">Manage sources</string> <string name="manage_sources">Manage sources</string>