Cleanup extension screen search query predicate

(cherry picked from commit e059190fabe3cbe8498fc3cec7e39b0350f3c289)
This commit is contained in:
AntsyLich 2025-12-27 01:34:00 +06:00 committed by Cuong-Tran
parent 9402b96162
commit 3a7898eeac

View file

@ -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 { screenModelScope.launchIO {
combine( combine(
state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), state.map { it.searchQuery }
.distinctUntilChanged()
.debounce(SEARCH_DEBOUNCE_MILLIS)
.map { searchQueryPredicate(it ?: "") },
// KMK --> // KMK -->
state.map { it.nsfwOnly }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), state.map { it.nsfwOnly }
.distinctUntilChanged()
.debounce(SEARCH_DEBOUNCE_MILLIS),
// KMK <-- // KMK <--
currentDownloads, currentDownloads,
getExtensions.subscribe(), getExtensions.subscribe(),
) { query, nsfwOnly, downloads, (_updates, _installed, _available, _untrusted) -> ) { predicate, nsfwOnly, downloads, (_updates, _installed, _available, _untrusted) ->
val searchQuery = query ?: "" buildMap {
val updates = _updates.filter(predicate).map(extensionMapper(downloads))
val itemsGroups: ItemGroups = mutableMapOf() // KMK -->
.filter { !nsfwOnly || it.extension.isNsfw }
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 }
// KMK <-- // KMK <--
.groupBy { it.lang } if (updates.isNotEmpty()) {
.toSortedMap(LocaleHelper.comparator) put(ExtensionUiModel.Header.Resource(MR.strings.ext_updates_pending), updates)
.map { (lang, exts) ->
ExtensionUiModel.Header.Text(LocaleHelper.getSourceDisplayName(lang, context)) to
exts.map(extensionMapper(downloads))
} }
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 -> mutableState.update { state ->
state.copy( state.copy(
isLoading = false, isLoading = false,
items = it, items = items,
) )
} }
} }
@ -168,6 +139,36 @@ class ExtensionsScreenModel(
.launchIn(screenModelScope) .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?) { fun search(query: String?) {
mutableState.update { mutableState.update {
it.copy(searchQuery = query) it.copy(searchQuery = query)
@ -277,7 +278,7 @@ class ExtensionsScreenModel(
} }
} }
typealias ItemGroups = MutableMap<ExtensionUiModel.Header, List<ExtensionUiModel.Item>> typealias ItemGroups = Map<ExtensionUiModel.Header, List<ExtensionUiModel.Item>>
object ExtensionUiModel { object ExtensionUiModel {
sealed interface Header { sealed interface Header {