fix #95: unicode normalization for search

This commit is contained in:
Josia Pietsch 2025-01-10 00:21:57 +01:00
parent ce5fade39a
commit c6d0477e3a
Signed by: jrpie
GPG key ID: E70B571D66986A2D
2 changed files with 22 additions and 8 deletions

View file

@ -1,6 +1,9 @@
package de.jrpie.android.launcher.apps package de.jrpie.android.launcher.apps
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.icu.text.Normalizer2
import android.os.Build
import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Action
import de.jrpie.android.launcher.actions.AppAction import de.jrpie.android.launcher.actions.AppAction
import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.Gesture
@ -10,10 +13,12 @@ import kotlin.text.Regex.Companion.escape
class AppFilter( class AppFilter(
var context: Context, var context: Context,
var search: String, var query: String,
var favoritesVisibility: AppSetVisibility = AppSetVisibility.VISIBLE, var favoritesVisibility: AppSetVisibility = AppSetVisibility.VISIBLE,
var hiddenVisibility: AppSetVisibility = AppSetVisibility.HIDDEN, var hiddenVisibility: AppSetVisibility = AppSetVisibility.HIDDEN,
) { ) {
@SuppressLint("NewApi")
operator fun invoke(apps: List<DetailedAppInfo>): List<DetailedAppInfo> { operator fun invoke(apps: List<DetailedAppInfo>): List<DetailedAppInfo> {
var apps = var apps =
apps.sortedBy { app -> app.getCustomLabel(context).toString().lowercase(Locale.ROOT) } apps.sortedBy { app -> app.getCustomLabel(context).toString().lowercase(Locale.ROOT) }
@ -35,7 +40,7 @@ class AppFilter(
} }
// normalize text for search // normalize text for search
val allowedSpecialCharacters = search val allowedSpecialCharacters = unicodeNormalize(query)
.lowercase(Locale.ROOT) .lowercase(Locale.ROOT)
.toCharArray() .toCharArray()
.distinct() .distinct()
@ -45,20 +50,21 @@ class AppFilter(
val disallowedCharsRegex = "[^\\p{L}$allowedSpecialCharacters]".toRegex() val disallowedCharsRegex = "[^\\p{L}$allowedSpecialCharacters]".toRegex()
fun normalize(text: String): String { fun normalize(text: String): String {
return text.lowercase(Locale.ROOT).replace(disallowedCharsRegex, "") return unicodeNormalize(text).replace(disallowedCharsRegex, "")
} }
if (search.isEmpty()) {
if (query.isEmpty()) {
return apps return apps
} else { } else {
val r: MutableList<DetailedAppInfo> = ArrayList() val r: MutableList<DetailedAppInfo> = ArrayList()
val appsSecondary: MutableList<DetailedAppInfo> = ArrayList() val appsSecondary: MutableList<DetailedAppInfo> = ArrayList()
val normalizedText: String = normalize(search) val normalizedQuery: String = normalize(query)
for (item in apps) { for (item in apps) {
val itemLabel: String = normalize(item.getCustomLabel(context).toString()) val itemLabel: String = normalize(item.getCustomLabel(context).toString())
if (itemLabel.startsWith(normalizedText)) { if (itemLabel.startsWith(normalizedQuery)) {
r.add(item) r.add(item)
} else if (itemLabel.contains(normalizedText)) { } else if (itemLabel.contains(normalizedQuery)) {
appsSecondary.add(item) appsSecondary.add(item)
} }
} }
@ -77,5 +83,13 @@ class AppFilter(
EXCLUSIVE({ set, appInfo -> set.contains(appInfo.app) }), EXCLUSIVE({ set, appInfo -> set.contains(appInfo.app) }),
; ;
} }
private fun unicodeNormalize(s: String): String {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val normalizer = Normalizer2.getNFKDInstance()
return normalizer.normalize(s.lowercase(Locale.ROOT))
}
return s.lowercase(Locale.ROOT)
}
} }
} }

View file

@ -226,7 +226,7 @@ class AppsRecyclerAdapter(
* The function [setSearchString] is used to search elements within this [RecyclerView]. * The function [setSearchString] is used to search elements within this [RecyclerView].
*/ */
fun setSearchString(search: String) { fun setSearchString(search: String) {
appFilter.search = search appFilter.query = search
updateAppsList(true) updateAppsList(true)
} }