Skip to content

feat(YouTube - Settings): Add ability to search in settings #4881

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 46 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
073e99b
feat(YouTube - Settings): Add SearchView to ReVanced settings
MarcaDian Apr 29, 2025
f616b7f
apply from review
MarcaDian Apr 29, 2025
af49e1e
refactor -> replace SearchView with EditText
MarcaDian Apr 30, 2025
e4e6927
remove unnecessary EditText margin
MarcaDian Apr 30, 2025
768e54c
apply from review -> move search ability to another class
MarcaDian May 1, 2025
80ccc8a
redesign Search bar
MarcaDian May 1, 2025
9315832
refactor -> bring back to SearchView
MarcaDian May 1, 2025
e723218
refactor -> resolve margin problem
MarcaDian May 2, 2025
e98a6d5
refactor -> fix text direction in Arabic and Hebrew
MarcaDian May 2, 2025
2da1a90
Revert "refactor -> fix text direction in Arabic and Hebrew"
MarcaDian May 2, 2025
eef25f9
fix toolbar in other fragments
MarcaDian May 2, 2025
75fd5e6
refactor -> fix text direction in Arabic and Hebrew
MarcaDian May 2, 2025
35c794c
reapply from review
MarcaDian May 2, 2025
f910780
Possible fix for settings flickering when language override is used
LisoUseInAIKyrios May 2, 2025
31b1c1c
fix: Search RYD settings
LisoUseInAIKyrios May 2, 2025
82da35a
cleanup
LisoUseInAIKyrios May 2, 2025
fe6b963
Convert SB work in progress
LisoUseInAIKyrios May 2, 2025
c96fa38
fix export error due to RYD migration code
LisoUseInAIKyrios May 2, 2025
82f488c
fix SB settings not updating after importing settings
LisoUseInAIKyrios May 2, 2025
6b60d9b
Use utility method to check for RTL text layout
LisoUseInAIKyrios May 2, 2025
8ca7973
refactor
LisoUseInAIKyrios May 2, 2025
8e6c2ed
fixes, refactor
LisoUseInAIKyrios May 2, 2025
e8ee20a
refactor: Remove unused preference list
LisoUseInAIKyrios May 2, 2025
30355d6
fix sub-preference search in settings
MarcaDian May 3, 2025
2cb1118
clean
MarcaDian May 3, 2025
f4d9a01
Merge remote-tracking branch 'revanced/dev' into add_searchbar_to_set…
MarcaDian May 3, 2025
00f4944
possibly fix looping
MarcaDian May 3, 2025
98e8993
refactor
LisoUseInAIKyrios May 3, 2025
3a0602a
Work in progress: Show navigation path as preference category
LisoUseInAIKyrios May 3, 2025
e3de443
fix duplicate preference groups
LisoUseInAIKyrios May 3, 2025
ed38d91
refactor
LisoUseInAIKyrios May 3, 2025
f6db6c5
fix settings migration
LisoUseInAIKyrios May 3, 2025
db626ea
refactor
LisoUseInAIKyrios May 3, 2025
1d42739
refactor
LisoUseInAIKyrios May 3, 2025
0fd733d
Work in progress: Category titles are clickable. Needs back button f…
LisoUseInAIKyrios May 3, 2025
04c8572
Revert "Work in progress: Category titles are clickable. Needs back …
LisoUseInAIKyrios May 3, 2025
be884f3
cleanup
LisoUseInAIKyrios May 3, 2025
7079e62
work in progress: Modernize SB settings preferences
LisoUseInAIKyrios May 3, 2025
06769f4
Merge remote-tracking branch 'upstream/dev' into add_searchbar_to_set…
LisoUseInAIKyrios May 4, 2025
212b946
possible fix for missing search results after locking/unlocking
LisoUseInAIKyrios May 4, 2025
eec41da
fix categories displayed in inconsistent order compared to it's enclo…
LisoUseInAIKyrios May 4, 2025
f672ff4
Work in progress modernization of last SB settings. Reverting until …
LisoUseInAIKyrios May 4, 2025
2ea2214
Revert "Work in progress modernization of last SB settings. Revertin…
LisoUseInAIKyrios May 4, 2025
ffc7318
fix SB categories not showing in search
LisoUseInAIKyrios May 4, 2025
93e35f1
fix SB categories not working after importing but not restarting
LisoUseInAIKyrios May 4, 2025
65c20bf
fix query hint
MarcaDian May 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package app.revanced.extension.youtube.settings;

