Optimize string normalization
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user