From 861e43ff06bf45dbd9c21bcebc2cbb5ea818e167 Mon Sep 17 00:00:00 2001 From: "Cuong M. Tran" Date: Sun, 4 Feb 2024 17:06:20 +0700 Subject: [PATCH] Filter to show NSFW sources/extensions only in Browse tab --- .../browse/extension/ExtensionsScreenModel.kt | 29 +++++++++++---- .../ui/browse/extension/ExtensionsTab.kt | 13 +++++++ .../ui/browse/source/SourcesScreenModel.kt | 36 ++++++++++++++++++- .../tachiyomi/ui/browse/source/SourcesTab.kt | 11 ++++++ .../commonMain/resources/MR/base/strings.xml | 2 ++ 5 files changed, 84 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsScreenModel.kt index 2b6cec06a..1ac87a4a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsScreenModel.kt @@ -49,8 +49,11 @@ class ExtensionsScreenModel( ExtensionUiModel.Item(it, map[it.pkgName] ?: InstallStep.Idle) } } - val queryFilter: (String) -> ((Extension) -> Boolean) = { query -> + val queryFilter: (String/* KMK --> */, Boolean/* KMK <-- */) -> ((Extension) -> Boolean) = { query/* KMK --> */, nsfwOnly/* KMK <-- */ -> filter@{ extension -> + // KMK --> + if (nsfwOnly && !extension.isNsfw) return@filter false + // KMK <-- if (query.isEmpty()) return@filter true query.split(",").any { _input -> val input = _input.trim() @@ -79,26 +82,29 @@ class ExtensionsScreenModel( screenModelScope.launchIO { combine( state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), + // KMK --> + state.map { it.nsfwOnly }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), + // KMK <-- _currentDownloads, getExtensions.subscribe(), - ) { query, downloads, (_updates, _installed, _available, _untrusted) -> + ) { query/* KMK --> */, nsfwOnly/* KMK <-- */, downloads, (_updates, _installed, _available, _untrusted) -> val searchQuery = query ?: "" val itemsGroups: ItemGroups = mutableMapOf() - val updates = _updates.filter(queryFilter(searchQuery)).map(extensionMapper(downloads)) + val updates = _updates.filter(queryFilter(searchQuery/* KMK --> */, nsfwOnly/* KMK <-- */)).map(extensionMapper(downloads)) if (updates.isNotEmpty()) { itemsGroups[ExtensionUiModel.Header.Resource(MR.strings.ext_updates_pending)] = updates } - val installed = _installed.filter(queryFilter(searchQuery)).map(extensionMapper(downloads)) - val untrusted = _untrusted.filter(queryFilter(searchQuery)).map(extensionMapper(downloads)) + val installed = _installed.filter(queryFilter(searchQuery/* KMK --> */, nsfwOnly/* KMK <-- */)).map(extensionMapper(downloads)) + val untrusted = _untrusted.filter(queryFilter(searchQuery/* KMK --> */, nsfwOnly/* KMK <-- */)).map(extensionMapper(downloads)) if (installed.isNotEmpty() || untrusted.isNotEmpty()) { itemsGroups[ExtensionUiModel.Header.Resource(MR.strings.ext_installed)] = untrusted + installed } val languagesWithExtensions = _available - .filter(queryFilter(searchQuery)) + .filter(queryFilter(searchQuery/* KMK --> */, nsfwOnly/* KMK <-- */)) .groupBy { it.lang } .toSortedMap(LocaleHelper.comparator) .map { (lang, exts) -> @@ -198,6 +204,14 @@ class ExtensionsScreenModel( extensionManager.trust(extension) } + // KMK --> + fun toggleNsfwOnly() { + mutableState.update { + it.copy(nsfwOnly = !it.nsfwOnly) + } + } + // KMK <-- + @Immutable data class State( val isLoading: Boolean = true, @@ -206,6 +220,9 @@ class ExtensionsScreenModel( val updates: Int = 0, val installer: BasePreferences.ExtensionInstaller? = null, val searchQuery: String? = null, + // KMK --> + val nsfwOnly: Boolean = false, + // KMK <-- ) { val isEmpty = items.isEmpty() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsTab.kt index be6d243b8..3242031f8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsTab.kt @@ -1,5 +1,9 @@ package eu.kanade.tachiyomi.ui.browse.extension +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined._18UpRating +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -14,6 +18,7 @@ import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreen import eu.kanade.tachiyomi.ui.webview.WebViewScreen import kotlinx.collections.immutable.persistentListOf import tachiyomi.i18n.MR +import tachiyomi.i18n.sy.SYMR import tachiyomi.presentation.core.i18n.stringResource @Composable @@ -28,6 +33,14 @@ fun extensionsTab( badgeNumber = state.updates.takeIf { it > 0 }, searchEnabled = true, actions = persistentListOf( + // KMK --> + AppBar.Action( + title = stringResource(SYMR.strings.label_extension_nsfw), + icon = Icons.Outlined._18UpRating, + iconTint = if (state.nsfwOnly) MaterialTheme.colorScheme.error else LocalContentColor.current, + onClick = { extensionsScreenModel.toggleNsfwOnly() }, + ), + // KMK <-- AppBar.OverflowAction( title = stringResource(MR.strings.action_filter), onClick = { navigator.push(ExtensionFilterScreen()) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt index 980e4c71d..15a6b346b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt @@ -12,6 +12,7 @@ import eu.kanade.domain.source.interactor.SetSourceCategories import eu.kanade.domain.source.interactor.ToggleExcludeFromDataSaver import eu.kanade.domain.source.interactor.ToggleSource import eu.kanade.domain.source.interactor.ToggleSourcePin +import eu.kanade.domain.source.model.installedExtension import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences.DataSaver import eu.kanade.domain.ui.UiPreferences @@ -22,6 +23,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce @@ -66,6 +68,7 @@ class SourcesScreenModel( combine( // KMK --> state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), + state.map { it.nsfwOnly }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), // KMK <-- getEnabledSources.subscribe(), getSourceCategories.subscribe(), @@ -95,13 +98,17 @@ class SourcesScreenModel( private fun collectLatestSources( // KMK --> searchQuery: String?, + nsfwOnly: Boolean, @Suppress("LocalVariableName") _sources: List, // KMK <-- - categories: List, showLatest: Boolean, showPin: Boolean + categories: List, + showLatest: Boolean, + showPin: Boolean ) { // KMK --> val queryFilter: (String?) -> ((Source) -> Boolean) = { query -> filter@{ source -> + if (nsfwOnly && source.installedExtension != null && !source.installedExtension!!.isNsfw) return@filter false if (query.isNullOrBlank()) return@filter true query.split(",").any { val input = it.trim() @@ -192,6 +199,32 @@ class SourcesScreenModel( it.copy(searchQuery = query) } } + + fun toggleNsfwOnly() { + mutableState.update { + it.copy(nsfwOnly = !it.nsfwOnly) + } + } + + private fun combine( + flow: Flow, + flow2: Flow, + flow3: Flow, + flow4: Flow, + flow5: Flow, + flow6: Flow, + transform: suspend (T1, T2, T3, T4, T5, T6) -> R + ): Flow = combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*> -> + @Suppress("UNCHECKED_CAST") + transform( + args[0] as T1, + args[1] as T2, + args[2] as T3, + args[3] as T4, + args[4] as T5, + args[5] as T6, + ) + } // KMK <-- fun showSourceDialog(source: Source) { @@ -224,6 +257,7 @@ class SourcesScreenModel( // SY <-- // KMK --> val searchQuery: String? = null, + val nsfwOnly: Boolean = false, // KMK <-- ) { val isEmpty = items.isEmpty() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt index e1c99417b..92c9bd322 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt @@ -3,6 +3,9 @@ package eu.kanade.tachiyomi.ui.browse.source import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FilterList import androidx.compose.material.icons.outlined.TravelExplore +import androidx.compose.material.icons.outlined._18UpRating +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -50,6 +53,14 @@ fun Screen.sourcesTab( icon = Icons.Outlined.TravelExplore, onClick = { navigator.push(GlobalSearchScreen()) }, ), + // KMK --> + AppBar.Action( + title = stringResource(SYMR.strings.label_source_nsfw), + icon = Icons.Outlined._18UpRating, + iconTint = if (state.nsfwOnly) MaterialTheme.colorScheme.error else LocalContentColor.current, + onClick = { screenModel.toggleNsfwOnly() }, + ), + // KMK <-- AppBar.Action( title = stringResource(MR.strings.action_filter), icon = Icons.Outlined.FilterList, diff --git a/i18n-sy/src/commonMain/resources/MR/base/strings.xml b/i18n-sy/src/commonMain/resources/MR/base/strings.xml index cf086b52f..cb2a23775 100644 --- a/i18n-sy/src/commonMain/resources/MR/base/strings.xml +++ b/i18n-sy/src/commonMain/resources/MR/base/strings.xml @@ -350,6 +350,7 @@ Find in another source Exclude from data saver Stop excluding from data saver + Toggle NSFW only Searching source… @@ -391,6 +392,7 @@ Redundant This extension is redundant and will not be used inside this version of Tachiyomi. + Toggle NSFW only Select sources