import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;

import android.annotation.SuppressLint;
Expand All @@ -8,6 +9,8 @@
import android.preference.PreferenceFragment;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.SearchView;
import android.widget.TextView;
import android.widget.Toolbar;

Expand Down Expand Up @@ -108,7 +111,7 @@ public static void initialize(Activity licenseActivity) {
return;
}

createToolbar(licenseActivity, toolbarTitleResourceName);
createToolbar(licenseActivity, toolbarTitleResourceName, fragment);

//noinspection deprecation
licenseActivity.getFragmentManager()
Expand All @@ -121,12 +124,12 @@ public static void initialize(Activity licenseActivity) {
}

@SuppressLint("UseCompatLoadingForDrawables")
private static void createToolbar(Activity activity, String toolbarTitleResourceName) {
private static void createToolbar(Activity activity, String toolbarTitleResourceName, PreferenceFragment fragment) {
// Replace dummy placeholder toolbar.
// This is required to fix submenu title alignment issue with Android ASOP 15+
ViewGroup toolBarParent = activity.findViewById(
getResourceIdentifier("revanced_toolbar_parent", "id"));
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent,"revanced_toolbar");
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent, "revanced_toolbar");
toolbarLayoutParams = dummyToolbar.getLayoutParams();
toolBarParent.removeView(dummyToolbar);

Expand All @@ -147,7 +150,34 @@ private static void createToolbar(Activity activity, String toolbarTitleResource
}
setToolbarLayoutParams(toolbar);

// Add SearchView only for ReVancedPreferenceFragment
if (fragment instanceof ReVancedPreferenceFragment) {
SearchView searchView = activity.findViewById(getResourceIdentifier("search_view", "id"));
if (searchView != null) {
searchView.setQueryHint(str("revanced_search_settings"));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
Logger.printDebug(() -> "Search query: " + newText);
((ReVancedPreferenceFragment) fragment).filterPreferences(newText);
return true;
}
});
} else {
Logger.printDebug(() -> "SearchView not found in layout");
}
} else {
// Remove SearchView for SponsorBlock and ReturnYouTubeDislike
ViewGroup searchView = activity.findViewById(getResourceIdentifier("search_view", "id"));
if (searchView != null) {
((ViewGroup) searchView.getParent()).removeView(searchView);
}
}

