Search through all sources in catalog

pull/565/head
Koitharu 2 years ago
parent b9fd2e100d
commit 95fbe496cb
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -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 {

@ -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)

@ -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) {

@ -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,
) )
} }
} }

@ -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
} }

@ -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)
} }

@ -23,18 +23,35 @@
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:orientation="vertical">
<TextView
android:id="@+id/textView_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end" android:ellipsize="end"
android:singleLine="true" android:singleLine="true"
android:textAppearance="?attr/textAppearanceTitleSmall" android:textAppearance="?attr/textAppearanceTitleSmall"
tools:text="@tools:sample/lorem[15]" /> 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"
android:layout_width="wrap_content" android:layout_width="wrap_content"

@ -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>

Loading…
Cancel
Save