From c8017f185621a6f4701133e3237334cc3b03c95c Mon Sep 17 00:00:00 2001 From: Aaron Veil <70171475+anddea@users.noreply.github.com> Date: Tue, 3 Sep 2024 11:26:37 +0300 Subject: [PATCH] feat(YouTube - Settings): Show categories while searching settings --- .../ReVancedPreferenceFragment.java | 260 ++++++++++++------ .../VideoQualitySettingsActivity.java | 25 +- 2 files changed, 190 insertions(+), 95 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedPreferenceFragment.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedPreferenceFragment.java index 85a68b4227..3c0a4361d9 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedPreferenceFragment.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedPreferenceFragment.java @@ -25,14 +25,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.preference.EditTextPreference; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.preference.PreferenceGroup; -import android.preference.PreferenceManager; -import android.preference.PreferenceScreen; -import android.preference.SwitchPreference; +import android.preference.*; import android.util.TypedValue; import android.view.ViewGroup; import android.widget.TextView; @@ -43,16 +36,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; +import java.util.*; import app.revanced.integrations.shared.settings.BooleanSetting; import app.revanced.integrations.shared.settings.Setting; @@ -260,17 +244,8 @@ private void setPreferenceScreenToolbar() { } // TODO: SEARCH BAR - // 0. Add ability to search for SB and RYD settings - // 1. Structure settings - // 1.1. Add each parent PreferenceScreen as PreferenceCategory while searching. - // or - // 1.2. Clarify prefs descriptions, because there are duplicates - // (e.g. "Hide cast button" in "General" and "Player buttons"). - // 2. Make PreferenceCategory in search clickable to open according PreferenceScreen. - // 3. Make search bar look more like YouTube search. - - // List to store all preferences - private final List allPreferences = new ArrayList<>(); + // - Add ability to search for SB and RYD settings + // Map to store dependencies: key is the preference key, value is a list of dependent preferences private final Map> dependencyMap = new HashMap<>(); // Set to track already added preferences to avoid duplicates @@ -340,16 +315,28 @@ public void onDestroy() { super.onDestroy(); } + // Map to store preferences grouped by their parent PreferenceGroup + private final Map> groupedPreferences = new LinkedHashMap<>(); + /** - * Recursively stores all preferences and their dependencies. + * Recursively stores all preferences and their dependencies grouped by their parent PreferenceGroup. * * @param preferenceGroup The preference group to scan. */ private void storeAllPreferences(PreferenceGroup preferenceGroup) { + Logger.printDebug(() -> "SearchFragmentPrefGroup: " + preferenceGroup); + + // Initialize a list to hold preferences of the current group + List currentGroupPreferences = groupedPreferences.computeIfAbsent(preferenceGroup, k -> new ArrayList<>()); + for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) { Preference preference = preferenceGroup.getPreference(i); - allPreferences.add(preference); - Logger.printDebug(() -> "SearchFragment: Stored preference with key: " + preference.getKey()); + + // Add preference to the current group if not already added + if (!currentGroupPreferences.contains(preference)) { + currentGroupPreferences.add(preference); + Logger.printDebug(() -> "SearchFragment: Stored preference with key: " + preference.getKey()); + } // Store dependencies if (preference.getDependency() != null) { @@ -358,16 +345,18 @@ private void storeAllPreferences(PreferenceGroup preferenceGroup) { Logger.printDebug(() -> "SearchFragment: Added dependency for key: " + dependencyKey + " on preference: " + preference.getKey()); } - if (preference instanceof PreferenceGroup preferenceGroup1) { - storeAllPreferences(preferenceGroup1); + // Recursively handle nested PreferenceGroups + if (preference instanceof PreferenceGroup nestedGroup) { + storeAllPreferences(nestedGroup); } } + + Logger.printDebug(() -> "SearchFragmentAllPrefs: " + groupedPreferences); + Logger.printDebug(() -> "SearchFragmentCurrentGroup: " + currentGroupPreferences); } /** - * Filters preferences based on the search query. - *

