Skip to content

Commit 76dcfae

Browse files
authored
fix(YouTube - Settings): Resolve settings search crash when searching for specific words (#6231)
1 parent e4f5234 commit 76dcfae

File tree

5 files changed

+74
-17
lines changed

5 files changed

+74
-17
lines changed

extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import androidx.annotation.Nullable;
4646

4747
import java.text.Bidi;
48+
import java.text.Collator;
49+
import java.text.Normalizer;
4850
import java.util.ArrayList;
4951
import java.util.Collections;
5052
import java.util.List;
@@ -79,6 +81,15 @@ public class Utils {
7981
@Nullable
8082
private static Boolean isDarkModeEnabled;
8183

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+
8293
private Utils() {
8394
} // utility class
8495

@@ -976,30 +987,60 @@ static Sort fromKey(@Nullable String key, Sort defaultSort) {
976987
}
977988
}
978989

979-
private static final Pattern punctuationPattern = Pattern.compile("\\p{P}+");
980-
981990
/**
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.
983992
*/
984993
public static String removePunctuationToLowercase(@Nullable CharSequence original) {
985994
if (original == null) return "";
986-
return punctuationPattern.matcher(original).replaceAll("")
995+
return PUNCTUATION_PATTERN.matcher(original).replaceAll("")
987996
.toLowerCase(BaseSettings.REVANCED_LANGUAGE.get().getLocale());
988997
}
989998

990999
/**
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.
9921026
* <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.
9941029
* <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
9971035
*/
9981036
@SuppressWarnings("deprecation")
9991037
public static void sortPreferenceGroups(PreferenceGroup group) {
10001038
Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED);
10011039
List<Pair<String, Preference>> preferences = new ArrayList<>();
10021040

1041+
// Get cached Collator for locale-aware string comparison.
1042+
Collator collator = getCollator();
1043+
10031044
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
10041045
Preference preference = group.getPreference(i);
10051046

@@ -1030,10 +1071,11 @@ public static void sortPreferenceGroups(PreferenceGroup group) {
10301071
preferences.add(new Pair<>(sortValue, preference));
10311072
}
10321073

1033-
//noinspection ComparatorCombinators
1074+
// Sort the list using locale-specific collation rules.
10341075
Collections.sort(preferences, (pair1, pair2)
1035-
-> pair1.first.compareTo(pair2.first));
1076+
-> collator.compare(pair1.first, pair2.first));
10361077

1078+
// Reassign order values to reflect the new sorted sequence
10371079
int index = 0;
10381080
for (Pair<String, Preference> pair : preferences) {
10391081
int order = index++;

extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,10 +392,13 @@ public boolean isAvailable() {
392392

393393
/**
394394
* Get the parent Settings that this setting depends on.
395-
* @return List of parent Settings (e.g., BooleanSetting or EnumSetting), or empty list if no dependencies exist.
395+
* @return List of parent Settings, or empty list if no dependencies exist.
396+
* Defensive: handles null availability or missing getParentSettings() override.
396397
*/
397398
public List<Setting<?>> getParentSettings() {
398-
return availability == null ? Collections.emptyList() : availability.getParentSettings();
399+
return availability == null
400+
? Collections.emptyList()
401+
: Objects.requireNonNullElse(availability.getParentSettings(), Collections.emptyList());
399402
}
400403

401404
/**

extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/BaseSearchResultItem.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ private static int getResourceIdentifier(String name) {
7575

7676
// Shared method for highlighting text with search query.
7777
protected static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern) {
78-
if (TextUtils.isEmpty(text)) return text;
78+
if (TextUtils.isEmpty(text) || queryPattern == null) return text;
7979

8080
final int adjustedColor = Utils.adjustColorBrightness(
8181
Utils.getAppBackgroundColor(), 0.95f, 1.20f);
@@ -84,7 +84,10 @@ protected static CharSequence highlightSearchQuery(CharSequence text, Pattern qu
8484

8585
Matcher matcher = queryPattern.matcher(text);
8686
while (matcher.find()) {
87-
spannable.setSpan(highlightSpan, matcher.start(), matcher.end(),
87+
int start = matcher.start();
88+
int end = matcher.end();
89+
if (start == end) continue; // Skip zero matches.
90+
spannable.setSpan(highlightSpan, start, end,
8891
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
8992
}
9093

@@ -224,10 +227,14 @@ private String buildSearchableText(Preference pref) {
224227
return searchBuilder.toString();
225228
}
226229

230+
/**
231+
* Appends normalized searchable text to the builder.
232+
* Uses full Unicode normalization for accurate search across all languages.
233+
*/
227234
private void appendText(StringBuilder builder, CharSequence text) {
228235
if (!TextUtils.isEmpty(text)) {
229236
if (builder.length() > 0) builder.append(" ");
230-
builder.append(Utils.removePunctuationToLowercase(text));
237+
builder.append(Utils.normalizeTextToLowercase(text));
231238
}
232239
}
233240

@@ -272,7 +279,7 @@ public CharSequence getCurrentEffectiveSummary() {
272279
*/
273280
@Override
274281
boolean matchesQuery(String query) {
275-
return searchableText.contains(Utils.removePunctuationToLowercase(query));
282+
return searchableText.contains(Utils.normalizeTextToLowercase(query));
276283
}
277284

278285
/**

extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/search/BaseSearchViewController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ protected void filterAndShowResults(String query) {
450450

451451
filteredSearchItems.clear();
452452

453-
String queryLower = Utils.removePunctuationToLowercase(query);
453+
String queryLower = Utils.normalizeTextToLowercase(query);
454454
Pattern queryPattern = Pattern.compile(Pattern.quote(queryLower), Pattern.CASE_INSENSITIVE);
455455

456456
// Clear highlighting only for items that were previously visible.

extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/spoof/SpoofVideoStreamsPatch.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ public boolean isAvailable() {
2222
return Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.isAvailable()
2323
&& Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ANDROID_VR_1_43_32;
2424
}
25+
26+
@Override
27+
public List<Setting<?>> getParentSettings() {
28+
return List.of(Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE);
29+
}
2530
}
2631

2732
/**

0 commit comments

Comments
 (0)