Local manga source filter

This commit is contained in:
Koitharu
2024-09-23 19:22:48 +03:00
parent 8365603bf1
commit bc4622d610
7 changed files with 50 additions and 22 deletions

View File

@@ -97,7 +97,7 @@ class FilterFieldLayout @JvmOverloads constructor(
label,
context.getThemeColorStateList(materialR.attr.colorControlNormal),
)
addView(errorView)
addView(label)
errorView = label
return label
}

View File

@@ -11,6 +11,7 @@ import org.koitharu.kotatsu.filter.ui.model.TagCatalogItem
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.errorFooterAD
import org.koitharu.kotatsu.list.ui.adapter.errorStateListAD
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -24,6 +25,7 @@ class TagsCatalogAdapter(
addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())
addDelegate(ListItemType.FOOTER_ERROR, errorFooterAD(null))
addDelegate(ListItemType.STATE_ERROR, errorStateListAD(null))
}
override fun getSectionText(context: Context, position: Int): CharSequence? {

View File

@@ -11,20 +11,22 @@ import org.koitharu.kotatsu.list.ui.model.ErrorState
import org.koitharu.kotatsu.list.ui.model.ListModel
fun errorStateListAD(
listener: ListStateHolderListener,
listener: ListStateHolderListener?,
) = adapterDelegateViewBinding<ErrorState, ListModel, ItemErrorStateBinding>(
{ inflater, parent -> ItemErrorStateBinding.inflate(inflater, parent, false) },
) {
val onClickListener = View.OnClickListener { v ->
when (v.id) {
R.id.button_retry -> listener.onRetryClick(item.exception)
R.id.button_secondary -> listener.onSecondaryErrorActionClick(item.exception)
if (listener != null) {
val onClickListener = View.OnClickListener { v ->
when (v.id) {
R.id.button_retry -> listener.onRetryClick(item.exception)
R.id.button_secondary -> listener.onSecondaryErrorActionClick(item.exception)
}
}
}
binding.buttonRetry.setOnClickListener(onClickListener)
binding.buttonSecondary.setOnClickListener(onClickListener)
binding.buttonRetry.setOnClickListener(onClickListener)
binding.buttonSecondary.setOnClickListener(onClickListener)
}
bind {
with(binding.textViewError) {
@@ -32,7 +34,7 @@ fun errorStateListAD(
setCompoundDrawablesWithIntrinsicBounds(0, item.icon, 0, 0)
}
with(binding.buttonRetry) {
isVisible = item.canRetry
isVisible = item.canRetry && listener != null
setText(item.buttonText)
}
binding.buttonSecondary.setTextAndVisible(item.secondaryButtonText)

View File

@@ -25,13 +25,16 @@ import org.koitharu.kotatsu.local.data.output.LocalMangaOutput
import org.koitharu.kotatsu.local.data.output.LocalMangaUtil
import org.koitharu.kotatsu.local.domain.MangaLock
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import java.io.File
import java.util.EnumSet
@@ -67,7 +70,14 @@ class LocalMangaRepository @Inject constructor(
settings.localListOrder = value
}
override suspend fun getFilterOptions() = MangaListFilterOptions()
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = localMangaIndex.getAvailableTags().mapToSet { MangaTag(title = it, key = it, source = source) },
availableContentRating = if (!settings.isNsfwContentDisabled) {
EnumSet.of(ContentRating.SAFE, ContentRating.ADULT)
} else {
emptySet()
},
)
override suspend fun getList(offset: Int, order: SortOrder?, filter: MangaListFilter?): List<Manga> {
if (offset > 0) {
@@ -75,7 +85,7 @@ class LocalMangaRepository @Inject constructor(
}
val list = getRawList()
if (settings.isNsfwContentDisabled) {
list.removeIf { it.manga.isNsfw }
list.removeAll { it.manga.isNsfw }
}
if (filter != null) {
val query = filter.query
@@ -83,10 +93,14 @@ class LocalMangaRepository @Inject constructor(
list.retainAll { x -> x.isMatchesQuery(query) }
}
if (filter.tags.isNotEmpty()) {
list.retainAll { x -> x.containsTags(filter.tags) }
list.retainAll { x -> x.containsTags(filter.tags.mapToSet { it.title }) }
}
if (filter.tagsExclude.isNotEmpty()) {
list.removeAll { x -> x.containsAnyTag(filter.tags) }
list.removeAll { x -> x.containsAnyTag(filter.tagsExclude.mapToSet { it.title }) }
}
filter.contentRating.singleOrNull()?.let { contentRating ->
val isNsfw = contentRating == ContentRating.ADULT
list.retainAll { it.manga.isNsfw == isNsfw }
}
}
when (order) {

View File

@@ -16,6 +16,7 @@ import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.local.data.LocalStorageManager
import org.koitharu.kotatsu.local.data.input.LocalMangaInput
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import java.io.File
import javax.inject.Inject
@@ -85,6 +86,10 @@ class LocalMangaIndex @Inject constructor(
db.getLocalMangaIndexDao().delete(mangaId)
}
suspend fun getAvailableTags(): List<String> {
return db.getLocalMangaIndexDao().findTags()
}
private fun LocalManga.toEntity() = LocalMangaIndexEntity(
mangaId = manga.id,
path = file.path,

View File

@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.local.data.index
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Upsert
import org.koitharu.kotatsu.core.db.entity.TagEntity
@Dao
interface LocalMangaIndexDao {
@@ -11,6 +10,9 @@ interface LocalMangaIndexDao {
@Query("SELECT path FROM local_index WHERE manga_id = :mangaId")
suspend fun findPath(mangaId: Long): String?
@Query("SELECT title FROM local_index LEFT JOIN manga_tags ON manga_tags.manga_id = local_index.manga_id LEFT JOIN tags ON tags.tag_id = manga_tags.tag_id WHERE title IS NOT NULL GROUP BY title")
suspend fun findTags(): List<String>
@Upsert
suspend fun upsert(entity: LocalMangaIndexEntity)

View File

@@ -23,17 +23,20 @@ data class LocalManga(
fun isMatchesQuery(query: String): Boolean {
return manga.title.contains(query, ignoreCase = true) ||
manga.altTitle?.contains(query, ignoreCase = true) == true
manga.altTitle?.contains(query, ignoreCase = true) == true ||
manga.author?.contains(query, ignoreCase = true) == true
}
fun containsTags(tags: Set<MangaTag>): Boolean {
return manga.tags.containsAll(tags)
fun containsTags(tags: Collection<String>): Boolean {
return tags.all { tag -> tag in manga.tags }
}
fun containsAnyTag(tags: Set<MangaTag>): Boolean {
return tags.any { tag ->
manga.tags.contains(tag)
}
fun containsAnyTag(tags: Collection<String>): Boolean {
return tags.any { tag -> tag in manga.tags }
}
private operator fun Collection<MangaTag>.contains(title: String): Boolean {
return any { it.title.equals(title, ignoreCase = true) }
}
override fun toString(): String {