This commit is contained in:
LisoUseInAIKyrios 2025-05-09 21:25:12 +04:00
parent 828013155a
commit 208a8e2660
2 changed files with 295 additions and 283 deletions

View file

@ -19,6 +19,13 @@ import android.widget.SearchView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toolbar; import android.widget.Toolbar;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage; import app.revanced.extension.shared.settings.AppLanguage;
@ -26,19 +33,15 @@ import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.ThemeHelper; import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment; import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Set;
/** /**
* Controller for managing the search view in ReVanced settings. * Controller for managing the search view in ReVanced settings.
*/ */
@SuppressWarnings({"deprecated", "DiscouragedApi"})
public class SearchViewController { public class SearchViewController {
private final SearchView searchView; private final SearchView searchView;
private final FrameLayout searchContainer; private final FrameLayout searchContainer;
private final Toolbar toolbar; private final Toolbar toolbar;
private final Activity activity; private final Activity activity;
private final ReVancedPreferenceFragment fragment;
private boolean isSearchActive; private boolean isSearchActive;
private final CharSequence originalTitle; private final CharSequence originalTitle;
private final SharedPreferences searchHistoryPrefs; private final SharedPreferences searchHistoryPrefs;
@ -81,7 +84,6 @@ public class SearchViewController {
private SearchViewController(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) { private SearchViewController(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
this.activity = activity; this.activity = activity;
this.toolbar = toolbar; this.toolbar = toolbar;
this.fragment = fragment;
this.originalTitle = toolbar.getTitle(); this.originalTitle = toolbar.getTitle();
this.searchHistoryPrefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); this.searchHistoryPrefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
@ -107,8 +109,13 @@ public class SearchViewController {
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override @Override
public boolean onQueryTextSubmit(String query) { public boolean onQueryTextSubmit(String query) {
if (!query.trim().isEmpty()) { try {
saveSearchQuery(query.trim()); String queryTrimmed = query.trim();
if (!queryTrimmed.isEmpty()) {
saveSearchQuery(queryTrimmed);
}
} catch (Exception ex) {
Logger.printException(() -> "onQueryTextSubmit failure", ex);
} }
return false; return false;
} }
@ -181,11 +188,10 @@ public class SearchViewController {
/** /**
* Retrieves the search history from SharedPreferences. * Retrieves the search history from SharedPreferences.
* @return List of search history entries, up to MAX_HISTORY_SIZE. * @return Set of search history entries, up to MAX_HISTORY_SIZE.
*/ */
private ArrayList<String> getSearchHistory() { private Set<String> getSearchHistory() {
Set<String> historySet = searchHistoryPrefs.getStringSet(KEY_SEARCH_HISTORY, new LinkedHashSet<>()); return searchHistoryPrefs.getStringSet(KEY_SEARCH_HISTORY, new LinkedHashSet<>());
return new ArrayList<>(historySet);
} }
/** /**
@ -193,14 +199,15 @@ public class SearchViewController {
* @param query The search query to save. * @param query The search query to save.
*/ */
private void saveSearchQuery(String query) { private void saveSearchQuery(String query) {
LinkedHashSet<String> historySet = new LinkedHashSet<>(getSearchHistory()); Set<String> historySet = getSearchHistory();
historySet.remove(query); // Remove if already exists to update position historySet.remove(query); // Remove if already exists to update position
historySet.add(query); // Add to the end (most recent) historySet.add(query); // Add to the end (most recent)
// Keep only the last MAX_HISTORY_SIZE entries // Keep only the last MAX_HISTORY_SIZE entries
Iterator<String> iterator = historySet.iterator();
while (historySet.size() > MAX_HISTORY_SIZE) { while (historySet.size() > MAX_HISTORY_SIZE) {
String first = historySet.iterator().next(); iterator.next();
historySet.remove(first); iterator.remove();
} }
// Save to SharedPreferences // Save to SharedPreferences
@ -217,7 +224,7 @@ public class SearchViewController {
* @param query The search query to remove. * @param query The search query to remove.
*/ */
private void removeSearchQuery(String query) { private void removeSearchQuery(String query) {
LinkedHashSet<String> historySet = new LinkedHashSet<>(getSearchHistory()); Set<String> historySet = getSearchHistory();
historySet.remove(query); historySet.remove(query);
// Save to SharedPreferences // Save to SharedPreferences
@ -233,8 +240,8 @@ public class SearchViewController {
* Updates the search history adapter with the latest history. * Updates the search history adapter with the latest history.
*/ */
private void updateSearchHistoryAdapter() { private void updateSearchHistoryAdapter() {
AutoCompleteTextView autoCompleteTextView = searchView.findViewById( AutoCompleteTextView autoCompleteTextView = searchView.findViewById(searchView.getContext()
searchView.getContext().getResources().getIdentifier("android:id/search_src_text", null, null)); .getResources().getIdentifier("android:id/search_src_text", null, null));
if (autoCompleteTextView != null) { if (autoCompleteTextView != null) {
SearchHistoryAdapter adapter = (SearchHistoryAdapter) autoCompleteTextView.getAdapter(); SearchHistoryAdapter adapter = (SearchHistoryAdapter) autoCompleteTextView.getAdapter();
adapter.clear(); adapter.clear();
@ -283,17 +290,16 @@ public class SearchViewController {
* Custom ArrayAdapter for search history. * Custom ArrayAdapter for search history.
*/ */
private class SearchHistoryAdapter extends ArrayAdapter<String> { private class SearchHistoryAdapter extends ArrayAdapter<String> {
private final ArrayList<String> history; public SearchHistoryAdapter(Context context, Set<String> history) {
super(context, 0, new ArrayList<>(history));
public SearchHistoryAdapter(Context context, ArrayList<String> history) {
super(context, 0, history);
this.history = history;
} }
@NonNull
@Override @Override
public View getView(int position, View convertView, android.view.ViewGroup parent) { public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) {
if (convertView == null) { if (convertView == null) {
convertView = LinearLayout.inflate(getContext(), getResourceIdentifier("revanced_search_suggestion_item", "layout"), null); convertView = LinearLayout.inflate(getContext(), getResourceIdentifier(
"revanced_search_suggestion_item", "layout"), null);
} }
// Apply rounded corners programmatically // Apply rounded corners programmatically
@ -301,7 +307,8 @@ public class SearchViewController {
String query = getItem(position); String query = getItem(position);
// Set query text // Set query text
TextView textView = convertView.findViewById(getResourceIdentifier("suggestion_text", "id")); TextView textView = convertView.findViewById(getResourceIdentifier(
"suggestion_text", "id"));
if (textView != null) { if (textView != null) {
textView.setText(query); textView.setText(query);
} }

View file

@ -54,263 +54,6 @@ import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGrou
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment { public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
private abstract static class AbstractPreferenceSearchData<T extends Preference> {
/**
* @return The navigation path for the given preference, such as "Player > Action buttons".
*/
private static String getPreferenceNavigationString(Preference preference) {
Deque<CharSequence> pathElements = new ArrayDeque<>();
while (true) {
preference = preference.getParent();
if (preference == null) {
if (pathElements.isEmpty()) {
return "";
}
return Utils.getTextDirectionString() + String.join(" > ", pathElements);
}
if (!(preference instanceof NoTitlePreferenceCategory)
&& !(preference instanceof SponsorBlockPreferenceGroup)) {
CharSequence title = preference.getTitle();
if (title != null && title.length() > 0) {
pathElements.addFirst(title);
}
}
}
}
/**
* Highlights the search query in the given text by applying bold and underline style spans.
* @param text The original text to process.
* @param queryPattern The search query to highlight.
* @return The text with highlighted query matches as a SpannableStringBuilder.
*/
static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern) {
if (TextUtils.isEmpty(text)) {
return text;
}
final int baseColor = ThemeHelper.getBackgroundColor();
final int adjustedColor = ThemeHelper.isDarkTheme()
? ThemeHelper.adjustColorBrightness(baseColor, 1.20f) // Lighten for dark theme
: ThemeHelper.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
Matcher matcher = queryPattern.matcher(text);
while (matcher.find()) {
spannable.setSpan(
new BackgroundColorSpan(adjustedColor), // Highlight color
matcher.start(),
matcher.end(),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE
);
}
return spannable;
}
final T preference;
final String key;
final String navigationPath;
boolean highlightingApplied;
@Nullable
CharSequence originalTitle;
@Nullable
String searchTitle;
AbstractPreferenceSearchData(T pref) {
preference = pref;
key = Utils.removePunctuationToLowercase(pref.getKey());
navigationPath = getPreferenceNavigationString(pref);
}
@CallSuper
void updateSearchDataIfNeeded() {
if (highlightingApplied) {
// Must clear, otherwise old highlighting is still applied.
clearHighlighting();
}
CharSequence title = preference.getTitle();
if (originalTitle != title) { // Check using reference equality.
originalTitle = title;
searchTitle = Utils.removePunctuationToLowercase(title);
}
}
@CallSuper
boolean matchesSearchQuery(String query) {
updateSearchDataIfNeeded();
return key.contains(query)
|| searchTitle != null && searchTitle.contains(query);
}
@CallSuper
void applyHighlighting(String query, Pattern queryPattern) {
preference.setTitle(highlightSearchQuery(originalTitle, queryPattern));
highlightingApplied = true;
}
@CallSuper
void clearHighlighting() {
if (highlightingApplied) {
preference.setTitle(originalTitle);
highlightingApplied = false;
}
}
}
/**
* Regular preference type that only uses the base preference summary.
* Should only be used if a more specific data class does not exist.
*/
private static class PreferenceSearchData extends AbstractPreferenceSearchData<Preference> {
@Nullable
CharSequence originalSummary;
@Nullable
String searchSummary;
PreferenceSearchData(Preference pref) {
super(pref);
}
void updateSearchDataIfNeeded() {
super.updateSearchDataIfNeeded();
CharSequence summary = preference.getSummary();
if (originalSummary != summary) {
originalSummary = summary;
searchSummary = Utils.removePunctuationToLowercase(summary);
}
}
boolean matchesSearchQuery(String query) {
return super.matchesSearchQuery(query)
|| searchSummary != null && searchSummary.contains(query);
}
@Override
void applyHighlighting(String query, Pattern queryPattern) {
super.applyHighlighting(query, queryPattern);
preference.setSummary(highlightSearchQuery(originalSummary, queryPattern));
}
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
preference.setSummary(originalSummary);
}
}
/**
* Switch preference type that uses summaryOn and summaryOff.
*/
private static class SwitchPreferenceSearchData extends AbstractPreferenceSearchData<SwitchPreference> {
@Nullable
CharSequence originalSummaryOn, originalSummaryOff;
@Nullable
String searchSummaryOn, searchSummaryOff;
SwitchPreferenceSearchData(SwitchPreference pref) {
super(pref);
}
void updateSearchDataIfNeeded() {
super.updateSearchDataIfNeeded();
CharSequence summaryOn = preference.getSummaryOn();
if (originalSummaryOn != summaryOn) {
originalSummaryOn = summaryOn;
searchSummaryOn = Utils.removePunctuationToLowercase(summaryOn);
}
CharSequence summaryOff = preference.getSummaryOff();
if (originalSummaryOff != summaryOff) {
originalSummaryOff = summaryOff;
searchSummaryOff = Utils.removePunctuationToLowercase(summaryOff);
}
}
boolean matchesSearchQuery(String query) {
return super.matchesSearchQuery(query)
|| searchSummaryOn != null && searchSummaryOn.contains(query)
|| searchSummaryOff != null && searchSummaryOff.contains(query);
}
@Override
void applyHighlighting(String query, Pattern queryPattern) {
super.applyHighlighting(query, queryPattern);
preference.setSummaryOn(highlightSearchQuery(originalSummaryOn, queryPattern));
preference.setSummaryOff(highlightSearchQuery(originalSummaryOff, queryPattern));
}
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
preference.setSummaryOn(originalSummaryOn);
preference.setSummaryOff(originalSummaryOff);
}
}
/**
* List preference type that uses entries.
*/
private static class ListPreferenceSearchData extends AbstractPreferenceSearchData<ListPreference> {
@Nullable
CharSequence[] originalEntries;
@Nullable
String searchEntries;
ListPreferenceSearchData(ListPreference pref) {
super(pref);
}
void updateSearchDataIfNeeded() {
super.updateSearchDataIfNeeded();
CharSequence[] entries = preference.getEntries();
if (originalEntries != entries) {
originalEntries = entries;
searchEntries = Utils.removePunctuationToLowercase(String.join(" ", entries));
}
}
boolean matchesSearchQuery(String query) {
return super.matchesSearchQuery(query)
|| searchEntries != null && searchEntries.contains(query);
}
@Override
void applyHighlighting(String query, Pattern queryPattern) {
super.applyHighlighting(query, queryPattern);
if (originalEntries != null) {
final int length = originalEntries.length;
CharSequence[] highlightedEntries = new CharSequence[length];
for (int i = 0; i < length; i++) {
highlightedEntries[i] = highlightSearchQuery(originalEntries[i], queryPattern);
}
preference.setEntries(highlightedEntries);
}
}
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
preference.setEntries(originalEntries);
}
}
/** /**
* The main PreferenceScreen used to display the current set of preferences. * The main PreferenceScreen used to display the current set of preferences.
* This screen is manipulated during initialization and filtering to show or hide preferences. * This screen is manipulated during initialization and filtering to show or hide preferences.
@ -595,3 +338,265 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
} }
} }
} }
@SuppressWarnings("deprecation")
class AbstractPreferenceSearchData<T extends Preference> {
/**
* @return The navigation path for the given preference, such as "Player > Action buttons".
*/
private static String getPreferenceNavigationString(Preference preference) {
Deque<CharSequence> pathElements = new ArrayDeque<>();
while (true) {
preference = preference.getParent();
if (preference == null) {
if (pathElements.isEmpty()) {
return "";
}
return Utils.getTextDirectionString() + String.join(" > ", pathElements);
}
if (!(preference instanceof NoTitlePreferenceCategory)
&& !(preference instanceof SponsorBlockPreferenceGroup)) {
CharSequence title = preference.getTitle();
if (title != null && title.length() > 0) {
pathElements.addFirst(title);
}
}
}
}
/**
* Highlights the search query in the given text by applying bold and underline style spans.
* @param text The original text to process.
* @param queryPattern The search query to highlight.
* @return The text with highlighted query matches as a SpannableStringBuilder.
*/
static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern) {
if (TextUtils.isEmpty(text)) {
return text;
}
final int baseColor = ThemeHelper.getBackgroundColor();
final int adjustedColor = ThemeHelper.isDarkTheme()
? ThemeHelper.adjustColorBrightness(baseColor, 1.20f) // Lighten for dark theme
: ThemeHelper.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
Matcher matcher = queryPattern.matcher(text);
while (matcher.find()) {
spannable.setSpan(
new BackgroundColorSpan(adjustedColor), // Highlight color
matcher.start(),
matcher.end(),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE
);
}
return spannable;
}
final T preference;
final String key;
final String navigationPath;
boolean highlightingApplied;
@Nullable
CharSequence originalTitle;
@Nullable
String searchTitle;
AbstractPreferenceSearchData(T pref) {
preference = pref;
key = Utils.removePunctuationToLowercase(pref.getKey());
navigationPath = getPreferenceNavigationString(pref);
}
@CallSuper
void updateSearchDataIfNeeded() {
if (highlightingApplied) {
// Must clear, otherwise old highlighting is still applied.
clearHighlighting();
}
CharSequence title = preference.getTitle();
if (originalTitle != title) { // Check using reference equality.
originalTitle = title;
searchTitle = Utils.removePunctuationToLowercase(title);
}
}
@CallSuper
boolean matchesSearchQuery(String query) {
updateSearchDataIfNeeded();
return key.contains(query)
|| searchTitle != null && searchTitle.contains(query);
}
@CallSuper
void applyHighlighting(String query, Pattern queryPattern) {
preference.setTitle(highlightSearchQuery(originalTitle, queryPattern));
highlightingApplied = true;
}
@CallSuper
void clearHighlighting() {
if (highlightingApplied) {
preference.setTitle(originalTitle);
highlightingApplied = false;
}
}
}
/**
* Regular preference type that only uses the base preference summary.
* Should only be used if a more specific data class does not exist.
*/
@SuppressWarnings("deprecation")
class PreferenceSearchData extends AbstractPreferenceSearchData<Preference> {
@Nullable
CharSequence originalSummary;
@Nullable
String searchSummary;
PreferenceSearchData(Preference pref) {
super(pref);
}
void updateSearchDataIfNeeded() {
super.updateSearchDataIfNeeded();
CharSequence summary = preference.getSummary();
if (originalSummary != summary) {
originalSummary = summary;
searchSummary = Utils.removePunctuationToLowercase(summary);
}
}
boolean matchesSearchQuery(String query) {
return super.matchesSearchQuery(query)
|| searchSummary != null && searchSummary.contains(query);
}
@Override
void applyHighlighting(String query, Pattern queryPattern) {
super.applyHighlighting(query, queryPattern);
preference.setSummary(highlightSearchQuery(originalSummary, queryPattern));
}
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
preference.setSummary(originalSummary);
}
}
/**
* Switch preference type that uses summaryOn and summaryOff.
*/
@SuppressWarnings("deprecation")
class SwitchPreferenceSearchData extends AbstractPreferenceSearchData<SwitchPreference> {
@Nullable
CharSequence originalSummaryOn, originalSummaryOff;
@Nullable
String searchSummaryOn, searchSummaryOff;
SwitchPreferenceSearchData(SwitchPreference pref) {
super(pref);
}
void updateSearchDataIfNeeded() {
super.updateSearchDataIfNeeded();
CharSequence summaryOn = preference.getSummaryOn();
if (originalSummaryOn != summaryOn) {
originalSummaryOn = summaryOn;
searchSummaryOn = Utils.removePunctuationToLowercase(summaryOn);
}
CharSequence summaryOff = preference.getSummaryOff();
if (originalSummaryOff != summaryOff) {
originalSummaryOff = summaryOff;
searchSummaryOff = Utils.removePunctuationToLowercase(summaryOff);
}
}
boolean matchesSearchQuery(String query) {
return super.matchesSearchQuery(query)
|| searchSummaryOn != null && searchSummaryOn.contains(query)
|| searchSummaryOff != null && searchSummaryOff.contains(query);
}
@Override
void applyHighlighting(String query, Pattern queryPattern) {
super.applyHighlighting(query, queryPattern);
preference.setSummaryOn(highlightSearchQuery(originalSummaryOn, queryPattern));
preference.setSummaryOff(highlightSearchQuery(originalSummaryOff, queryPattern));
}
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
preference.setSummaryOn(originalSummaryOn);
preference.setSummaryOff(originalSummaryOff);
}
}
/**
* List preference type that uses entries.
*/
@SuppressWarnings("deprecation")
class ListPreferenceSearchData extends AbstractPreferenceSearchData<ListPreference> {
@Nullable
CharSequence[] originalEntries;
@Nullable
String searchEntries;
ListPreferenceSearchData(ListPreference pref) {
super(pref);
}
void updateSearchDataIfNeeded() {
super.updateSearchDataIfNeeded();
CharSequence[] entries = preference.getEntries();
if (originalEntries != entries) {
originalEntries = entries;
searchEntries = Utils.removePunctuationToLowercase(String.join(" ", entries));
}
}
boolean matchesSearchQuery(String query) {
return super.matchesSearchQuery(query)
|| searchEntries != null && searchEntries.contains(query);
}
@Override
void applyHighlighting(String query, Pattern queryPattern) {
super.applyHighlighting(query, queryPattern);
if (originalEntries != null) {
final int length = originalEntries.length;
CharSequence[] highlightedEntries = new CharSequence[length];
for (int i = 0; i < length; i++) {
highlightedEntries[i] = highlightSearchQuery(originalEntries[i], queryPattern);
}
preference.setEntries(highlightedEntries);
}
}
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
preference.setEntries(originalEntries);
}
}