Fix sources catalog content types

This commit is contained in:
Koitharu
2024-09-24 10:18:10 +03:00
committed by Mac135135
parent 0ee1cda0e4
commit 2191d9c83b
8 changed files with 70 additions and 44 deletions

View File

@@ -16,8 +16,8 @@ android {
applicationId 'org.koitharu.kotatsu' applicationId 'org.koitharu.kotatsu'
minSdk = 21 minSdk = 21
targetSdk = 35 targetSdk = 35
versionCode = 670 versionCode = 671
versionName = '7.6-a2' versionName = '7.6-a3'
generatedDensities = [] generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp { ksp {
@@ -83,7 +83,7 @@ afterEvaluate {
} }
dependencies { dependencies {
//noinspection GradleDependency //noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:1.0') { implementation('com.github.KotatsuApp:kotatsu-parsers:613623fa53') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }

View File

@@ -80,6 +80,7 @@ val Demographic.titleResId: Int
Demographic.SHOUJO -> R.string.demographic_shoujo Demographic.SHOUJO -> R.string.demographic_shoujo
Demographic.SEINEN -> R.string.demographic_seinen Demographic.SEINEN -> R.string.demographic_seinen
Demographic.JOSEI -> R.string.demographic_josei Demographic.JOSEI -> R.string.demographic_josei
Demographic.KODOMO -> R.string.demographic_kodomo
Demographic.NONE -> R.string.none Demographic.NONE -> R.string.none
} }

View File

@@ -61,6 +61,7 @@ val ContentType.titleResId
ContentType.MANHWA -> R.string.content_type_manhwa ContentType.MANHWA -> R.string.content_type_manhwa
ContentType.MANHUA -> R.string.content_type_manhua ContentType.MANHUA -> R.string.content_type_manhua
ContentType.NOVEL -> R.string.content_type_novel ContentType.NOVEL -> R.string.content_type_novel
ContentType.ONE_SHOT -> R.string.content_type_one_shot
} }
tailrec fun MangaSource.unwrap(): MangaSource = if (this is MangaSourceInfo) { tailrec fun MangaSource.unwrap(): MangaSource = if (this is MangaSourceInfo) {

View File

@@ -89,7 +89,7 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is HttpException -> getHttpDisplayMessage(response.code, resources) is HttpException -> getHttpDisplayMessage(response.code, resources)
is HttpStatusException -> getHttpDisplayMessage(statusCode, resources) is HttpStatusException -> getHttpDisplayMessage(statusCode, resources)
else -> getDisplayMessage(message, resources) ?: localizedMessage else -> getDisplayMessage(message, resources) ?: message
}.ifNullOrEmpty { }.ifNullOrEmpty {
resources.getString(R.string.error_occurred) resources.getString(R.string.error_occurred)
} }

View File

