|
45 | 45 | import androidx.annotation.Nullable; |
46 | 46 |
|
47 | 47 | import java.text.Bidi; |
| 48 | +import java.text.Collator; |
| 49 | +import java.text.Normalizer; |
48 | 50 | import java.util.ArrayList; |
49 | 51 | import java.util.Collections; |
50 | 52 | import java.util.List; |
@@ -79,6 +81,15 @@ public class Utils { |
79 | 81 | @Nullable |
80 | 82 | private static Boolean isDarkModeEnabled; |
81 | 83 |
|
| 84 | + // Cached Collator instance with its locale. |
| 85 | + @Nullable |
| 86 | + private static Locale cachedCollatorLocale; |
| 87 | + @Nullable |
| 88 | + private static Collator cachedCollator; |
| 89 | + |
| 90 | + private static final Pattern PUNCTUATION_PATTERN = Pattern.compile("\\p{P}+"); |
| 91 | + private static final Pattern DIACRITICS_PATTERN = Pattern.compile("\\p{M}"); |
| 92 | + |
82 | 93 | private Utils() { |
83 | 94 | } // utility class |
84 | 95 |
|
@@ -976,30 +987,60 @@ static Sort fromKey(@Nullable String key, Sort defaultSort) { |
976 | 987 | } |
977 | 988 | } |
978 | 989 |
|
979 | | - private static final Pattern punctuationPattern = Pattern.compile("\\p{P}+"); |
980 | | - |
981 | 990 | /** |
982 | | - * Strips all punctuation and converts to lower case. A null parameter returns an empty string. |
| 991 | + * Removes punctuation and converts text to lowercase. Returns an empty string if input is null. |
983 | 992 | */ |
984 | 993 | public static String removePunctuationToLowercase(@Nullable CharSequence original) { |
985 | 994 | if (original == null) return ""; |
986 | | - return punctuationPattern.matcher(original).replaceAll("") |
| 995 | + return PUNCTUATION_PATTERN.matcher(original).replaceAll("") |
987 | 996 | .toLowerCase(BaseSettings.REVANCED_LANGUAGE.get().getLocale()); |
988 | 997 | } |
989 | 998 |
|
990 | 999 | /** |
991 | | - * Sort a PreferenceGroup and all it's sub groups by title or key. |
| 1000 | + * Normalizes text for search: applies NFD, removes diacritics, and lowercases (locale-neutral). |
| 1001 | + * Returns an empty string if input is null. |
| 1002 | + */ |
| 1003 | + public static String normalizeTextToLowercase(@Nullable CharSequence original) { |
| 1004 | + if (original == null) return ""; |
| 1005 | + return DIACRITICS_PATTERN.matcher(Normalizer.normalize(original, Normalizer.Form.NFD)) |
| 1006 | + .replaceAll("").toLowerCase(Locale.ROOT); |
| 1007 | + } |
| 1008 | + |
| 1009 | + /** |
| 1010 | + * Returns a cached Collator for the current locale, or creates a new one if locale changed. |
| 1011 | + */ |
| 1012 | + private static Collator getCollator() { |
| 1013 | + Locale currentLocale = BaseSettings.REVANCED_LANGUAGE.get().getLocale(); |
| 1014 | + |
| 1015 | + if (cachedCollator == null || !currentLocale.equals(cachedCollatorLocale)) { |
| 1016 | + cachedCollatorLocale = currentLocale; |
| 1017 | + cachedCollator = Collator.getInstance(currentLocale); |
| 1018 | + cachedCollator.setStrength(Collator.SECONDARY); // Case-insensitive, diacritic-insensitive. |
| 1019 | + } |
| 1020 | + |
| 1021 | + return cachedCollator; |
| 1022 | + } |
| 1023 | + |
| 1024 | + /** |
| 1025 | + * Sorts a {@link PreferenceGroup} and all nested subgroups by title or key. |
992 | 1026 | * <p> |
993 | | - * Sort order is determined by the preferences key {@link Sort} suffix. |
| 1027 | + * The sort order is controlled by the {@link Sort} suffix present in the preference key. |
| 1028 | + * Preferences without a key or without a {@link Sort} suffix remain in their original order. |
994 | 1029 | * <p> |
995 | | - * If a preference has no key or no {@link Sort} suffix, |
996 | | - * then the preferences are left unsorted. |
| 1030 | + * Sorting is performed using {@link Collator} with the current user locale, |
| 1031 | + * ensuring correct alphabetical ordering for all supported languages |
| 1032 | + * (e.g., Ukrainian "і", German "ß", French accented characters, etc.). |
| 1033 | + * |
| 1034 | + * @param group the {@link PreferenceGroup} to sort |
997 | 1035 | */ |
998 | 1036 | @SuppressWarnings("deprecation") |
999 | 1037 | public static void sortPreferenceGroups(PreferenceGroup group) { |
1000 | 1038 | Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED); |
1001 | 1039 | List<Pair<String, Preference>> preferences = new ArrayList<>(); |
1002 | 1040 |
|
| 1041 | + // Get cached Collator for locale-aware string comparison. |
| 1042 | + Collator collator = getCollator(); |
| 1043 | + |
1003 | 1044 | for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) { |
1004 | 1045 | Preference preference = group.getPreference(i); |
1005 | 1046 |
|
@@ -1030,10 +1071,11 @@ public static void sortPreferenceGroups(PreferenceGroup group) { |
1030 | 1071 | preferences.add(new Pair<>(sortValue, preference)); |
1031 | 1072 | } |
1032 | 1073 |
|
1033 | | - //noinspection ComparatorCombinators |
| 1074 | + // Sort the list using locale-specific collation rules. |
1034 | 1075 | Collections.sort(preferences, (pair1, pair2) |
1035 | | - -> pair1.first.compareTo(pair2.first)); |
| 1076 | + -> collator.compare(pair1.first, pair2.first)); |
1036 | 1077 |
|
| 1078 | + // Reassign order values to reflect the new sorted sequence |
1037 | 1079 | int index = 0; |
1038 | 1080 | for (Pair<String, Preference> pair : preferences) { |
1039 | 1081 | int order = index++; |
|
0 commit comments