From 080cdbb71dbffd40f18fcd656ebe151dc41e3327 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 20 Aug 2025 16:34:23 -0300 Subject: [PATCH] Do diacritics insensitive category search --- .../fdroid/ui/discover/DiscoverViewModel.kt | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/next/src/main/kotlin/org/fdroid/ui/discover/DiscoverViewModel.kt b/next/src/main/kotlin/org/fdroid/ui/discover/DiscoverViewModel.kt index d5b8cf809..f76273fb9 100644 --- a/next/src/main/kotlin/org/fdroid/ui/discover/DiscoverViewModel.kt +++ b/next/src/main/kotlin/org/fdroid/ui/discover/DiscoverViewModel.kt @@ -27,9 +27,11 @@ import org.fdroid.ui.lists.AppListItem import org.fdroid.updates.UpdatesManager import org.fdroid.utils.IoDispatcher import java.text.Collator +import java.text.Normalizer +import java.text.Normalizer.Form.NFKD import java.util.Locale import javax.inject.Inject -import kotlin.time.measureTime +import kotlin.time.measureTimedValue @HiltViewModel class DiscoverViewModel @Inject constructor( @@ -43,6 +45,8 @@ class DiscoverViewModel @Inject constructor( private val log = KotlinLogging.logger { } private val scope = CoroutineScope(viewModelScope.coroutineContext + AndroidUiDispatcher.Main) + private val collator = Collator.getInstance(Locale.getDefault()) + private val normalizerRegex = "\\p{M}".toRegex() val numUpdates = updatesManager.numUpdates val apps = db.getAppDao().getAppOverviewItems().asFlow().map { list -> @@ -60,7 +64,6 @@ class DiscoverViewModel @Inject constructor( } } val categories = db.getRepositoryDao().getLiveCategories().asFlow().map { categories -> - val collator = Collator.getInstance(Locale.getDefault()) categories.map { category -> CategoryItem( id = category.id, @@ -96,8 +99,8 @@ class DiscoverViewModel @Inject constructor( } } log.info { "Searching for: $query" } - val duration = measureTime { - val apps = try { + val timedApps = measureTimedValue { + try { db.getAppDao().getAppSearchItems(query).sortedDescending().mapNotNull { val repository = repoManager.getRepository(it.repoId) ?: return@mapNotNull null AppListItem( @@ -115,19 +118,30 @@ class DiscoverViewModel @Inject constructor( log.error(e) { "Error searching for $query: " } emptyList() } - val categories = this@DiscoverViewModel.categories.first().filter { - // TODO handle diacritics as well - it.name.contains(sanitized, ignoreCase = true) - } - searchResults.value = SearchResults(apps, categories) } + val timedCategories = measureTimedValue { + this@DiscoverViewModel.categories.first().filter { + // normalization removed diacritics, so searches without them work + it.name.normalize().contains(sanitized.normalize(), ignoreCase = true) + } + } + searchResults.value = SearchResults(timedApps.value, timedCategories.value) log.debug { val numResults = searchResults.value?.apps?.size ?: 0 - "Search for $query had $numResults results and took $duration" + "Search for $query had $numResults results " + + "and took ${timedApps.duration} and ${timedCategories.duration}" } } fun onSearchCleared() { searchResults.value = null } + + /** + * Normalizes the string by removing any diacritics that may appear. + */ + private fun String.normalize(): String { + if (Normalizer.isNormalized(this, NFKD)) return this + return Normalizer.normalize(this, NFKD).replace(normalizerRegex, "") + } }