Search through all sources in catalog
This commit is contained in:
@@ -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,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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user