@@ -5,6 +5,7 @@ import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel
import org.koitharu.kotatsu.filter.ui.model.FilterProperty import org.koitharu.kotatsu.filter.ui.model.FilterProperty
import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.search.domain.MangaSearchRepository import org.koitharu.kotatsu.search.domain.MangaSearchRepository
@@ -19,6 +20,7 @@ class FilterHeaderProducer @Inject constructor(
return combine(filterCoordinator.tags, filterCoordinator.query) { tags, query -> return combine(filterCoordinator.tags, filterCoordinator.query) { tags, query ->
createChipsList( createChipsList(
source = filterCoordinator.mangaSource, source = filterCoordinator.mangaSource,
capabilities = filterCoordinator.capabilities,
property = tags, property = tags,
query = query, query = query,
limit = 8, limit = 8,
@@ -34,42 +36,45 @@ class FilterHeaderProducer @Inject constructor(
private suspend fun createChipsList( private suspend fun createChipsList(
source: MangaSource, source: MangaSource,
capabilities: MangaListFilterCapabilities,
property: FilterProperty<MangaTag>, property: FilterProperty<MangaTag>,
query: String?, query: String?,
limit: Int, limit: Int,
): List<ChipsView.ChipModel> { ): List<ChipsView.ChipModel> {
val selectedTags = property.selectedItems.toMutableSet() val result = ArrayDeque<ChipsView.ChipModel>(limit + 3)
var tags = if (selectedTags.isEmpty()) { if (query.isNullOrEmpty() || capabilities.isSearchWithFiltersSupported) {
searchRepository.getTagsSuggestion("", limit, source) val selectedTags = property.selectedItems.toMutableSet()
} else { var tags = if (selectedTags.isEmpty()) {
searchRepository.getTagsSuggestion(selectedTags).take(limit) searchRepository.getTagsSuggestion("", limit, source)
}
if (tags.size < limit) {
tags = tags + property.availableItems.take(limit - tags.size)
}
if (tags.isEmpty() && selectedTags.isEmpty()) {
return emptyList()
}
val result = ArrayDeque<ChipsView.ChipModel>(tags.size + selectedTags.size + 1)
for (tag in tags) {
val model = ChipsView.ChipModel(
title = tag.title,
isChecked = selectedTags.remove(tag),
data = tag,
)
if (model.isChecked) {
result.addFirst(model)
} else { } else {
result.addLast(model) searchRepository.getTagsSuggestion(selectedTags).take(limit)
}
if (tags.size < limit) {
tags = tags + property.availableItems.take(limit - tags.size)
}
if (tags.isEmpty() && selectedTags.isEmpty()) {
return emptyList()
}
for (tag in tags) {
val model = ChipsView.ChipModel(
title = tag.title,
isChecked = selectedTags.remove(tag),
data = tag,
)
if (model.isChecked) {
result.addFirst(model)
} else {
result.addLast(model)
}
}
for (tag in selectedTags) {
val model = ChipsView.ChipModel(
title = tag.title,
isChecked = true,
data = tag,
)
result.addFirst(model)
} }
}
for (tag in selectedTags) {
val model = ChipsView.ChipModel(
title = tag.title,
isChecked = true,
data = tag,
)
result.addFirst(model)
} }
if (!query.isNullOrEmpty()) { if (!query.isNullOrEmpty()) {
result.addFirst( result.addFirst(

View File

@@ -64,8 +64,8 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
this, this,
ReversibleActionObserver(viewBinding.recyclerView), ReversibleActionObserver(viewBinding.recyclerView),
) )
combine(viewModel.appliedFilter, viewModel.hasNewSources, ::Pair).observe(this) { combine(viewModel.appliedFilter, viewModel.hasNewSources, viewModel.contentTypes, ::Triple).observe(this) {
updateFilers(it.first, it.second) updateFilers(it.first, it.second, it.third)
} }
addMenuProvider(SourcesCatalogMenuProvider(this, viewModel, this)) addMenuProvider(SourcesCatalogMenuProvider(this, viewModel, this))
} }
@@ -111,8 +111,9 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
private fun updateFilers( private fun updateFilers(
appliedFilter: SourcesCatalogFilter, appliedFilter: SourcesCatalogFilter,
hasNewSources: Boolean, hasNewSources: Boolean,
contentTypes: List<ContentType>,
) { ) {
val chips = ArrayList<ChipModel>(ContentType.entries.size + 2) val chips = ArrayList<ChipModel>(contentTypes.size + 2)
chips += ChipModel( chips += ChipModel(
title = appliedFilter.locale?.toLocale().getDisplayName(this), title = appliedFilter.locale?.toLocale().getDisplayName(this),
icon = R.drawable.ic_language, icon = R.drawable.ic_language,
@@ -126,11 +127,8 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
data = true, data = true,
) )
} }
for (type in ContentType.entries) { contentTypes.mapTo(chips) { type ->
if (type == ContentType.HENTAI && viewModel.isNsfwDisabled) { ChipModel(
continue
}
chips += ChipModel(
title = getString(type.titleResId), title = getString(type.titleResId),
isChecked = type in appliedFilter.types, isChecked = type in appliedFilter.types,
data = type, data = type,

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.settings.sources.catalog package org.koitharu.kotatsu.settings.sources.catalog
import androidx.annotation.WorkerThread
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.room.invalidationTrackerFlow import androidx.room.invalidationTrackerFlow
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@@ -13,6 +14,7 @@ import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.TABLE_SOURCES import org.koitharu.kotatsu.core.db.TABLE_SOURCES
import org.koitharu.kotatsu.core.model.isNsfw
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.ui.util.ReversibleAction import org.koitharu.kotatsu.core.ui.util.ReversibleAction
@@ -23,7 +25,9 @@ import org.koitharu.kotatsu.explore.data.SourcesSortOrder
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import java.util.EnumMap
import java.util.EnumSet import java.util.EnumSet
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@@ -49,11 +53,11 @@ class SourcesCatalogViewModel @Inject constructor(
), ),
) )
val isNsfwDisabled = settings.isNsfwContentDisabled
val hasNewSources = repository.observeHasNewSources() val hasNewSources = repository.observeHasNewSources()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false) .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)
val contentTypes = MutableStateFlow<List<ContentType>>(emptyList())
val content: StateFlow<List<ListModel>> = combine( val content: StateFlow<List<ListModel>> = combine(
searchQuery, searchQuery,
appliedFilter, appliedFilter,
@@ -64,6 +68,9 @@ class SourcesCatalogViewModel @Inject constructor(
init { init {
repository.clearNewSourcesBadge() repository.clearNewSourcesBadge()
launchJob(Dispatchers.Default) {
contentTypes.value = getContentTypes(settings.isNsfwContentDisabled)
}
} }
fun performSearch(query: String?) { fun performSearch(query: String?) {
@@ -129,4 +136,16 @@ class SourcesCatalogViewModel @Inject constructor(
} }
} }
} }
@WorkerThread
private fun getContentTypes(isNsfwDisabled: Boolean): List<ContentType> {
val map = EnumMap<ContentType, Int>(ContentType::class.java)
for (e in MangaParserSource.entries) {
if (isNsfwDisabled && e.isNsfw()) {
continue
}
map[e.contentType] = map.getOrDefault(e.contentType, 0) + 1
}
return map.entries.sortedByDescending { it.value }.map { it.key }
}
} }

View File

@@ -727,4 +727,6 @@
<string name="years">Years</string> <string name="years">Years</string>
<string name="any">Any</string> <string name="any">Any</string>
<string name="filter_search_warning">This source does not support search with filters. Your filters have been cleared</string> <string name="filter_search_warning">This source does not support search with filters. Your filters have been cleared</string>
<string name="demographic_kodomo">Kodomo</string>
<string name="content_type_one_shot">One shot</string>
</resources> </resources>