Optimize string normalization

This commit is contained in:
MM20
2025-12-24 13:51:18 +01:00
parent 142b9429e1
commit 49d09c01c7
6 changed files with 50 additions and 19 deletions

View File

@@ -1,5 +1,11 @@
package de.mm20.launcher2.search
interface StringNormalizer {
/**
* A unique identifier for the normalization algorithm. Two normalizers that share the same ID must
* return the same normalized string for the same input.
*/
val id: String
fun normalize(input: String): String
}

View File

@@ -240,21 +240,30 @@ internal class AppRepositoryImpl(
override fun search(query: String, allowNetwork: Boolean): Flow<ImmutableList<LauncherApp>> {
val normalizedQuery = stringNormalizer.normalize(query)
return installedApps.map { apps ->
withContext(Dispatchers.Default) {
val normalizerId = stringNormalizer.id
val appResults = mutableListOf<LauncherApp>()
if (query.isEmpty()) {
appResults.addAll(apps)
} else {
appResults.addAll(apps.mapNotNull {
appResults.addAll(apps.mapNotNull { app ->
val cachedLabel = app.cachedNormalizerResult
val score = ResultScore.from(
query = normalizedQuery,
primaryFields = listOf(
stringNormalizer.normalize(it.label)
if (cachedLabel?.first == normalizerId) {
cachedLabel.second
} else {
stringNormalizer.normalize(app.label).also {
app.cachedNormalizerResult = normalizerId to it
}
}
),
)
if (score.score < 0.8f) return@mapNotNull null
it.copy(
app.copy(
score = score
)
})

View File

@@ -47,6 +47,13 @@ internal data class LauncherApp(
override val label: String = launcherActivityInfo.label.toString()
/**
* Cached result of the normalized label.
* First string is the normalizer ID
* Second string is the normalized label
*/
internal var cachedNormalizerResult: Pair<String, String>? = null
constructor(
context: Context,

View File

@@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.withContext
@@ -169,7 +170,7 @@ internal class AppShortcutRepositoryImpl(
} else {
persistentListOf()
}
}
}.flowOn(Dispatchers.Default)
}
private val shortcutChangeEmitter = callbackFlow {

View File

@@ -8,6 +8,9 @@ import java.util.Locale
* Pre Android 10 StringNormalizer. Only strips accents from latin characters
*/
internal class CompatStringNormalizer: StringNormalizer {
override val id: String = "null"
override fun normalize(input: String): String {
return StringUtils.stripAccents(input.lowercase(Locale.getDefault()))
.replace("æ", "ae")

View File

@@ -22,21 +22,26 @@ internal class IcuStringNormalizer(
localeSettings: LocaleSettings,
) : StringNormalizer {
override val id: String
get() = transliteratorId.value ?: "null"
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val transliterator = localeSettings.transliterator
private val transliteratorId = localeSettings.transliterator
.map {
try {
getTransliterator(it)
} catch (e: IllegalArgumentException) {
CrashReporter.logException(e)
null
}
getTransliteratorId(it)
}
.stateIn(scope, SharingStarted.Eagerly, null)
override fun normalize(input: String): String {
val transliterator = transliterator.value
val id = transliteratorId.value
val transliterator = try {
Transliterator.getInstance(id)
} catch (e: IllegalArgumentException) {
CrashReporter.logException(e)
null
}
if (transliterator == null) {
return StringUtils.stripAccents(input.lowercase(Locale.getDefault()))
@@ -48,17 +53,17 @@ internal class IcuStringNormalizer(
return transliterator.transliterate(input).lowercase()
}
private fun getTransliterator(preferenceValue: String?): Transliterator {
val id = preferenceValue ?: return Transliterator.getInstance(DisabledTransliteratorId)
private fun getTransliteratorId(preferenceValue: String?): String {
val id = preferenceValue ?: return DisabledTransliteratorId
if (id.isNotBlank()) {
return Transliterator.getInstance("$id;$BaseTransliteratorId")
return "$id;$BaseTransliteratorId"
}
val locales = context.resources.configuration.locales
if (locales.isEmpty) {
Transliterator.getInstance(BaseTransliteratorId)
return BaseTransliteratorId
}
val scripts = mutableSetOf<String>()
@@ -79,7 +84,7 @@ internal class IcuStringNormalizer(
val id = availableIds.find { it.startsWith(filter) }
if (id != null) {
return Transliterator.getInstance("$id;$BaseTransliteratorId")
return "$id;$BaseTransliteratorId"
}
languages.add(lng)
@@ -91,12 +96,12 @@ internal class IcuStringNormalizer(
val id = availableIds.find { it.startsWith(filter) }
if (id != null) {
return Transliterator.getInstance("$id;$BaseTransliteratorId")
return "$id;$BaseTransliteratorId"
}
scripts.add(ulocale.script)
}
}
return Transliterator.getInstance(BaseTransliteratorId)
return BaseTransliteratorId
}
companion object {