Fix sources catalog content types
This commit is contained in:
@@ -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'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user