From 3a7898eeac9802e70458da913c737e9b07f23bc0 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Sat, 27 Dec 2025 01:34:00 +0600 Subject: [PATCH] Cleanup extension screen search query predicate (cherry picked from commit e059190fabe3cbe8498fc3cec7e39b0350f3c289) --- .../browse/extension/ExtensionsScreenModel.kt | 161 +++++++++--------- 1 file changed, 81 insertions(+), 80 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 b3ef0a481..d3768217c 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 @@ -59,99 +59,70 @@ class ExtensionsScreenModel( ) } } - val queryFilter: (String) -> ((Extension) -> Boolean) = { query -> - filter@{ extension -> - if (query.isEmpty()) return@filter true - query.split(",").any { _input -> - val input = _input.trim() - if (input.isEmpty()) return@any false - when (extension) { - is Extension.Available -> { - extension.sources.any { - it.name.contains(input, ignoreCase = true) || - it.baseUrl.contains(input, ignoreCase = true) || - it.id == input.toLongOrNull() - } || - extension.name.contains(input, ignoreCase = true) - } - is Extension.Installed -> { - extension.sources.any { - it.name.contains(input, ignoreCase = true) || - it.id == input.toLongOrNull() || - if (it is HttpSource) { - it.baseUrl.contains(input, ignoreCase = true) - } else { - false - } - } || - extension.name.contains(input, ignoreCase = true) - } - is Extension.Untrusted -> extension.name.contains(input, ignoreCase = true) - } - } - } - } screenModelScope.launchIO { combine( - state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), + state.map { it.searchQuery } + .distinctUntilChanged() + .debounce(SEARCH_DEBOUNCE_MILLIS) + .map { searchQueryPredicate(it ?: "") }, // KMK --> - state.map { it.nsfwOnly }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), + state.map { it.nsfwOnly } + .distinctUntilChanged() + .debounce(SEARCH_DEBOUNCE_MILLIS), // KMK <-- currentDownloads, getExtensions.subscribe(), - ) { query, nsfwOnly, downloads, (_updates, _installed, _available, _untrusted) -> - val searchQuery = query ?: "" - - val itemsGroups: ItemGroups = mutableMapOf() - - val updates = _updates.filter(queryFilter(searchQuery)).map(extensionMapper(downloads)) - // KMK --> - .filter { !nsfwOnly || it.extension.isNsfw } - // KMK <-- - if (updates.isNotEmpty()) { - itemsGroups[ExtensionUiModel.Header.Resource(MR.strings.ext_updates_pending)] = updates - } - - val installed = _installed.filter(queryFilter(searchQuery)).map(extensionMapper(downloads)) - // KMK --> - .filter { !nsfwOnly || it.extension.isNsfw } - // KMK <-- - val untrusted = _untrusted.filter(queryFilter(searchQuery)).map(extensionMapper(downloads)) - // KMK --> - .filter { !nsfwOnly || it.extension.isNsfw } - // KMK <-- - if (installed.isNotEmpty() || untrusted.isNotEmpty()) { - itemsGroups[ExtensionUiModel.Header.Resource(MR.strings.ext_installed)] = installed + untrusted - } - - val languagesWithExtensions = _available - .filter(queryFilter(searchQuery)) - // KMK --> - .filter { !nsfwOnly || it.isNsfw } + ) { predicate, nsfwOnly, downloads, (_updates, _installed, _available, _untrusted) -> + buildMap { + val updates = _updates.filter(predicate).map(extensionMapper(downloads)) + // KMK --> + .filter { !nsfwOnly || it.extension.isNsfw } // KMK <-- - .groupBy { it.lang } - .toSortedMap(LocaleHelper.comparator) - .map { (lang, exts) -> - ExtensionUiModel.Header.Text(LocaleHelper.getSourceDisplayName(lang, context)) to - exts.map(extensionMapper(downloads)) + if (updates.isNotEmpty()) { + put(ExtensionUiModel.Header.Resource(MR.strings.ext_updates_pending), updates) } - if (languagesWithExtensions.isNotEmpty()) { - itemsGroups.putAll(languagesWithExtensions) - } - // KMK --> - if (_available.isEmpty()) { - itemsGroups[ExtensionUiModel.Header.Resource(KMR.strings.extensions_page_more)] = emptyList() - } - // KMK <-- - itemsGroups + val installed = _installed.filter(predicate).map(extensionMapper(downloads)) + // KMK --> + .filter { !nsfwOnly || it.extension.isNsfw } + // KMK <-- + val untrusted = _untrusted.filter(predicate).map(extensionMapper(downloads)) + // KMK --> + .filter { !nsfwOnly || it.extension.isNsfw } + // KMK <-- + if (installed.isNotEmpty() || untrusted.isNotEmpty()) { + put(ExtensionUiModel.Header.Resource(MR.strings.ext_installed), installed + untrusted) + } + + val languagesWithExtensions = _available + .filter(predicate) + // KMK --> + .filter { !nsfwOnly || it.isNsfw } + // KMK <-- + .groupBy { it.lang } + .toSortedMap(LocaleHelper.comparator) + .map { (lang, exts) -> + ExtensionUiModel.Header.Text(LocaleHelper.getSourceDisplayName(lang, context)) to + exts.map(extensionMapper(downloads)) + } + if (languagesWithExtensions.isNotEmpty()) { + putAll(languagesWithExtensions) + } + + // KMK --> + // Show "More..." header if no available extensions + if (_available.isEmpty()) { + put(ExtensionUiModel.Header.Resource(KMR.strings.extensions_page_more), emptyList()) + } + // KMK <-- + } } - .collectLatest { + .collectLatest { items -> mutableState.update { state -> state.copy( isLoading = false, - items = it, + items = items, ) } } @@ -168,6 +139,36 @@ class ExtensionsScreenModel( .launchIn(screenModelScope) } + fun searchQueryPredicate(query: String): (Extension) -> Boolean { + val subqueries = query.split(",") + .map { it.trim() } + .filterNot { it.isBlank() } + + if (subqueries.isEmpty()) return { true } + + return { extension -> + subqueries.any { subquery -> + if (extension.name.contains(subquery, ignoreCase = true)) return@any true + + when (extension) { + is Extension.Installed -> extension.sources.any { source -> + source.name.contains(subquery, ignoreCase = true) || + (source as? HttpSource)?.baseUrl?.contains(subquery, ignoreCase = true) == true || + source.id == subquery.toLongOrNull() + } + + is Extension.Available -> extension.sources.any { + it.name.contains(subquery, ignoreCase = true) || + it.baseUrl.contains(subquery, ignoreCase = true) || + it.id == subquery.toLongOrNull() + } + + else -> false + } + } + } + } + fun search(query: String?) { mutableState.update { it.copy(searchQuery = query) @@ -277,7 +278,7 @@ class ExtensionsScreenModel( } } -typealias ItemGroups = MutableMap> +typealias ItemGroups = Map> object ExtensionUiModel { sealed interface Header {