toolBarParent.addView(toolbar, 0);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.text.TextUtils;
import android.util.Pair;
import android.util.TypedValue;
import android.view.ViewGroup;
Expand All @@ -18,7 +22,9 @@
import android.widget.Toolbar;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
Expand All @@ -37,6 +43,11 @@
*/
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {

private PreferenceScreen preferenceScreen;
private PreferenceScreen originalPreferenceScreen;
private List<Preference> topLevelPreferences;
private List<Preference> allPreferences;

@SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getBackButtonDrawable() {
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme()
Expand Down Expand Up @@ -96,12 +107,35 @@ private static void sortListPreferenceByValues(ListPreference listPreference, in
listPreference.setEntryValues(sortedEntryValues);
}

/**
* Initializes the preference fragment, copying the original screen to allow full restoration.
*/
@Override
protected void initialize() {
super.initialize();

try {
setPreferenceScreenToolbar(getPreferenceScreen());
preferenceScreen = getPreferenceScreen();
if (preferenceScreen == null) {
Logger.printDebug(() -> "PreferenceScreen is null during initialization");
throw new IllegalStateException("PreferenceScreen is null");
}

// Store the original structure for restoration after filtering
originalPreferenceScreen = getPreferenceManager().createPreferenceScreen(getContext());
for (int i = 0; i < preferenceScreen.getPreferenceCount(); i++) {
originalPreferenceScreen.addPreference(preferenceScreen.getPreference(i));
}

topLevelPreferences = new ArrayList<>();
allPreferences = new ArrayList<>();

for (int i = 0; i < preferenceScreen.getPreferenceCount(); i++) {
topLevelPreferences.add(preferenceScreen.getPreference(i));
}

collectPreferences(preferenceScreen, allPreferences);
setPreferenceScreenToolbar(preferenceScreen);

// If the preference was included, then initialize it based on the available playback speed.
Preference preference = findPreference(Settings.PLAYBACK_SPEED_DEFAULT.key);
Expand All @@ -117,13 +151,93 @@ protected void initialize() {
}
}

/**
* Recursively collects all preferences from the screen.
*/
private void collectPreferences(PreferenceScreen screen, List<Preference> preferences) {
for (int i = 0; i < screen.getPreferenceCount(); i++) {
Preference preference = screen.getPreference(i);
preferences.add(preference);
if (preference instanceof PreferenceScreen) {
collectPreferences((PreferenceScreen) preference, preferences);
}
}
}

/**
* Filters the preferences using the given query string.
*/
public void filterPreferences(String query) {
if (preferenceScreen == null || topLevelPreferences == null || allPreferences == null || originalPreferenceScreen == null) {
return;
}

preferenceScreen.removeAll();
if (TextUtils.isEmpty(query)) {
restoreOriginalPreferences(preferenceScreen, originalPreferenceScreen);
} else {
String queryLower = query.toLowerCase();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also strip spaces? "sponsorblock" and "sponsor block" would match.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or things like ', " etc. Lowercase alphabet would be ideal probably. @LisoUseInAIKyrios what do you say?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the exception of "SponsorBlock" and "Sponsor Block", I don't think the preference text has any ambiguos spacing or punctuation that would affect searching. Even if searching for "sponsor block", the SponsorBlock settings will show before they get to "block". Using a combination of individual word searches (if little to no results exist) I mentioned would also fix this since "sponsor block" would show the combined search of each word "sponsor" and "block".

Since the results show up as the user is typing, I think they may stop when the result they want is shown.

Copy link
Member

@oSumAtrIX oSumAtrIX Apr 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the preference text has any ambiguos spacing

That's just an assumption. There's no benefit in including spaces in search. It's only beneficial for the statistics to omit them from the search as it's more unlikely to be useful in search than not useful

Set<Preference> addedPreferences = new HashSet<>();
for (Preference preference : allPreferences) {
String title = preference.getTitle() != null ? preference.getTitle().toString().toLowerCase() : "";
String summary = preference.getSummary() != null ? preference.getSummary().toString().toLowerCase() : "";
if (title.contains(queryLower) || summary.contains(queryLower)) {
addPreferenceWithParent(preference, preferenceScreen, addedPreferences);
}
}
}
}

/**
* Adds a preference to the target screen, preserving parent category if applicable.
*/
private void addPreferenceWithParent(Preference preference, PreferenceScreen targetScreen, Set<Preference> addedPreferences) {
if (addedPreferences.contains(preference)) return;

PreferenceGroup parent = preference.getParent();
if (parent instanceof PreferenceCategory && !addedPreferences.contains(parent)) {
PreferenceCategory newCategory = new PreferenceCategory(targetScreen.getContext());
newCategory.setKey(parent.getKey());
newCategory.setTitle(parent.getTitle());
newCategory.setSummary(parent.getSummary());
newCategory.setIcon(parent.getIcon());
targetScreen.addPreference(newCategory);
addedPreferences.add(parent);
newCategory.addPreference(preference);
} else {
targetScreen.addPreference(preference);
}
addedPreferences.add(preference);
}

/**
* Restores preferences to the original state before filtering.
*/
private void restoreOriginalPreferences(PreferenceScreen targetScreen, PreferenceScreen sourceScreen) {
targetScreen.removeAll();
for (int i = 0; i < sourceScreen.getPreferenceCount(); i++) {
targetScreen.addPreference(sourceScreen.getPreference(i));
}
setPreferenceScreenToolbar(targetScreen);
Utils.sortPreferenceGroups(targetScreen);
sortPreferenceListMenu(Settings.CHANGE_START_PAGE);
sortPreferenceListMenu(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LisoUseInAIKyrios Searching should be possible via the base implementation of the settings patch, so when we add the patch to other apps, its there as well. Isn't this implemented in the wrong place?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only simple solution is to make a self sorting preference list, but there's nuances to that since for this use case the first list element should always be first (so 'default' or 'disabled' is always first).

The biggest headache of doing that is the way preferences are programmatically created during patching, then serialized to xml, then de-serialized during runtime. Because of the xml serialization it makes adding extra custom parameters very difficult or overly complex (such as do not sort the first N preference list elements).

The best solution is to not do the xml serialization/deserialization and instead create all preferences at runtime, similar to how ReVanced TikTok does it. Then all kinds of extra data and behavior could easily be added to the preference objects and not what RV YouTube does with clunky static methods modifying stock Android Preferences.

Making that change is way outside the scope of this PR and would be a lot of work to accomplish, but it would both simplify things and open up a lot of use cases that are difficult or almost impossible to do right now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR would break compilation on the attempt to separate the shared patches to their own extension as the youtube unspecific patch references youtube specific settings. For that the code needs to be restructured

sortPreferenceListMenu(BaseSettings.REVANCED_LANGUAGE);
}

/**
* Sorts a specific list preference by its entries.
*/
private void sortPreferenceListMenu(EnumSetting<?> setting) {
Preference preference = findPreference(setting.key);
if (preference instanceof ListPreference languagePreference) {
sortListPreferenceByValues(languagePreference, 1);
}
}

/**
* Sets toolbar for all nested preference screens.
*/
private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) {
for (int i = 0, preferenceCount = parentScreen.getPreferenceCount(); i < preferenceCount; i++) {
Preference childPreference = parentScreen.getPreference(i);
Expand Down Expand Up @@ -153,6 +267,7 @@ private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) {
toolbar.setTitle(childScreen.getTitle());
toolbar.setNavigationIcon(getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());

final int margin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ private val settingsResourcePatch = resourcePatch {

arrayOf(
ResourceGroup("drawable",
"revanced_cursor.xml",
"revanced_settings_icon.xml",
"revanced_settings_screen_00_about.xml",
"revanced_settings_screen_01_ads.xml",
Expand All @@ -89,7 +90,7 @@ private val settingsResourcePatch = resourcePatch {
"revanced_settings_screen_11_misc.xml",
"revanced_settings_screen_12_video.xml",
),
ResourceGroup("layout", "revanced_settings_with_toolbar.xml"),
ResourceGroup("layout", "revanced_settings_with_toolbar.xml")
).forEach { resourceGroup ->
copyResources("settings", resourceGroup)
}
Expand Down
1 change: 1 addition & 0 deletions patches/src/main/resources/addresources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Second \"item\" text"</string>
<patch id="misc.settings.settingsResourcePatch">
<string name="revanced_settings_submenu_title">Settings</string>
<string name="revanced_settings_title" translatable="false">ReVanced</string>
<string name="revanced_search_settings">Search settings</string>
<string name="revanced_settings_confirm_user_dialog_title">Do you wish to proceed?</string>
<string name="revanced_settings_reset">Reset</string>
<string name="revanced_settings_restart_title">Refresh and restart</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
android:id="@+id/revanced_cursor">
<solid android:color="?ytTextPrimary" />
<size android:width="1dp" />
</shape>
3 changes: 3 additions & 0 deletions patches/src/main/resources/settings/host/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@
<item name="android:windowIsTranslucent">true</item>
<item name="android:background">@android:color/transparent</item>
</style>
<style name="revanced_searchbar_cursor">
<item name="android:textCursorDrawable">@drawable/revanced_cursor</item>
</style>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@
app:title="@string/revanced_settings_title" />
</FrameLayout>

<android.widget.SearchView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:iconifiedByDefault="false"
android:searchIcon="@null"
android:queryBackground="@null"
android:layout_margin="5dp"
android:theme="@style/revanced_searchbar_cursor"
android:padding="5dp" />

<FrameLayout
android:id="@+id/revanced_settings_fragments"
android:layout_width="match_parent"
Expand Down