- * This method searches within the preference's title, summary, entries, and values. + * Filters preferences based on the search query, displaying grouped results with group titles. * * @param query The search query. */ @@ -381,8 +370,6 @@ public void filterPreferences(String query) { // Convert the query to lowercase for case-insensitive search query = query.toLowerCase(); - // String finalQuery = query; - // Logger.printDebug(() -> "SearchFragment: Query after conversion to lowercase: " + finalQuery); // Get the preference screen to modify PreferenceScreen preferenceScreen = getPreferenceScreen(); @@ -391,79 +378,155 @@ public void filterPreferences(String query) { // Clear the list of added preferences to start fresh addedPreferences.clear(); - // Loop through all available preferences - for (Preference preference : allPreferences) { - // Check if the title contains the query string - boolean matches = preference.getTitle().toString().toLowerCase().contains(query); + // Create a map to store matched preferences for each group + Map> matchedGroupPreferences = new LinkedHashMap<>(); - // Debugging title match - if (matches) { - Logger.printDebug(() -> "SearchFragment: Title matched: " + preference.getTitle()); - } + // Create a set to store all keys that should be included + Set keysToInclude = new HashSet<>(); - // Check if the summary contains the query string - CharSequence summary = preference.getSummary(); - if (!matches && summary != null && summary.toString().toLowerCase().contains(query)) { - matches = true; - Logger.printDebug(() -> "SearchFragment: Summary matched: " + summary); + // First pass: identify all preferences that match the query and their dependencies + for (Map.Entry> entry : groupedPreferences.entrySet()) { + List preferences = entry.getValue(); + for (Preference preference : preferences) { + if (preferenceMatches(preference, query)) { + addPreferenceAndDependencies(preference, keysToInclude); + } } + } - // Additional check for SwitchPreference with summaryOn and summaryOff - if (!matches && preference instanceof SwitchPreference switchPreference) { - CharSequence summaryOn = switchPreference.getSummaryOn(); - CharSequence summaryOff = switchPreference.getSummaryOff(); + // Second pass: add all identified preferences to matchedGroupPreferences + for (Map.Entry> entry : groupedPreferences.entrySet()) { + PreferenceGroup group = entry.getKey(); + List preferences = entry.getValue(); + List matchedPreferences = new ArrayList<>(); - if (summaryOn != null && summaryOn.toString().toLowerCase().contains(query)) { - matches = true; - Logger.printDebug(() -> "SearchFragment: SummaryOn matched: " + summaryOn); + for (Preference preference : preferences) { + if (keysToInclude.contains(preference.getKey())) { + matchedPreferences.add(preference); } + } + + Logger.printDebug(() -> "SearchFragment: Keys to include: " + keysToInclude); + + if (!matchedPreferences.isEmpty()) { + matchedGroupPreferences.put(group, matchedPreferences); + } + } + + Logger.printDebug(() -> "SearchFragmentMatchedGroupPreferences: " + matchedGroupPreferences); - if (summaryOff != null && summaryOff.toString().toLowerCase().contains(query)) { - matches = true; - Logger.printDebug(() -> "SearchFragment: SummaryOff matched: " + summaryOff); + // Add matched preferences to the screen, maintaining the original order + for (Map.Entry> entry : matchedGroupPreferences.entrySet()) { + PreferenceGroup group = entry.getKey(); + List matchedPreferences = entry.getValue(); + + // Add the category for this group + PreferenceCategory category = new PreferenceCategory(preferenceScreen.getContext()); + category.setTitle(group.getTitle()); + preferenceScreen.addPreference(category); + + Logger.printDebug(() -> "SearchFragment: Adding category: " + group.getTitle()); + + // Add matched preferences for this group + for (Preference preference : matchedPreferences) { + if (preference.isSelectable()) { + addPreferenceWithDependencies(category, preference); + } else { + // For non-selectable preferences, just add them directly + category.addPreference(preference); + Logger.printDebug(() -> "SearchFragment: Added non-selectable preference: " + preference.getTitle()); } } + } - // Check if the entries or values contain the query string (for ListPreference) - if (!matches && preference instanceof ListPreference listPreference) { - // Check entries - CharSequence[] entries = listPreference.getEntries(); - if (entries != null) { - for (CharSequence entry : entries) { - if (entry.toString().toLowerCase().contains(query)) { - matches = true; - Logger.printDebug(() -> "SearchFragment: Entry matched: " + entry); - break; - } + Logger.printDebug(() -> "SearchFragment: Filtered preferences added. Group count: " + matchedGroupPreferences.size()); + } + + /** + * Checks if a preference matches the given query. + * + * @param preference The preference to check. + * @param query The search query. + * @return True if the preference matches the query, false otherwise. + */ + private boolean preferenceMatches(Preference preference, String query) { + // Check if the title contains the query string + if (preference.getTitle().toString().toLowerCase().contains(query)) { + return true; + } + + // Check if the summary contains the query string + if (preference.getSummary() != null && preference.getSummary().toString().toLowerCase().contains(query)) { + return true; + } + + // Additional checks for SwitchPreference + if (preference instanceof SwitchPreference switchPreference) { + CharSequence summaryOn = switchPreference.getSummaryOn(); + CharSequence summaryOff = switchPreference.getSummaryOff(); + + if ((summaryOn != null && summaryOn.toString().toLowerCase().contains(query)) || + (summaryOff != null && summaryOff.toString().toLowerCase().contains(query))) { + return true; + } + } + + // Additional checks for ListPreference + if (preference instanceof ListPreference listPreference) { + CharSequence[] entries = listPreference.getEntries(); + if (entries != null) { + for (CharSequence entry : entries) { + if (entry.toString().toLowerCase().contains(query)) { + return true; } } + } - // Check entry values - if (!matches) { - CharSequence[] entryValues = listPreference.getEntryValues(); - if (entryValues != null) { - for (CharSequence entryValue : entryValues) { - if (entryValue.toString().toLowerCase().contains(query)) { - matches = true; - Logger.printDebug(() -> "SearchFragment: EntryValue matched: " + entryValue); - break; - } - } + CharSequence[] entryValues = listPreference.getEntryValues(); + if (entryValues != null) { + for (CharSequence entryValue : entryValues) { + if (entryValue.toString().toLowerCase().contains(query)) { + return true; } } } + } + + return false; + } + + /** + * Recursively adds a preference and its dependencies to the set of keys to include. + * + * @param preference The preference to add. + * @param keysToInclude The set of keys to include. + */ + private void addPreferenceAndDependencies(Preference preference, Set keysToInclude) { + String key = preference.getKey(); + if (key != null && !keysToInclude.contains(key)) { + keysToInclude.add(key); + + // Add the preference this one depends on + String dependencyKey = preference.getDependency(); + if (dependencyKey != null) { + Preference dependency = findPreferenceInAllGroups(dependencyKey); + if (dependency != null) { + addPreferenceAndDependencies(dependency, keysToInclude); + } + } - // If the preference matches the query, add it to the preference screen - if (matches) { - Logger.printDebug(() -> "SearchFragment: Adding preference with title: " + preference.getTitle()); - addPreferenceWithDependencies(preferenceScreen, preference); + // Add preferences that depend on this one + if (dependencyMap.containsKey(key)) { + for (Preference dependentPreference : Objects.requireNonNull(dependencyMap.get(key))) { + addPreferenceAndDependencies(dependentPreference, keysToInclude); + } } } } /** * Recursively adds a preference along with its dependencies - * (android:dependency attibute in XML). + * (android:dependency attribute in XML). * * @param preferenceGroup The preference group to add to. * @param preference The preference to add. @@ -475,7 +538,7 @@ private void addPreferenceWithDependencies(PreferenceGroup preferenceGroup, Pref if (preference.getDependency() != null) { String dependencyKey = preference.getDependency(); Logger.printDebug(() -> "SearchFragment: Adding preference dependency for key: " + dependencyKey); - Preference dependency = mPreferenceManager.findPreference(dependencyKey); + Preference dependency = findPreferenceInAllGroups(dependencyKey); if (dependency != null) { addPreferenceWithDependencies(preferenceGroup, dependency); } else { @@ -499,6 +562,23 @@ private void addPreferenceWithDependencies(PreferenceGroup preferenceGroup, Pref } } + /** + * Finds a preference in all groups based on its key. + * + * @param key The key of the preference to find. + * @return The found preference, or null if not found. + */ + private Preference findPreferenceInAllGroups(String key) { + for (List preferences : groupedPreferences.values()) { + for (Preference preference : preferences) { + if (preference.getKey() != null && preference.getKey().equals(key)) { + return preference; + } + } + } + return null; + } + /** * Resets the preference screen to its original state. */ diff --git a/app/src/main/java/com/google/android/apps/youtube/app/settings/videoquality/VideoQualitySettingsActivity.java b/app/src/main/java/com/google/android/apps/youtube/app/settings/videoquality/VideoQualitySettingsActivity.java index bf7f3c39af..a4240e1149 100644 --- a/app/src/main/java/com/google/android/apps/youtube/app/settings/videoquality/VideoQualitySettingsActivity.java +++ b/app/src/main/java/com/google/android/apps/youtube/app/settings/videoquality/VideoQualitySettingsActivity.java @@ -6,12 +6,14 @@ import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; import android.widget.SearchView; import android.widget.SearchView.OnQueryTextListener; import android.widget.TextView; import android.widget.Toolbar; import java.lang.ref.WeakReference; +import java.lang.reflect.Field; import java.util.Objects; import app.revanced.integrations.shared.utils.Logger; @@ -129,11 +131,6 @@ private void setToolbar() { toolBarParent.addView(toolbar, 0); } - /** - * TODO: in order to match the original app's search bar, we'd need to change the searchbar cursor color - * to white (#FFFFFF) if ThemeUtils.isDarkTheme(), and black (#000000) if not. - * Currently it's always blue. - */ private void setSearchView() { SearchView searchView = findViewById(ResourceUtils.getIdIdentifier("search_view")); @@ -145,6 +142,24 @@ private void setSearchView() { searchView.setQueryHint(finalSearchHint); + // Set the font size + try { + // Access the SearchView's EditText via reflection + Field field = searchView.getClass().getDeclaredField("mSearchSrcTextView"); + field.setAccessible(true); + + // Get the EditText instance + EditText searchEditText = (EditText) field.get(searchView); + + // Set the font size + if (searchEditText != null) { + searchEditText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + } + + } catch (NoSuchFieldException | IllegalAccessException e) { + Logger.printDebug(() -> "Reflection error accessing mSearchSrcTextView: " + e.getMessage()); + } + // endregion // region SearchView dimensions