Skip to content
This repository was archived by the owner on Dec 11, 2024. It is now read-only.

Commit

Permalink
feat(YouTube - Settings): Show categories while searching settings
Browse files Browse the repository at this point in the history
  • Loading branch information
anddea committed Sep 3, 2024
1 parent d6ec2fc commit c8017f1
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<Preference> 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<String, List<Preference>> dependencyMap = new HashMap<>();
// Set to track already added preferences to avoid duplicates
Expand Down Expand Up @@ -340,16 +315,28 @@ public void onDestroy() {
super.onDestroy();
}

// Map to store preferences grouped by their parent PreferenceGroup
private final Map<PreferenceGroup, List<Preference>> 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<Preference> 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) {
Expand All @@ -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.
* <p>
* 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.
*/
Expand All @@ -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();
Expand All @@ -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<PreferenceGroup, List<Preference>> 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<String> 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<PreferenceGroup, List<Preference>> entry : groupedPreferences.entrySet()) {
List<Preference> 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<PreferenceGroup, List<Preference>> entry : groupedPreferences.entrySet()) {
PreferenceGroup group = entry.getKey();
List<Preference> preferences = entry.getValue();
List<Preference> 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<PreferenceGroup, List<Preference>> entry : matchedGroupPreferences.entrySet()) {
PreferenceGroup group = entry.getKey();
List<Preference> 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<String> 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.
Expand All @@ -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 {
Expand All @@ -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<Preference> 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.
*/
Expand Down
Loading

0 comments on commit c8017f1

Please sign in to comment.