mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-07 01:51:27 +01:00
fix(YouTube - Settings): Resolve settings search crash when searching for specific words (#6231)
This commit is contained in:
@@ -45,6 +45,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.text.Bidi;
|
import java.text.Bidi;
|
||||||
|
import java.text.Collator;
|
||||||
|
import java.text.Normalizer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -79,6 +81,15 @@ public class Utils {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private static Boolean isDarkModeEnabled;
|
private static Boolean isDarkModeEnabled;
|
||||||
|
|
||||||
|
// Cached Collator instance with its locale.
|
||||||
|
@Nullable
|
||||||
|
private static Locale cachedCollatorLocale;
|
||||||
|
@Nullable
|
||||||
|
private static Collator cachedCollator;
|
||||||
|
|
||||||
|
private static final Pattern PUNCTUATION_PATTERN = Pattern.compile("\\p{P}+");
|
||||||
|
private static final Pattern DIACRITICS_PATTERN = Pattern.compile("\\p{M}");
|
||||||
|
|
||||||
private Utils() {
|
private Utils() {
|
||||||
} // utility class
|
} // utility class
|
||||||
|
|
||||||
@@ -976,30 +987,60 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Pattern punctuationPattern = Pattern.compile("\\p{P}+");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strips all punctuation and converts to lower case. A null parameter returns an empty string.
|
* Removes punctuation and converts text to lowercase. Returns an empty string if input is null.
|
||||||
*/
|
*/
|
||||||
public static String removePunctuationToLowercase(@Nullable CharSequence original) {
|
public static String removePunctuationToLowercase(@Nullable CharSequence original) {
|
||||||
if (original == null) return "";
|
if (original == null) return "";
|
||||||
return punctuationPattern.matcher(original).replaceAll("")
|
return PUNCTUATION_PATTERN.matcher(original).replaceAll("")
|
||||||
.toLowerCase(BaseSettings.REVANCED_LANGUAGE.get().getLocale());
|
.toLowerCase(BaseSettings.REVANCED_LANGUAGE.get().getLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort a PreferenceGroup and all it's sub groups by title or key.
|
* Normalizes text for search: applies NFD, removes diacritics, and lowercases (locale-neutral).
|
||||||
|
* Returns an empty string if input is null.
|
||||||
|
*/
|
||||||
|
public static String normalizeTextToLowercase(@Nullable CharSequence original) {
|
||||||
|
if (original == null) return "";
|
||||||
|
return DIACRITICS_PATTERN.matcher(Normalizer.normalize(original, Normalizer.Form.NFD))
|
||||||
|
.replaceAll("").toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a cached Collator for the current locale, or creates a new one if locale changed.
|
||||||
|
*/
|
||||||
|
private static Collator getCollator() {
|
||||||
|
Locale currentLocale = BaseSettings.REVANCED_LANGUAGE.get().getLocale();
|
||||||
|
|
||||||
|
if (cachedCollator == null || !currentLocale.equals(cachedCollatorLocale)) {
|
||||||
|
cachedCollatorLocale = currentLocale;
|
||||||
|
cachedCollator = Collator.getInstance(currentLocale);
|
||||||
|
cachedCollator.setStrength(Collator.SECONDARY); // Case-insensitive, diacritic-insensitive.
|
||||||
|
}
|
||||||
|
|
||||||
|
return cachedCollator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts a {@link PreferenceGroup} and all nested subgroups by title or key.
|
||||||
* <p>
|
* <p>
|
||||||
* Sort order is determined by the preferences key {@link Sort} suffix.
|
* The sort order is controlled by the {@link Sort} suffix present in the preference key.
|
||||||
|
* Preferences without a key or without a {@link Sort} suffix remain in their original order.
|
||||||
* <p>
|
* <p>
|
||||||
* If a preference has no key or no {@link Sort} suffix,
|
* Sorting is performed using {@link Collator} with the current user locale,
|
||||||
* then the preferences are left unsorted.
|
* ensuring correct alphabetical ordering for all supported languages
|
||||||
|
* (e.g., Ukrainian "і", German "ß", French accented characters, etc.).
|
||||||
|
*
|
||||||
|
* @param group the {@link PreferenceGroup} to sort
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public static void sortPreferenceGroups(PreferenceGroup group) {
|
public static void sortPreferenceGroups(PreferenceGroup group) {
|
||||||
Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED);
|
Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED);
|
||||||
List<Pair<String, Preference>> preferences = new ArrayList<>();
|
List<Pair<String, Preference>> preferences = new ArrayList<>();
|
||||||
|
|
||||||
|
// Get cached Collator for locale-aware string comparison.
|
||||||
|
Collator collator = getCollator();
|
||||||
|
|
||||||
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
||||||
Preference preference = group.getPreference(i);
|
Preference preference = group.getPreference(i);
|
||||||
|
|
||||||
@@ -1030,10 +1071,11 @@ public class Utils {
|
|||||||
preferences.add(new Pair<>(sortValue, preference));
|
preferences.add(new Pair<>(sortValue, preference));
|
||||||
}
|
}
|
||||||
|
|
||||||
//noinspection ComparatorCombinators
|
// Sort the list using locale-specific collation rules.
|
||||||
Collections.sort(preferences, (pair1, pair2)
|
Collections.sort(preferences, (pair1, pair2)
|
||||||
-> pair1.first.compareTo(pair2.first));
|
-> collator.compare(pair1.first, pair2.first));
|
||||||
|
|
||||||
|
// Reassign order values to reflect the new sorted sequence
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (Pair<String, Preference> pair : preferences) {
|
for (Pair<String, Preference> pair : preferences) {
|
||||||
int order = index++;
|
int order = index++;
|
||||||
|
|||||||
@@ -392,10 +392,13 @@ public abstract class Setting<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the parent Settings that this setting depends on.
|
* Get the parent Settings that this setting depends on.
|
||||||
* @return List of parent Settings (e.g., BooleanSetting or EnumSetting), or empty list if no dependencies exist.
|
* @return List of parent Settings, or empty list if no dependencies exist.
|
||||||
|
* Defensive: handles null availability or missing getParentSettings() override.
|
||||||
*/
|
*/
|
||||||
public List<Setting<?>> getParentSettings() {
|
public List<Setting<?>> getParentSettings() {
|
||||||
return availability == null ? Collections.emptyList() : availability.getParentSettings();
|
return availability == null
|
||||||
|
? Collections.emptyList()
|
||||||
|
: Objects.requireNonNullElse(availability.getParentSettings(), Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public abstract class BaseSearchResultItem {
|
|||||||
|
|
||||||
// Shared method for highlighting text with search query.
|
// Shared method for highlighting text with search query.
|
||||||
protected static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern) {
|
protected static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern) {
|
||||||
if (TextUtils.isEmpty(text)) return text;
|
if (TextUtils.isEmpty(text) || queryPattern == null) return text;
|
||||||
|
|
||||||
final int adjustedColor = Utils.adjustColorBrightness(
|
final int adjustedColor = Utils.adjustColorBrightness(
|
||||||
Utils.getAppBackgroundColor(), 0.95f, 1.20f);
|
Utils.getAppBackgroundColor(), 0.95f, 1.20f);
|
||||||
@@ -84,7 +84,10 @@ public abstract class BaseSearchResultItem {
|
|||||||
|
|
||||||
Matcher matcher = queryPattern.matcher(text);
|
Matcher matcher = queryPattern.matcher(text);
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
spannable.setSpan(highlightSpan, matcher.start(), matcher.end(),
|
int start = matcher.start();
|
||||||
|
int end = matcher.end();
|
||||||
|
if (start == end) continue; // Skip zero matches.
|
||||||
|
spannable.setSpan(highlightSpan, start, end,
|
||||||
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
|
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,10 +227,14 @@ public abstract class BaseSearchResultItem {
|
|||||||
return searchBuilder.toString();
|
return searchBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends normalized searchable text to the builder.
|
||||||
|
* Uses full Unicode normalization for accurate search across all languages.
|
||||||
|
*/
|
||||||
private void appendText(StringBuilder builder, CharSequence text) {
|
private void appendText(StringBuilder builder, CharSequence text) {
|
||||||
if (!TextUtils.isEmpty(text)) {
|
if (!TextUtils.isEmpty(text)) {
|
||||||
if (builder.length() > 0) builder.append(" ");
|
if (builder.length() > 0) builder.append(" ");
|
||||||
builder.append(Utils.removePunctuationToLowercase(text));
|
builder.append(Utils.normalizeTextToLowercase(text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +279,7 @@ public abstract class BaseSearchResultItem {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
boolean matchesQuery(String query) {
|
boolean matchesQuery(String query) {
|
||||||
return searchableText.contains(Utils.removePunctuationToLowercase(query));
|
return searchableText.contains(Utils.normalizeTextToLowercase(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ public abstract class BaseSearchViewController {
|
|||||||
|
|
||||||
filteredSearchItems.clear();
|
filteredSearchItems.clear();
|
||||||
|
|
||||||
String queryLower = Utils.removePunctuationToLowercase(query);
|
String queryLower = Utils.normalizeTextToLowercase(query);
|
||||||
Pattern queryPattern = Pattern.compile(Pattern.quote(queryLower), Pattern.CASE_INSENSITIVE);
|
Pattern queryPattern = Pattern.compile(Pattern.quote(queryLower), Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
// Clear highlighting only for items that were previously visible.
|
// Clear highlighting only for items that were previously visible.
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ public class SpoofVideoStreamsPatch {
|
|||||||
return Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.isAvailable()
|
return Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.isAvailable()
|
||||||
&& Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ANDROID_VR_1_43_32;
|
&& Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ANDROID_VR_1_43_32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Setting<?>> getParentSettings() {
|
||||||
|
return List.of(Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user