mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-31 06:31:02 +00:00
feat(YouTube Music): Add Settings patch (#5838)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
@@ -1,50 +1,120 @@
|
||||
package app.revanced.extension.youtube.settings;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.view.View;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.AppLanguage;
|
||||
import app.revanced.extension.shared.settings.BaseActivityHook;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.youtube.patches.VersionCheckPatch;
|
||||
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
|
||||
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
|
||||
|
||||
/**
|
||||
* Hooks LicenseActivity.
|
||||
* <p>
|
||||
* This class is responsible for injecting our own fragment by replacing the LicenseActivity.
|
||||
* Hooks LicenseActivity to inject a custom ReVancedPreferenceFragment with a toolbar and search functionality.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class LicenseActivityHook extends Activity {
|
||||
@SuppressWarnings("deprecation")
|
||||
public class LicenseActivityHook extends BaseActivityHook {
|
||||
|
||||
private static int currentThemeValueOrdinal = -1; // Must initially be a non-valid enum ordinal value.
|
||||
|
||||
private static ViewGroup.LayoutParams toolbarLayoutParams;
|
||||
|
||||
/**
|
||||
* Controller for managing search view components in the toolbar.
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public static SearchViewController searchViewController;
|
||||
|
||||
public static void setToolbarLayoutParams(Toolbar toolbar) {
|
||||
if (toolbarLayoutParams != null) {
|
||||
toolbar.setLayoutParams(toolbarLayoutParams);
|
||||
/**
|
||||
* Injection point
|
||||
* <p>
|
||||
* Creates an instance of LicenseActivityHook for use in static initialization.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static LicenseActivityHook createInstance() {
|
||||
return new LicenseActivityHook();
|
||||
}
|
||||
|
||||
/**
|
||||
* Customizes the activity theme based on dark/light mode.
|
||||
*/
|
||||
@Override
|
||||
protected void customizeActivityTheme(Activity activity) {
|
||||
final var theme = Utils.isDarkModeEnabled()
|
||||
? "Theme.YouTube.Settings.Dark"
|
||||
: "Theme.YouTube.Settings";
|
||||
activity.setTheme(Utils.getResourceIdentifier(theme, "style"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resource ID for the YouTube settings layout.
|
||||
*/
|
||||
@Override
|
||||
protected int getContentViewResourceId() {
|
||||
return Utils.getResourceIdentifier("revanced_settings_with_toolbar", "layout");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the toolbar background color based on dark/light mode.
|
||||
*/
|
||||
@Override
|
||||
protected int getToolbarBackgroundColor() {
|
||||
final String colorName = Utils.isDarkModeEnabled()
|
||||
? "yt_black3"
|
||||
: "yt_white1";
|
||||
return Utils.getColorFromString(colorName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the navigation icon drawable for the toolbar.
|
||||
*/
|
||||
@Override
|
||||
protected Drawable getNavigationIcon() {
|
||||
return ReVancedPreferenceFragment.getBackButtonDrawable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the click listener for the navigation icon.
|
||||
*/
|
||||
@Override
|
||||
protected View.OnClickListener getNavigationClickListener(Activity activity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds search view components to the toolbar for ReVancedPreferenceFragment.
|
||||
*
|
||||
* @param activity The activity hosting the toolbar.
|
||||
* @param toolbar The configured toolbar.
|
||||
* @param fragment The PreferenceFragment associated with the activity.
|
||||
*/
|
||||
@Override
|
||||
protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) {
|
||||
if (fragment instanceof ReVancedPreferenceFragment) {
|
||||
searchViewController = SearchViewController.addSearchViewComponents(
|
||||
activity, toolbar, (ReVancedPreferenceFragment) fragment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ReVancedPreferenceFragment for the activity.
|
||||
*/
|
||||
@Override
|
||||
protected PreferenceFragment createPreferenceFragment() {
|
||||
return new ReVancedPreferenceFragment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Overrides the ReVanced settings language.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static Context getAttachBaseContext(Context original) {
|
||||
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
|
||||
if (language == AppLanguage.DEFAULT) {
|
||||
@@ -57,6 +127,7 @@ public class LicenseActivityHook extends Activity {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static boolean useCairoSettingsFragment(boolean original) {
|
||||
// Early targets have layout issues and it's better to always force off.
|
||||
if (!VersionCheckPatch.IS_19_34_OR_GREATER) {
|
||||
@@ -80,87 +151,6 @@ public class LicenseActivityHook extends Activity {
|
||||
/**
|
||||
* Injection point.
|
||||
* <p>
|
||||
* Hooks LicenseActivity#onCreate in order to inject our own fragment.
|
||||
*/
|
||||
public static void initialize(Activity licenseActivity) {
|
||||
try {
|
||||
setActivityTheme(licenseActivity);
|
||||
ReVancedPreferenceFragment.setNavigationBarColor(licenseActivity.getWindow());
|
||||
licenseActivity.setContentView(getResourceIdentifier(
|
||||
"revanced_settings_with_toolbar", "layout"));
|
||||
|
||||
// Sanity check.
|
||||
String dataString = licenseActivity.getIntent().getDataString();
|
||||
if (!"revanced_settings_intent".equals(dataString)) {
|
||||
Logger.printException(() -> "Unknown intent: " + dataString);
|
||||
return;
|
||||
}
|
||||
|
||||
PreferenceFragment fragment = new ReVancedPreferenceFragment();
|
||||
createToolbar(licenseActivity, fragment);
|
||||
|
||||
//noinspection deprecation
|
||||
licenseActivity.getFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(getResourceIdentifier("revanced_settings_fragments", "id"), fragment)
|
||||
.commit();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "initialize failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UseCompatLoadingForDrawables")
|
||||
private static void createToolbar(Activity activity, 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");
|
||||
toolbarLayoutParams = dummyToolbar.getLayoutParams();
|
||||
toolBarParent.removeView(dummyToolbar);
|
||||
|
||||
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
|
||||
toolbar.setBackgroundColor(getToolbarBackgroundColor());
|
||||
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
|
||||
toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string"));
|
||||
|
||||
final int margin = Utils.dipToPixels(16);
|
||||
toolbar.setTitleMarginStart(margin);
|
||||
toolbar.setTitleMarginEnd(margin);
|
||||
TextView toolbarTextView = Utils.getChildView(toolbar, false,
|
||||
view -> view instanceof TextView);
|
||||
if (toolbarTextView != null) {
|
||||
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
|
||||
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||
}
|
||||
setToolbarLayoutParams(toolbar);
|
||||
|
||||
// Add Search bar only for ReVancedPreferenceFragment.
|
||||
if (fragment instanceof ReVancedPreferenceFragment) {
|
||||
searchViewController = SearchViewController.addSearchViewComponents(activity, toolbar, (ReVancedPreferenceFragment) fragment);
|
||||
}
|
||||
|
||||
toolBarParent.addView(toolbar, 0);
|
||||
}
|
||||
|
||||
public static void setActivityTheme(Activity activity) {
|
||||
final var theme = Utils.isDarkModeEnabled()
|
||||
? "Theme.YouTube.Settings.Dark"
|
||||
: "Theme.YouTube.Settings";
|
||||
activity.setTheme(getResourceIdentifier(theme, "style"));
|
||||
}
|
||||
|
||||
public static int getToolbarBackgroundColor() {
|
||||
final String colorName = Utils.isDarkModeEnabled()
|
||||
? "yt_black3"
|
||||
: "yt_white1";
|
||||
|
||||
return Utils.getColorFromString(colorName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* Updates dark/light mode since YT settings can force light/dark mode
|
||||
* which can differ from the global device settings.
|
||||
*/
|
||||
@@ -173,6 +163,10 @@ public class LicenseActivityHook extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles configuration changes, such as orientation, to update the search view.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void handleConfigurationChanged(Activity activity, Configuration newConfig) {
|
||||
if (searchViewController != null) {
|
||||
searchViewController.handleOrientationChange(newConfig.orientation);
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package app.revanced.extension.youtube.settings.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.preference.Preference;
|
||||
import app.revanced.extension.shared.settings.preference.LogBufferManager;
|
||||
|
||||
/**
|
||||
* A custom preference that clears the ReVanced debug log buffer when clicked.
|
||||
* Invokes the {@link LogBufferManager#clearLogBuffer} method.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ClearLogBufferPreference extends Preference {
|
||||
|
||||
{
|
||||
setOnPreferenceClickListener(pref -> {
|
||||
LogBufferManager.clearLogBuffer();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public ClearLogBufferPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
public ClearLogBufferPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
public ClearLogBufferPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public ClearLogBufferPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package app.revanced.extension.youtube.settings.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.preference.Preference;
|
||||
import app.revanced.extension.shared.settings.preference.LogBufferManager;
|
||||
|
||||
/**
|
||||
* A custom preference that triggers exporting ReVanced debug logs to the clipboard when clicked.
|
||||
* Invokes the {@link LogBufferManager#exportToClipboard} method.
|
||||
*/
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class ExportLogToClipboardPreference extends Preference {
|
||||
|
||||
{
|
||||
setOnPreferenceClickListener(pref -> {
|
||||
LogBufferManager.exportToClipboard();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public ExportLogToClipboardPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
public ExportLogToClipboardPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
public ExportLogToClipboardPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public ExportLogToClipboardPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,7 @@ package app.revanced.extension.youtube.settings.preference;
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
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;
|
||||
@@ -17,11 +13,6 @@ import android.preference.SwitchPreference;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
@@ -40,16 +31,16 @@ import java.util.regex.Pattern;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment;
|
||||
import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory;
|
||||
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
|
||||
import app.revanced.extension.youtube.settings.LicenseActivityHook;
|
||||
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
|
||||
|
||||
/**
|
||||
* Preference fragment for ReVanced settings.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
@SuppressWarnings({"deprecation", "NewApi"})
|
||||
public class ReVancedPreferenceFragment extends ToolbarPreferenceFragment {
|
||||
|
||||
/**
|
||||
* The main PreferenceScreen used to display the current set of preferences.
|
||||
@@ -70,31 +61,6 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
*/
|
||||
private final List<AbstractPreferenceSearchData<?>> allPreferences = new ArrayList<>();
|
||||
|
||||
@SuppressLint("UseCompatLoadingForDrawables")
|
||||
public static Drawable getBackButtonDrawable() {
|
||||
final int backButtonResource = getResourceIdentifier("revanced_settings_toolbar_arrow_left", "drawable");
|
||||
Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource);
|
||||
drawable.setTint(Utils.getAppForegroundColor());
|
||||
return drawable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the system navigation bar color for the activity.
|
||||
* Applies the background color obtained from {@link Utils#getAppBackgroundColor()} to the navigation bar.
|
||||
* For Android 10 (API 29) and above, enforces navigation bar contrast to ensure visibility.
|
||||
*/
|
||||
public static void setNavigationBarColor(@Nullable Window window) {
|
||||
if (window == null) {
|
||||
Logger.printDebug(() -> "Cannot set navigation bar color, window is null");
|
||||
return;
|
||||
}
|
||||
|
||||
window.setNavigationBarColor(Utils.getAppBackgroundColor());
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.setNavigationBarContrastEnforced(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the preference fragment, copying the original screen to allow full restoration.
|
||||
*/
|
||||
@@ -139,8 +105,28 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets toolbar for all nested preference screens.
|
||||
*/
|
||||
@Override
|
||||
protected void customizeToolbar(Toolbar toolbar) {
|
||||
LicenseActivityHook.setToolbarLayoutParams(toolbar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform actions after toolbar setup.
|
||||
*/
|
||||
@Override
|
||||
protected void onPostToolbarSetup(Toolbar toolbar, Dialog preferenceScreenDialog) {
|
||||
if (LicenseActivityHook.searchViewController != null
|
||||
&& LicenseActivityHook.searchViewController.isSearchActive()) {
|
||||
toolbar.post(() -> LicenseActivityHook.searchViewController.closeSearch());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively collects all preferences from the screen or group.
|
||||
*
|
||||
* @param includeDepth Menu depth to start including preferences.
|
||||
* A value of 0 adds all preferences.
|
||||
*/
|
||||
@@ -222,75 +208,6 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
preferenceScreen.addPreference(noResultsPreference);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets toolbar for all nested preference screens.
|
||||
*/
|
||||
private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) {
|
||||
for (int i = 0, count = parentScreen.getPreferenceCount(); i < count; i++) {
|
||||
Preference childPreference = parentScreen.getPreference(i);
|
||||
if (childPreference instanceof PreferenceScreen) {
|
||||
// Recursively set sub preferences.
|
||||
setPreferenceScreenToolbar((PreferenceScreen) childPreference);
|
||||
|
||||
childPreference.setOnPreferenceClickListener(
|
||||
childScreen -> {
|
||||
Dialog preferenceScreenDialog = ((PreferenceScreen) childScreen).getDialog();
|
||||
ViewGroup rootView = (ViewGroup) preferenceScreenDialog
|
||||
.findViewById(android.R.id.content)
|
||||
.getParent();
|
||||
|
||||
// Fix the system navigation bar color for submenus.
|
||||
setNavigationBarColor(preferenceScreenDialog.getWindow());
|
||||
|
||||
// Fix edge-to-edge screen with Android 15 and YT 19.45+
|
||||
// https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
rootView.setOnApplyWindowInsetsListener((v, insets) -> {
|
||||
Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars());
|
||||
Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars());
|
||||
Insets cutoutInsets = insets.getInsets(WindowInsets.Type.displayCutout());
|
||||
|
||||
// Apply padding for display cutout in landscape.
|
||||
int leftPadding = cutoutInsets.left;
|
||||
int rightPadding = cutoutInsets.right;
|
||||
int topPadding = statusInsets.top;
|
||||
int bottomPadding = navInsets.bottom;
|
||||
|
||||
v.setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
|
||||
return insets;
|
||||
});
|
||||
}
|
||||
|
||||
Toolbar toolbar = new Toolbar(childScreen.getContext());
|
||||
toolbar.setTitle(childScreen.getTitle());
|
||||
toolbar.setNavigationIcon(getBackButtonDrawable());
|
||||
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
|
||||
|
||||
final int margin = Utils.dipToPixels(16);
|
||||
toolbar.setTitleMargin(margin, 0, margin, 0);
|
||||
|
||||
TextView toolbarTextView = Utils.getChildView(toolbar,
|
||||
true, TextView.class::isInstance);
|
||||
if (toolbarTextView != null) {
|
||||
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
|
||||
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||
}
|
||||
|
||||
LicenseActivityHook.setToolbarLayoutParams(toolbar);
|
||||
|
||||
if (LicenseActivityHook.searchViewController != null
|
||||
&& LicenseActivityHook.searchViewController.isSearchActive()) {
|
||||
toolbar.post(() -> LicenseActivityHook.searchViewController.closeSearch());
|
||||
}
|
||||
|
||||
rootView.addView(toolbar, 0);
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
|
||||
Reference in New Issue
Block a user