feat: Use modern style settings dialogs (#5109)

Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
MarcaD
2025-06-13 10:29:13 +03:00
committed by GitHub
parent 442f5f5aec
commit 312b6dc04e
99 changed files with 2343 additions and 1324 deletions

View File

@ -5,7 +5,7 @@ import static app.revanced.extension.shared.requests.Route.Method.GET;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.Dialog;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@ -15,6 +15,8 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.PowerManager; import android.os.PowerManager;
import android.provider.Settings; import android.provider.Settings;
import android.util.Pair;
import android.widget.LinearLayout;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
@ -26,6 +28,7 @@ import java.util.Locale;
import app.revanced.extension.shared.requests.Requester; import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route; import app.revanced.extension.shared.requests.Route;
import app.revanced.extension.shared.Utils;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class GmsCoreSupport { public class GmsCoreSupport {
@ -78,13 +81,27 @@ public class GmsCoreSupport {
// Use a delay to allow the activity to finish initializing. // Use a delay to allow the activity to finish initializing.
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme. // Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
Utils.runOnMainThreadDelayed(() -> { Utils.runOnMainThreadDelayed(() -> {
// Create the custom dialog.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
str("gms_core_dialog_title"), // Title.
str(dialogMessageRef), // Message.
null, // No EditText.
str(positiveButtonTextRef), // OK button text.
() -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable.
null, // No Cancel button action.
null, // No Neutral button text.
null, // No Neutral button action.
true // Dismiss dialog when onNeutralClick.
);
Dialog dialog = dialogPair.first;
// Do not set cancelable to false, to allow using back button to skip the action, // Do not set cancelable to false, to allow using back button to skip the action,
// just in case the battery change can never be satisfied. // just in case the battery change can never be satisfied.
var dialog = new AlertDialog.Builder(context) dialog.setCancelable(true);
.setTitle(str("gms_core_dialog_title"))
.setMessage(str(dialogMessageRef)) // Show the dialog
.setPositiveButton(str(positiveButtonTextRef), onPositiveClickListener)
.create();
Utils.showDialog(context, dialog); Utils.showDialog(context, dialog);
}, 100); }, 100);
} }

View File

@ -3,15 +3,21 @@ package app.revanced.extension.shared.checks;
import static android.text.Html.FROM_HTML_MODE_COMPACT; import static android.text.Html.FROM_HTML_MODE_COMPACT;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction; import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.graphics.PorterDuff;
import android.net.Uri; import android.net.Uri;
import android.text.Html; import android.text.Html;
import android.util.Pair;
import android.view.Gravity;
import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -86,14 +92,15 @@ abstract class Check {
); );
Utils.runOnMainThreadDelayed(() -> { Utils.runOnMainThreadDelayed(() -> {
AlertDialog alert = new AlertDialog.Builder(activity) // Create the custom dialog.
.setCancelable(false) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setIconAttribute(android.R.attr.alertDialogIcon) activity,
.setTitle(str("revanced_check_environment_failed_title")) str("revanced_check_environment_failed_title"), // Title.
.setMessage(message) message, // Message.
.setPositiveButton( null, // No EditText.
" ", str("revanced_check_environment_dialog_open_official_source_button"), // OK button text.
(dialog, which) -> { () -> {
// Action for the OK (website) button.
final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE); final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(intent); activity.startActivity(intent);
@ -102,22 +109,42 @@ abstract class Check {
// which is no longer showing a warning dialog. // which is no longer showing a warning dialog.
activity.finishAffinity(); activity.finishAffinity();
System.exit(0); System.exit(0);
} },
).setNegativeButton( null, // No cancel button.
" ", str("revanced_check_environment_dialog_ignore_button"), // Neutral button text.
(dialog, which) -> { () -> {
// Neutral button action.
// Cleanup data if the user incorrectly imported a huge negative number. // Cleanup data if the user incorrectly imported a huge negative number.
final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get()); final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get());
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1); BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1);
},
true // Dismiss dialog when onNeutralClick.
);
dialog.dismiss(); // Get the dialog and main layout.
} Dialog dialog = dialogPair.first;
).create(); LinearLayout mainLayout = dialogPair.second;
Utils.showDialog(activity, alert, false, new DialogFragmentOnStartAction() { // Add icon to the dialog.
ImageView iconView = new ImageView(activity);
iconView.setImageResource(Utils.getResourceIdentifier("revanced_ic_dialog_alert", "drawable"));
iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
final int dip8 = dipToPixels(8);
iconView.setPadding(0, dip8, 0, dip8);
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
iconParams.gravity = Gravity.CENTER;
mainLayout.addView(iconView, 0); // Add icon at the top.
dialog.setCancelable(false);
// Show the dialog.
Utils.showDialog(activity, dialog, false, new DialogFragmentOnStartAction() {
boolean hasRun; boolean hasRun;
@Override @Override
public void onStart(AlertDialog dialog) { public void onStart(Dialog dialog) {
// Only run this once, otherwise if the user changes to a different app // Only run this once, otherwise if the user changes to a different app
// then changes back, this handler will run again and disable the buttons. // then changes back, this handler will run again and disable the buttons.
if (hasRun) { if (hasRun) {
@ -125,19 +152,43 @@ abstract class Check {
} }
hasRun = true; hasRun = true;
var openWebsiteButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); // Get the button container to access buttons.
LinearLayout buttonContainer = (LinearLayout) mainLayout.getChildAt(mainLayout.getChildCount() - 1);
Button openWebsiteButton;
Button ignoreButton;
// Check if buttons are in a single-row layout (buttonContainer has one child: rowContainer).
if (buttonContainer.getChildCount() == 1 && buttonContainer.getChildAt(0) instanceof LinearLayout) {
LinearLayout rowContainer = (LinearLayout) buttonContainer.getChildAt(0);
// Neutral button is the first child (index 0).
ignoreButton = (Button) rowContainer.getChildAt(0);
// OK button is the last child.
openWebsiteButton = (Button) rowContainer.getChildAt(rowContainer.getChildCount() - 1);
} else {
// Multi-row layout: buttons are in separate containers, ordered OK, Cancel, Neutral.
LinearLayout okContainer =
(LinearLayout) buttonContainer.getChildAt(0); // OK is first.
openWebsiteButton = (Button) okContainer.getChildAt(0);
LinearLayout neutralContainer =
(LinearLayout)buttonContainer.getChildAt(buttonContainer.getChildCount() - 1); // Neutral is last.
ignoreButton = (Button) neutralContainer.getChildAt(0);
}
// Initially set buttons to INVISIBLE and disabled.
openWebsiteButton.setVisibility(View.INVISIBLE);
openWebsiteButton.setEnabled(false); openWebsiteButton.setEnabled(false);
ignoreButton.setVisibility(View.INVISIBLE);
ignoreButton.setEnabled(false);
var dismissButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); // Start the countdown for showing and enabling buttons.
dismissButton.setEnabled(false); getCountdownRunnable(ignoreButton, openWebsiteButton).run();
getCountdownRunnable(dismissButton, openWebsiteButton).run();
} }
}); });
}, 1000); // Use a delay, so this dialog is shown on top of any other startup dialogs. }, 1000); // Use a delay, so this dialog is shown on top of any other startup dialogs.
} }
private static Runnable getCountdownRunnable(Button dismissButton, Button openWebsiteButton) { private static Runnable getCountdownRunnable(Button ignoreButton, Button openWebsiteButton) {
return new Runnable() { return new Runnable() {
private int secondsRemaining = SECONDS_BEFORE_SHOWING_IGNORE_BUTTON; private int secondsRemaining = SECONDS_BEFORE_SHOWING_IGNORE_BUTTON;
@ -146,17 +197,15 @@ abstract class Check {
Utils.verifyOnMainThread(); Utils.verifyOnMainThread();
if (secondsRemaining > 0) { if (secondsRemaining > 0) {
if (secondsRemaining - SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON == 0) { if (secondsRemaining - SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON <= 0) {
openWebsiteButton.setText(str("revanced_check_environment_dialog_open_official_source_button")); openWebsiteButton.setVisibility(View.VISIBLE);
openWebsiteButton.setEnabled(true); openWebsiteButton.setEnabled(true);
} }
secondsRemaining--; secondsRemaining--;
Utils.runOnMainThreadDelayed(this, 1000); Utils.runOnMainThreadDelayed(this, 1000);
} else { } else {
dismissButton.setText(str("revanced_check_environment_dialog_ignore_button")); ignoreButton.setVisibility(View.VISIBLE);
dismissButton.setEnabled(true); ignoreButton.setEnabled(true);
} }
} }
}; };

View File

@ -52,7 +52,7 @@ public class Route {
private int countMatches(CharSequence seq, char c) { private int countMatches(CharSequence seq, char c) {
int count = 0; int count = 0;
for (int i = 0; i < seq.length(); i++) { for (int i = 0, length = seq.length(); i < length; i++) {
if (seq.charAt(i) == c) if (seq.charAt(i) == c)
count++; count++;
} }

View File

@ -3,12 +3,20 @@ package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.*; 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.EditTextPreference;
import android.preference.ListPreference;
import android.util.Pair;
import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -44,7 +52,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
* Set by subclasses if Strings cannot be added as a resource. * Set by subclasses if Strings cannot be added as a resource.
*/ */
@Nullable @Nullable
protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle; protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle, restartDialogMessage;
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
try { try {
@ -76,7 +84,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
updatingPreference = true; updatingPreference = true;
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'. // Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
// Updating here can can cause a recursive call back into this same method. // Updating here can cause a recursive call back into this same method.
updatePreference(pref, setting, true, settingImportInProgress); updatePreference(pref, setting, true, settingImportInProgress);
// Update any other preference availability that may now be different. // Update any other preference availability that may now be different.
updateUIAvailability(); updateUIAvailability();
@ -116,11 +124,14 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
showingUserDialogMessage = true; showingUserDialogMessage = true;
new AlertDialog.Builder(context) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setTitle(confirmDialogTitle) context,
.setMessage(Objects.requireNonNull(setting.userDialogMessage).toString()) confirmDialogTitle, // Title.
.setPositiveButton(android.R.string.ok, (dialog, id) -> { Objects.requireNonNull(setting.userDialogMessage).toString(), // No message.
// User confirmed, save to the Setting. null, // No EditText.
null, // OK button text.
() -> {
// OK button action. User confirmed, save to the Setting.
updatePreference(pref, setting, true, false); updatePreference(pref, setting, true, false);
// Update availability of other preferences that may be changed. // Update availability of other preferences that may be changed.
@ -129,23 +140,27 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
if (setting.rebootApp) { if (setting.rebootApp) {
showRestartDialog(context); showRestartDialog(context);
} }
}) },
.setNegativeButton(android.R.string.cancel, (dialog, id) -> { () -> {
// Restore whatever the setting was before the change. // Cancel button action. Restore whatever the setting was before the change.
updatePreference(pref, setting, true, true); updatePreference(pref, setting, true, true);
}) },
.setOnDismissListener(dialog -> { null, // No Neutral button.
showingUserDialogMessage = false; null, // No Neutral button action.
}) true // Dismiss dialog when onNeutralClick.
.setCancelable(false) );
.show();
dialogPair.first.setOnDismissListener(d -> showingUserDialogMessage = false);
// Show the dialog.
dialogPair.first.show();
} }
/** /**
* Updates all Preferences values and their availability using the current values in {@link Setting}. * Updates all Preferences values and their availability using the current values in {@link Setting}.
*/ */
protected void updateUIToSettingValues() { protected void updateUIToSettingValues() {
updatePreferenceScreen(getPreferenceScreen(), true,true); updatePreferenceScreen(getPreferenceScreen(), true, true);
} }
/** /**
@ -280,17 +295,27 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
if (restartDialogTitle == null) { if (restartDialogTitle == null) {
restartDialogTitle = str("revanced_settings_restart_title"); restartDialogTitle = str("revanced_settings_restart_title");
} }
if (restartDialogMessage == null) {
restartDialogMessage = str("revanced_settings_restart_dialog_message");
}
if (restartDialogButtonText == null) { if (restartDialogButtonText == null) {
restartDialogButtonText = str("revanced_settings_restart"); restartDialogButtonText = str("revanced_settings_restart");
} }
new AlertDialog.Builder(context) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(context,
.setMessage(restartDialogTitle) restartDialogTitle, // Title.
.setPositiveButton(restartDialogButtonText, (dialog, id) restartDialogMessage, // Message.
-> Utils.restartApp(context)) null, // No EditText.
.setNegativeButton(android.R.string.cancel, null) restartDialogButtonText, // OK button text.
.setCancelable(false) () -> Utils.restartApp(context), // OK button action.
.show(); () -> {}, // Cancel button action (dismiss only).
null, // No Neutral button text.
null, // No Neutral button action.
true // Dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
} }
@SuppressLint("ResourceType") @SuppressLint("ResourceType")

View File

@ -2,8 +2,9 @@ package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier; import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
@ -18,6 +19,7 @@ import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan; import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -216,86 +218,6 @@ public class ColorPickerPreference extends EditTextPreference {
widgetColorDot.setAlpha(isEnabled() ? 1.0f : DISABLED_ALPHA); widgetColorDot.setAlpha(isEnabled() ? 1.0f : DISABLED_ALPHA);
} }
/**
* Creates a layout with a color preview and EditText for hex color input.
*
* @param context The context for creating the layout.
* @return A LinearLayout containing the color preview and EditText.
*/
private LinearLayout createDialogLayout(Context context) {
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(70, 0, 70, 0);
// Inflate color picker.
View colorPicker = LayoutInflater.from(context).inflate(
getResourceIdentifier("revanced_color_picker", "layout"), null);
dialogColorPickerView = colorPicker.findViewById(
getResourceIdentifier("color_picker_view", "id"));
dialogColorPickerView.setColor(currentColor);
layout.addView(colorPicker);
// Horizontal layout for preview and EditText.
LinearLayout inputLayout = new LinearLayout(context);
inputLayout.setOrientation(LinearLayout.HORIZONTAL);
inputLayout.setPadding(0, 20, 0, 0);
dialogColorPreview = new TextView(context);
inputLayout.addView(dialogColorPreview);
updateColorPreview();
EditText editText = getEditText();
ViewParent parent = editText.getParent();
if (parent instanceof ViewGroup parentViewGroup) {
parentViewGroup.removeView(editText);
}
editText.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
String currentColorString = getColorString(currentColor);
editText.setText(currentColorString);
editText.setSelection(currentColorString.length());
editText.setTypeface(Typeface.MONOSPACE);
colorTextWatcher = createColorTextWatcher(dialogColorPickerView);
editText.addTextChangedListener(colorTextWatcher);
inputLayout.addView(editText);
// Add a dummy view to take up remaining horizontal space,
// otherwise it will show an oversize underlined text view.
View paddingView = new View(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
0,
LinearLayout.LayoutParams.MATCH_PARENT,
1f
);
paddingView.setLayoutParams(params);
inputLayout.addView(paddingView);
layout.addView(inputLayout);
// Set up color picker listener with debouncing.
// Add listener last to prevent callbacks from set calls above.
dialogColorPickerView.setOnColorChangedListener(color -> {
// Check if it actually changed, since this callback
// can be caused by updates in afterTextChanged().
if (currentColor == color) {
return;
}
String updatedColorString = getColorString(color);
Logger.printDebug(() -> "onColorChanged: " + updatedColorString);
currentColor = color;
editText.setText(updatedColorString);
editText.setSelection(updatedColorString.length());
updateColorPreview();
updateWidgetColorDot();
});
return layout;
}
/** /**
* Updates the color preview TextView with a colored dot. * Updates the color preview TextView with a colored dot.
*/ */
@ -360,65 +282,142 @@ public class ColorPickerPreference extends EditTextPreference {
} }
/** /**
* Prepares the dialog builder with a custom view and reset button. * Creates a Dialog with a color preview and EditText for hex color input.
*
* @param builder The AlertDialog.Builder to configure.
*/ */
@Override @Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { protected void showDialog(Bundle state) {
Utils.setEditTextDialogTheme(builder); Context context = getContext();
LinearLayout dialogLayout = createDialogLayout(builder.getContext());
builder.setView(dialogLayout);
final int originalColor = currentColor;
builder.setNeutralButton(str("revanced_settings_reset_color"), null); // Inflate color picker view.
View colorPicker = LayoutInflater.from(context).inflate(
getResourceIdentifier("revanced_color_picker", "layout"), null);
dialogColorPickerView = colorPicker.findViewById(
getResourceIdentifier("revanced_color_picker_view", "id"));
dialogColorPickerView.setColor(currentColor);
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { // Horizontal layout for preview and EditText.
LinearLayout inputLayout = new LinearLayout(context);
inputLayout.setOrientation(LinearLayout.HORIZONTAL);
inputLayout.setPadding(0, 0, 0, dipToPixels(10));
dialogColorPreview = new TextView(context);
LinearLayout.LayoutParams previewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
previewParams.setMargins(dipToPixels(15), 0, dipToPixels(10), 0); // text dot has its own indents so 15, instead 16.
dialogColorPreview.setLayoutParams(previewParams);
inputLayout.addView(dialogColorPreview);
updateColorPreview();
EditText editText = getEditText();
ViewParent parent = editText.getParent();
if (parent instanceof ViewGroup parentViewGroup) {
parentViewGroup.removeView(editText);
}
editText.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
String currentColorString = getColorString(currentColor);
editText.setText(currentColorString);
editText.setSelection(currentColorString.length());
editText.setTypeface(Typeface.MONOSPACE);
colorTextWatcher = createColorTextWatcher(dialogColorPickerView);
editText.addTextChangedListener(colorTextWatcher);
inputLayout.addView(editText);
// Add a dummy view to take up remaining horizontal space,
// otherwise it will show an oversize underlined text view.
View paddingView = new View(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
0,
LinearLayout.LayoutParams.MATCH_PARENT,
1f
);
paddingView.setLayoutParams(params);
inputLayout.addView(paddingView);
// Create main container for color picker and input layout.
LinearLayout container = new LinearLayout(context);
container.setOrientation(LinearLayout.VERTICAL);
container.addView(colorPicker);
container.addView(inputLayout);
// Create custom dialog.
final int originalColor = currentColor & 0x00FFFFFF;
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
getTitle() != null ? getTitle().toString() : str("revanced_settings_color_picker_title"), // Title.
null, // No message.
null, // No EditText.
null, // OK button text.
() -> {
// OK button action.
try { try {
String colorString = getEditText().getText().toString(); String colorString = editText.getText().toString();
if (colorString.length() != COLOR_STRING_LENGTH) { if (colorString.length() != COLOR_STRING_LENGTH) {
Utils.showToastShort(str("revanced_settings_color_invalid")); Utils.showToastShort(str("revanced_settings_color_invalid"));
setText(getColorString(originalColor)); setText(getColorString(originalColor));
return; return;
} }
setText(colorString); setText(colorString);
} catch (Exception ex) { } catch (Exception ex) {
// Should never happen due to a bad color string, // Should never happen due to a bad color string,
// since the text is validated and fixed while the user types. // since the text is validated and fixed while the user types.
Logger.printException(() -> "setPositiveButton failure", ex); Logger.printException(() -> "OK button failure", ex);
} }
}); },
() -> {
builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> { // Cancel button action.
try { try {
// Restore the original color. // Restore the original color.
setText(getColorString(originalColor)); setText(getColorString(originalColor));
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "setNegativeButton failure", ex); Logger.printException(() -> "Cancel button failure", ex);
} }
}); },
} str("revanced_settings_reset_color"), // Neutral button text.
() -> {
@Override // Neutral button action.
protected void showDialog(Bundle state) {
super.showDialog(state);
AlertDialog dialog = (AlertDialog) getDialog();
dialog.setCanceledOnTouchOutside(false);
// Do not close dialog when reset is pressed.
Button button = dialog.getButton(AlertDialog.BUTTON_NEUTRAL);
button.setOnClickListener(view -> {
try { try {
final int defaultColor = Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF; final int defaultColor = Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF;
// Setting view color causes listener callback into this class. // Setting view color causes listener callback into this class.
dialogColorPickerView.setColor(defaultColor); dialogColorPickerView.setColor(defaultColor);
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "setOnClickListener failure", ex); Logger.printException(() -> "Reset button failure", ex);
} }
},
false // Do not dismiss dialog when onNeutralClick.
);
// Add the custom container to the dialog's main layout.
LinearLayout dialogMainLayout = dialogPair.second;
dialogMainLayout.addView(container, 1);
// Set up color picker listener with debouncing.
// Add listener last to prevent callbacks from set calls above.
dialogColorPickerView.setOnColorChangedListener(color -> {
// Check if it actually changed, since this callback
// can be caused by updates in afterTextChanged().
if (currentColor == color) {
return;
}
String updatedColorString = getColorString(color);
Logger.printDebug(() -> "onColorChanged: " + updatedColorString);
currentColor = color;
editText.setText(updatedColorString);
editText.setSelection(updatedColorString.length());
updateColorPreview();
updateWidgetColorDot();
}); });
// Configure and show the dialog.
Dialog dialog = dialogPair.first;
dialog.setCanceledOnTouchOutside(false);
dialog.show();
} }
@Override @Override

View File

@ -29,8 +29,8 @@ import app.revanced.extension.shared.Utils;
* <p> * <p>
* This view displays two main components for color selection: * This view displays two main components for color selection:
* <ul> * <ul>
* <li><b>Hue Bar:</b> A vertical bar on the right that allows the user to select the hue component of the color. * <li><b>Hue Bar:</b> A horizontal bar at the bottom that allows the user to select the hue component of the color.
* <li><b>Saturation-Value Selector:</b> A rectangular area that allows the user to select the saturation and value (brightness) * <li><b>Saturation-Value Selector:</b> A rectangular area above the hue bar that allows the user to select the saturation and value (brightness)
* components of the color based on the selected hue. * components of the color based on the selected hue.
* </ul> * </ul>
* *
@ -63,7 +63,7 @@ public class ColorPickerView extends View {
private static final float MARGIN_BETWEEN_AREAS = dipToPixels(24); private static final float MARGIN_BETWEEN_AREAS = dipToPixels(24);
private static final float VIEW_PADDING = dipToPixels(16); private static final float VIEW_PADDING = dipToPixels(16);
private static final float HUE_BAR_WIDTH = dipToPixels(12); private static final float HUE_BAR_HEIGHT = dipToPixels(12);
private static final float HUE_CORNER_RADIUS = dipToPixels(6); private static final float HUE_CORNER_RADIUS = dipToPixels(6);
private static final float SELECTOR_RADIUS = dipToPixels(12); private static final float SELECTOR_RADIUS = dipToPixels(12);
private static final float SELECTOR_STROKE_WIDTH = 8; private static final float SELECTOR_STROKE_WIDTH = 8;
@ -144,17 +144,17 @@ public class ColorPickerView extends View {
final float DESIRED_ASPECT_RATIO = 0.8f; // height = width * 0.8 final float DESIRED_ASPECT_RATIO = 0.8f; // height = width * 0.8
final int minWidth = Utils.dipToPixels(250); final int minWidth = Utils.dipToPixels(250);
final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO); final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS);
int width = resolveSize(minWidth, widthMeasureSpec); int width = resolveSize(minWidth, widthMeasureSpec);
int height = resolveSize(minHeight, heightMeasureSpec); int height = resolveSize(minHeight, heightMeasureSpec);
// Ensure minimum dimensions for usability // Ensure minimum dimensions for usability.
width = Math.max(width, minWidth); width = Math.max(width, minWidth);
height = Math.max(height, minHeight); height = Math.max(height, minHeight);
// Adjust height to maintain desired aspect ratio if possible // Adjust height to maintain desired aspect ratio if possible.
final int desiredHeight = (int) (width * DESIRED_ASPECT_RATIO); final int desiredHeight = (int) (width * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS);
if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) { if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
height = desiredHeight; height = desiredHeight;
} }
@ -171,22 +171,22 @@ public class ColorPickerView extends View {
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight); super.onSizeChanged(width, height, oldWidth, oldHeight);
// Calculate bounds with hue bar on the right // Calculate bounds with hue bar at the bottom.
final float effectiveWidth = width - (2 * VIEW_PADDING); final float effectiveWidth = width - (2 * VIEW_PADDING);
final float selectorWidth = effectiveWidth - HUE_BAR_WIDTH - MARGIN_BETWEEN_AREAS; final float effectiveHeight = height - (2 * VIEW_PADDING) - HUE_BAR_HEIGHT - MARGIN_BETWEEN_AREAS;
// Adjust rectangles to account for padding and density-independent dimensions // Adjust rectangles to account for padding and density-independent dimensions.
saturationValueRect.set( saturationValueRect.set(
VIEW_PADDING, VIEW_PADDING,
VIEW_PADDING, VIEW_PADDING,
VIEW_PADDING + selectorWidth, VIEW_PADDING + effectiveWidth,
height - VIEW_PADDING VIEW_PADDING + effectiveHeight
); );
hueRect.set( hueRect.set(
width - VIEW_PADDING - HUE_BAR_WIDTH,
VIEW_PADDING, VIEW_PADDING,
width - VIEW_PADDING, height - VIEW_PADDING - HUE_BAR_HEIGHT,
VIEW_PADDING + effectiveWidth,
height - VIEW_PADDING height - VIEW_PADDING
); );
@ -201,7 +201,7 @@ public class ColorPickerView extends View {
private void updateHueShader() { private void updateHueShader() {
LinearGradient hueShader = new LinearGradient( LinearGradient hueShader = new LinearGradient(
hueRect.left, hueRect.top, hueRect.left, hueRect.top,
hueRect.left, hueRect.bottom, hueRect.right, hueRect.top,
HUE_COLORS, HUE_COLORS,
null, null,
Shader.TileMode.CLAMP Shader.TileMode.CLAMP
@ -263,8 +263,8 @@ public class ColorPickerView extends View {
// Draw the hue bar. // Draw the hue bar.
canvas.drawRoundRect(hueRect, HUE_CORNER_RADIUS, HUE_CORNER_RADIUS, huePaint); canvas.drawRoundRect(hueRect, HUE_CORNER_RADIUS, HUE_CORNER_RADIUS, huePaint);
final float hueSelectorX = hueRect.centerX(); final float hueSelectorX = hueRect.left + (hue / 360f) * hueRect.width();
final float hueSelectorY = hueRect.top + (hue / 360f) * hueRect.height(); final float hueSelectorY = hueRect.centerY();
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width(); final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
final float satSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height(); final float satSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
@ -316,17 +316,17 @@ public class ColorPickerView extends View {
// Define touch expansion for the hue bar. // Define touch expansion for the hue bar.
RectF expandedHueRect = new RectF( RectF expandedHueRect = new RectF(
hueRect.left - TOUCH_EXPANSION, hueRect.left,
hueRect.top, hueRect.top - TOUCH_EXPANSION,
hueRect.right + TOUCH_EXPANSION, hueRect.right,
hueRect.bottom hueRect.bottom + TOUCH_EXPANSION
); );
switch (action) { switch (action) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
// Calculate current handle positions. // Calculate current handle positions.
final float hueSelectorX = hueRect.centerX(); final float hueSelectorX = hueRect.left + (hue / 360f) * hueRect.width();
final float hueSelectorY = hueRect.top + (hue / 360f) * hueRect.height(); final float hueSelectorY = hueRect.centerY();
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width(); final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
final float valSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height(); final float valSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
@ -348,14 +348,14 @@ public class ColorPickerView extends View {
// Check if the touch started on a handle or within the expanded hue bar area. // Check if the touch started on a handle or within the expanded hue bar area.
if (hueHitRect.contains(x, y)) { if (hueHitRect.contains(x, y)) {
isDraggingHue = true; isDraggingHue = true;
updateHueFromTouch(y); updateHueFromTouch(x);
} else if (satValHitRect.contains(x, y)) { } else if (satValHitRect.contains(x, y)) {
isDraggingSaturation = true; isDraggingSaturation = true;
updateSaturationValueFromTouch(x, y); updateSaturationValueFromTouch(x, y);
} else if (expandedHueRect.contains(x, y)) { } else if (expandedHueRect.contains(x, y)) {
// Handle touch within the expanded hue bar area. // Handle touch within the expanded hue bar area.
isDraggingHue = true; isDraggingHue = true;
updateHueFromTouch(y); updateHueFromTouch(x);
} else if (saturationValueRect.contains(x, y)) { } else if (saturationValueRect.contains(x, y)) {
isDraggingSaturation = true; isDraggingSaturation = true;
updateSaturationValueFromTouch(x, y); updateSaturationValueFromTouch(x, y);
@ -365,7 +365,7 @@ public class ColorPickerView extends View {
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
// Continue updating values even if touch moves outside the view. // Continue updating values even if touch moves outside the view.
if (isDraggingHue) { if (isDraggingHue) {
updateHueFromTouch(y); updateHueFromTouch(x);
} else if (isDraggingSaturation) { } else if (isDraggingSaturation) {
updateSaturationValueFromTouch(x, y); updateSaturationValueFromTouch(x, y);
} }
@ -387,12 +387,12 @@ public class ColorPickerView extends View {
/** /**
* Updates the hue value based on touch position, clamping to valid range. * Updates the hue value based on touch position, clamping to valid range.
* *
* @param y The y-coordinate of the touch position. * @param x The x-coordinate of the touch position.
*/ */
private void updateHueFromTouch(float y) { private void updateHueFromTouch(float x) {
// Clamp y to the hue rectangle bounds. // Clamp x to the hue rectangle bounds.
final float clampedY = Utils.clamp(y, hueRect.top, hueRect.bottom); final float clampedX = Utils.clamp(x, hueRect.left, hueRect.right);
final float updatedHue = ((clampedY - hueRect.top) / hueRect.height()) * 360f; final float updatedHue = ((clampedX - hueRect.left) / hueRect.width()) * 360f;
if (hue == updatedHue) { if (hue == updatedHue) {
return; return;
} }

View File

@ -0,0 +1,197 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.preference.ListPreference;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import app.revanced.extension.shared.Utils;
/**
* A custom ListPreference that uses a styled custom dialog with a custom checkmark indicator.
*/
@SuppressWarnings({"unused", "deprecation"})
public class CustomDialogListPreference extends ListPreference {
/**
* Custom ArrayAdapter to handle checkmark visibility.
*/
private static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
private static class SubViewDataContainer {
ImageView checkIcon;
View placeholder;
TextView itemText;
}
final int layoutResourceId;
final CharSequence[] entryValues;
String selectedValue;
public ListPreferenceArrayAdapter(Context context, int resource, CharSequence[] entries,
CharSequence[] entryValues, String selectedValue) {
super(context, resource, entries);
this.layoutResourceId = resource;
this.entryValues = entryValues;
this.selectedValue = selectedValue;
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
View view = convertView;
SubViewDataContainer holder;
if (view == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
view = inflater.inflate(layoutResourceId, parent, false);
holder = new SubViewDataContainer();
holder.checkIcon = view.findViewById(Utils.getResourceIdentifier(
"revanced_check_icon", "id"));
holder.placeholder = view.findViewById(Utils.getResourceIdentifier(
"revanced_check_icon_placeholder", "id"));
holder.itemText = view.findViewById(Utils.getResourceIdentifier(
"revanced_item_text", "id"));
view.setTag(holder);
} else {
holder = (SubViewDataContainer) view.getTag();
}
// Set text.
holder.itemText.setText(getItem(position));
holder.itemText.setTextColor(Utils.getAppForegroundColor());
// Show or hide checkmark and placeholder.
String currentValue = entryValues[position].toString();
boolean isSelected = currentValue.equals(selectedValue);
holder.checkIcon.setVisibility(isSelected ? View.VISIBLE : View.GONE);
holder.checkIcon.setColorFilter(Utils.getAppForegroundColor());
holder.placeholder.setVisibility(isSelected ? View.GONE : View.VISIBLE);
return view;
}
public void setSelectedValue(String value) {
this.selectedValue = value;
}
}
public CustomDialogListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public CustomDialogListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomDialogListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomDialogListPreference(Context context) {
super(context);
}
@Override
protected void showDialog(Bundle state) {
// Create ListView.
ListView listView = new ListView(getContext());
listView.setId(android.R.id.list);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Create custom adapter for the ListView.
ListPreferenceArrayAdapter adapter = new ListPreferenceArrayAdapter(
getContext(),
Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"),
getEntries(),
getEntryValues(),
getValue()
);
listView.setAdapter(adapter);
// Set checked item.
String currentValue = getValue();
if (currentValue != null) {
CharSequence[] entryValues = getEntryValues();
for (int i = 0, length = entryValues.length; i < length; i++) {
if (currentValue.equals(entryValues[i].toString())) {
listView.setItemChecked(i, true);
listView.setSelection(i);
break;
}
}
}
// Create the custom dialog without OK button.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
getContext(),
getTitle() != null ? getTitle().toString() : "",
null,
null,
null, // No OK button text.
null, // No OK button action.
() -> {}, // Cancel button action (just dismiss).
null,
null,
true
);
Dialog dialog = dialogPair.first;
LinearLayout mainLayout = dialogPair.second;
// Measure content height before adding ListView to layout.
// Otherwise, the ListView will push the buttons off the screen.
int totalHeight = 0;
int widthSpec = View.MeasureSpec.makeMeasureSpec(
getContext().getResources().getDisplayMetrics().widthPixels,
View.MeasureSpec.AT_MOST
);
int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, listView);
listItem.measure(widthSpec, heightSpec);
totalHeight += listItem.getMeasuredHeight();
}
// Cap the height at maxHeight.
int maxHeight = (int) (getContext().getResources().getDisplayMetrics().heightPixels * 0.6);
int finalHeight = Math.min(totalHeight, maxHeight);
// Add ListView to the main layout with calculated height.
LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
finalHeight // Use calculated height directly.
);
final int marginHorizontal = dipToPixels(8);
listViewParams.setMargins(0, marginHorizontal, 0, marginHorizontal);
mainLayout.addView(listView, mainLayout.getChildCount() - 1, listViewParams);
// Handle item click to select value and dismiss dialog.
listView.setOnItemClickListener((parent, view, position, id) -> {
String selectedValue = getEntryValues()[position].toString();
if (callChangeListener(selectedValue)) {
setValue(selectedValue);
adapter.setSelectedValue(selectedValue);
adapter.notifyDataSetChanged();
}
dialog.dismiss();
});
// Show the dialog.
dialog.show();
}
}

View File

@ -1,19 +1,30 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import android.app.AlertDialog; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference; import android.preference.EditTextPreference;
import android.preference.Preference; import android.preference.Preference;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText; import android.widget.EditText;
import app.revanced.extension.shared.settings.Setting; import android.widget.LinearLayout;
import android.widget.TextView;
import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
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.Setting;
import static app.revanced.extension.shared.StringRef.str;
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener { public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
@ -54,7 +65,8 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
try { try {
// Must set text before preparing dialog, otherwise text is non selectable if this preference is later reopened. // Must set text before showing dialog,
// otherwise text is non-selectable if this preference is later reopened.
existingSettings = Setting.exportToJson(getContext()); existingSettings = Setting.exportToJson(getContext());
getEditText().setText(existingSettings); getEditText().setText(existingSettings);
} catch (Exception ex) { } catch (Exception ex) {
@ -64,18 +76,32 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
} }
@Override @Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { protected void showDialog(Bundle state) {
try { try {
Utils.setEditTextDialogTheme(builder); Context context = getContext();
EditText editText = getEditText();
// Show the user the settings in JSON format. // Create a custom dialog with the EditText.
builder.setNeutralButton(str("revanced_settings_import_copy"), (dialog, which) -> { Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
Utils.setClipboard(getEditText().getText()); context,
}).setPositiveButton(str("revanced_settings_import"), (dialog, which) -> { str("revanced_pref_import_export_title"), // Title.
importSettings(builder.getContext(), getEditText().getText().toString()); null, // No message (EditText replaces it).
}); editText, // Pass the EditText.
str("revanced_settings_import"), // OK button text.
() -> importSettings(context, editText.getText().toString()), // OK button action.
() -> {}, // Cancel button action (dismiss only).
str("revanced_settings_import_copy"), // Neutral button (Copy) text.
() -> {
// Neutral button (Copy) action. Show the user the settings in JSON format.
Utils.setClipboard(editText.getText());
},
true // Dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "onPrepareDialogBuilder failure", ex); Logger.printException(() -> "showDialog failure", ex);
} }
} }
@ -88,7 +114,7 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
final boolean rebootNeeded = Setting.importFromJSON(context, replacementSettings); final boolean rebootNeeded = Setting.importFromJSON(context, replacementSettings);
if (rebootNeeded) { if (rebootNeeded) {
AbstractPreferenceFragment.showRestartDialog(getContext()); AbstractPreferenceFragment.showRestartDialog(context);
} }
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "importSettings failure", ex); Logger.printException(() -> "importSettings failure", ex);
@ -96,5 +122,4 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
AbstractPreferenceFragment.settingImportInProgress = false; AbstractPreferenceFragment.settingImportInProgress = false;
} }
} }
} }

View File

@ -1,6 +1,7 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import static app.revanced.extension.shared.requests.Route.Method.GET; import static app.revanced.extension.shared.requests.Route.Method.GET;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -8,7 +9,8 @@ import android.app.Dialog;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
@ -18,6 +20,7 @@ import android.util.AttributeSet;
import android.view.Window; import android.view.Window;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -48,28 +51,6 @@ public class ReVancedAboutPreference extends Preference {
return text.replace("-", "&#8209;"); // #8209 = non breaking hyphen. return text.replace("-", "&#8209;"); // #8209 = non breaking hyphen.
} }
private static String getColorHexString(int color) {
return String.format("#%06X", (0x00FFFFFF & color));
}
protected boolean isDarkModeEnabled() {
return Utils.isDarkModeEnabled();
}
/**
* Subclasses can override this and provide a themed color.
*/
protected int getLightColor() {
return Color.WHITE;
}
/**
* Subclasses can override this and provide a themed color.
*/
protected int getDarkColor() {
return Color.BLACK;
}
/** /**
* Apps that do not support bundling resources must override this. * Apps that do not support bundling resources must override this.
* *
@ -86,9 +67,8 @@ public class ReVancedAboutPreference extends Preference {
builder.append("<html>"); builder.append("<html>");
builder.append("<body style=\"text-align: center; padding: 10px;\">"); builder.append("<body style=\"text-align: center; padding: 10px;\">");
final boolean isDarkMode = isDarkModeEnabled(); String foregroundColorHex = Utils.getColorHexString(Utils.getAppForegroundColor());
String backgroundColorHex = getColorHexString(isDarkMode ? getDarkColor() : getLightColor()); String backgroundColorHex = Utils.getColorHexString(Utils.getDialogBackgroundColor());
String foregroundColorHex = getColorHexString(isDarkMode ? getLightColor() : getDarkColor());
// Apply light/dark mode colors. // Apply light/dark mode colors.
builder.append(String.format( builder.append(String.format(
"<style> body { background-color: %s; color: %s; } a { color: %s; } </style>", "<style> body { background-color: %s; color: %s; } a { color: %s; } </style>",
@ -220,14 +200,36 @@ class WebViewDialog extends Dialog {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar.
// Create main layout.
LinearLayout mainLayout = new LinearLayout(getContext());
mainLayout.setOrientation(LinearLayout.VERTICAL);
final int padding = dipToPixels(10);
mainLayout.setPadding(padding, padding, padding, padding);
// Set rounded rectangle background.
ShapeDrawable mainBackground = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(28), null, null));
mainBackground.getPaint().setColor(Utils.getDialogBackgroundColor());
mainLayout.setBackground(mainBackground);
// Create WebView.
WebView webView = new WebView(getContext()); WebView webView = new WebView(getContext());
webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new OpenLinksExternallyWebClient()); webView.setWebViewClient(new OpenLinksExternallyWebClient());
webView.loadDataWithBaseURL(null, htmlContent, "text/html", "utf-8", null); webView.loadDataWithBaseURL(null, htmlContent, "text/html", "utf-8", null);
setContentView(webView); // Add WebView to layout.
mainLayout.addView(webView);
setContentView(mainLayout);
// Set dialog window attributes
Window window = getWindow();
if (window != null) {
Utils.setDialogWindowParameters(getContext(), window);
}
} }
private class OpenLinksExternallyWebClient extends WebViewClient { private class OpenLinksExternallyWebClient extends WebViewClient {

View File

@ -1,14 +1,28 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.graphics.drawable.shapes.RoundRectShape;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.Paint.Style;
import android.os.Bundle; import android.os.Bundle;
import android.preference.EditTextPreference; import android.preference.EditTextPreference;
import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -45,10 +59,12 @@ public class ResettableEditTextPreference extends EditTextPreference {
} }
@Override @Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { protected void showDialog(Bundle state) {
super.onPrepareDialogBuilder(builder); try {
Utils.setEditTextDialogTheme(builder); Context context = getContext();
EditText editText = getEditText();
// Resolve setting if not already set.
if (setting == null) { if (setting == null) {
String key = getKey(); String key = getKey();
if (key != null) { if (key != null) {
@ -56,29 +72,47 @@ public class ResettableEditTextPreference extends EditTextPreference {
} }
} }
// Set initial EditText value to the current persisted value or empty string.
String initialValue = getText() != null ? getText() : "";
editText.setText(initialValue);
editText.setSelection(initialValue.length()); // Move cursor to end.
// Create custom dialog.
String neutralButtonText = (setting != null) ? str("revanced_settings_reset") : null;
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
getTitle() != null ? getTitle().toString() : "", // Title.
null, // Message is replaced by EditText.
editText, // Pass the EditText.
null, // OK button text.
() -> {
// OK button action. Persist the EditText value when OK is clicked.
String newValue = editText.getText().toString();
if (callChangeListener(newValue)) {
setText(newValue);
}
},
() -> {}, // Cancel button action (dismiss only).
neutralButtonText, // Neutral button text (Reset).
() -> {
// Neutral button action.
if (setting != null) { if (setting != null) {
builder.setNeutralButton(str("revanced_settings_reset"), null);
}
}
@Override
protected void showDialog(Bundle state) {
super.showDialog(state);
// Override the button click listener to prevent dismissing the dialog.
Button button = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEUTRAL);
if (button == null) {
return;
}
button.setOnClickListener(v -> {
try { try {
String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString(); String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString();
EditText editText = getEditText();
editText.setText(defaultStringValue); editText.setText(defaultStringValue);
editText.setSelection(defaultStringValue.length()); // move cursor to end of text editText.setSelection(defaultStringValue.length()); // Move cursor to end of text.
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "reset failure", ex); Logger.printException(() -> "reset failure", ex);
} }
}); }
},
false // Do not dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
} catch (Exception ex) {
Logger.printException(() -> "showDialog failure", ex);
}
} }
} }

View File

@ -1,7 +1,6 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import android.content.Context; import android.content.Context;
import android.preference.ListPreference;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair; import android.util.Pair;
@ -24,12 +23,14 @@ import app.revanced.extension.shared.Utils;
* it needs to subclass this preference and override {@link #getFirstEntriesToPreserve}. * it needs to subclass this preference and override {@link #getFirstEntriesToPreserve}.
*/ */
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public class SortedListPreference extends ListPreference { public class SortedListPreference extends CustomDialogListPreference {
/** /**
* Sorts the current list entries. * Sorts the current list entries.
* *
* @param firstEntriesToPreserve The number of entries to preserve in their original position. * @param firstEntriesToPreserve The number of entries to preserve in their original position,
* or a negative value to not sort and leave entries
* as they current are.
*/ */
public void sortEntryAndValues(int firstEntriesToPreserve) { public void sortEntryAndValues(int firstEntriesToPreserve) {
CharSequence[] entries = getEntries(); CharSequence[] entries = getEntries();
@ -44,6 +45,10 @@ public class SortedListPreference extends ListPreference {
throw new IllegalStateException(); throw new IllegalStateException();
} }
if (firstEntriesToPreserve < 0) {
return; // Nothing to do.
}
List<Pair<CharSequence, CharSequence>> firstEntries = new ArrayList<>(firstEntriesToPreserve); List<Pair<CharSequence, CharSequence>> firstEntries = new ArrayList<>(firstEntriesToPreserve);
// Android does not have a triple class like Kotlin, So instead use a nested pair. // Android does not have a triple class like Kotlin, So instead use a nested pair.
@ -85,10 +90,6 @@ public class SortedListPreference extends ListPreference {
super.setEntryValues(sortedEntryValues); super.setEntryValues(sortedEntryValues);
} }
protected int getFirstEntriesToPreserve() {
return 1;
}
public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
@ -112,4 +113,12 @@ public class SortedListPreference extends ListPreference {
sortEntryAndValues(getFirstEntriesToPreserve()); sortEntryAndValues(getFirstEntriesToPreserve());
} }
/**
* @return The number of first entries to leave exactly where they are, and do not sort them.
* A negative value indicates do not sort any entries.
*/
protected int getFirstEntriesToPreserve() {
return 1;
}
} }

View File

@ -1,179 +0,0 @@
package app.revanced.extension.youtube;
import static app.revanced.extension.shared.Utils.clamp;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.text.style.ReplacementSpan;
import android.text.TextPaint;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
public class ThemeHelper {
@Nullable
private static Integer darkThemeColor, lightThemeColor;
private static int themeValue;
/**
* Injection point.
*/
@SuppressWarnings("unused")
public static void setTheme(Enum<?> value) {
final int newOrdinalValue = value.ordinal();
if (themeValue != newOrdinalValue) {
themeValue = newOrdinalValue;
Logger.printDebug(() -> "Theme value: " + newOrdinalValue);
}
}
public static boolean isDarkTheme() {
return themeValue == 1;
}
public static void setActivityTheme(Activity activity) {
final var theme = isDarkTheme()
? "Theme.YouTube.Settings.Dark"
: "Theme.YouTube.Settings";
activity.setTheme(Utils.getResourceIdentifier(theme, "style"));
}
/**
* Injection point.
*/
@SuppressWarnings("SameReturnValue")
private static String darkThemeResourceName() {
// Value is changed by Theme patch, if included.
return "@color/yt_black3";
}
private static int getThemeColor(String resourceName, int defaultColor) {
try {
return Utils.getColorFromString(resourceName);
} catch (Exception ex) {
// User entered an invalid custom theme color.
// Normally this should never be reached, and no localized strings are needed.
Utils.showToastLong("Invalid custom theme color: " + resourceName);
return defaultColor;
}
}
/**
* @return The dark theme color as specified by the Theme patch (if included),
* or the dark mode background color unpatched YT uses.
*/
public static int getDarkThemeColor() {
if (darkThemeColor == null) {
darkThemeColor = getThemeColor(darkThemeResourceName(), Color.BLACK);
}
return darkThemeColor;
}
/**
* Injection point.
*/
@SuppressWarnings("SameReturnValue")
private static String lightThemeResourceName() {
// Value is changed by Theme patch, if included.
return "@color/yt_white1";
}
/**
* @return The light theme color as specified by the Theme patch (if included),
* or the non dark mode background color unpatched YT uses.
*/
public static int getLightThemeColor() {
if (lightThemeColor == null) {
lightThemeColor = getThemeColor(lightThemeResourceName(), Color.WHITE);
}
return lightThemeColor;
}
public static int getBackgroundColor() {
return isDarkTheme() ? getDarkThemeColor() : getLightThemeColor();
}
public static int getForegroundColor() {
return isDarkTheme() ? getLightThemeColor() : getDarkThemeColor();
}
public static int getDialogBackgroundColor() {
final String colorName = isDarkTheme()
? "yt_black1"
: "yt_white1";
return Utils.getColorFromString(colorName);
}
public static int getToolbarBackgroundColor() {
final String colorName = isDarkTheme()
? "yt_black3"
: "yt_white1";
return Utils.getColorFromString(colorName);
}
/**
* Sets the system navigation bar color for the activity.
* Applies the background color obtained from {@link #getBackgroundColor()} 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(getBackgroundColor());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(true);
}
}
/**
* Adjusts the brightness of a color by lightening or darkening it based on the given factor.
* <p>
* If the factor is greater than 1, the color is lightened by interpolating toward white (#FFFFFF).
* If the factor is less than or equal to 1, the color is darkened by scaling its RGB components toward black (#000000).
* The alpha channel remains unchanged.
*
* @param color The input color to adjust, in ARGB format.
* @param factor The adjustment factor. Use values > 1.0f to lighten (e.g., 1.11f for slight lightening)
* or values <= 1.0f to darken (e.g., 0.95f for slight darkening).
* @return The adjusted color in ARGB format.
*/
public static int adjustColorBrightness(int color, float factor) {
final int alpha = Color.alpha(color);
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
if (factor > 1.0f) {
// Lighten: Interpolate toward white (255)
final float t = 1.0f - (1.0f / factor); // Interpolation parameter
red = Math.round(red + (255 - red) * t);
green = Math.round(green + (255 - green) * t);
blue = Math.round(blue + (255 - blue) * t);
} else {
// Darken or no change: Scale toward black
red = (int) (red * factor);
green = (int) (green * factor);
blue = (int) (blue * factor);
}
// Ensure values are within [0, 255]
red = clamp(red, 0, 255);
green = clamp(green, 0, 255);
blue = clamp(blue, 0, 255);
return Color.argb(alpha, red, green, blue);
}
}

View File

@ -3,7 +3,10 @@ package app.revanced.extension.youtube.patches;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog;
import android.text.Html; import android.text.Html;
import android.util.Pair;
import android.widget.LinearLayout;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@ -63,18 +66,28 @@ public class CheckWatchHistoryDomainNameResolutionPatch {
} }
Utils.runOnMainThread(() -> { Utils.runOnMainThread(() -> {
var alert = new android.app.AlertDialog.Builder(context) try {
.setTitle(str("revanced_check_watch_history_domain_name_dialog_title")) // Create the custom dialog.
.setMessage(Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message"))) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setIconAttribute(android.R.attr.alertDialogIcon) context,
.setPositiveButton(android.R.string.ok, (dialog, which) -> { str("revanced_check_watch_history_domain_name_dialog_title"), // Title.
dialog.dismiss(); Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message")), // Message (HTML).
}).setNegativeButton(str("revanced_check_watch_history_domain_name_dialog_ignore"), (dialog, which) -> { null, // No EditText.
Settings.CHECK_WATCH_HISTORY_DOMAIN_NAME.save(false); null, // OK button text.
dialog.dismiss(); () -> {}, // OK button action (just dismiss).
}).create(); () -> {}, // Cancel button action (just dismiss).
str("revanced_check_watch_history_domain_name_dialog_ignore"), // Neutral button text.
() -> Settings.CHECK_WATCH_HISTORY_DOMAIN_NAME.save(false), // Neutral button action (Ignore).
true // Dismiss dialog on Neutral button click.
);
Utils.showDialog(context, alert, false, null); // Show the dialog.
Dialog dialog = dialogPair.first;
Utils.showDialog(context, dialog, false, null);
} catch (Exception ex) {
Logger.printException(() -> "checkDnsResolver dialog creation failure", ex);
}
}); });
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "checkDnsResolver failure", ex); Logger.printException(() -> "checkDnsResolver failure", ex);

View File

@ -2,13 +2,16 @@ package app.revanced.extension.youtube.patches.announcements;
import static android.text.Html.FROM_HTML_MODE_COMPACT; import static android.text.Html.FROM_HTML_MODE_COMPACT;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENTS; import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENTS;
import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENT_IDS; import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENT_IDS;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.Dialog;
import android.text.Html; import android.text.Html;
import android.text.method.LinkMovementMethod; import android.util.Pair;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.json.JSONArray; import org.json.JSONArray;
@ -120,25 +123,38 @@ public final class AnnouncementsPatch {
final Level finalLevel = level; final Level finalLevel = level;
Utils.runOnMainThread(() -> { Utils.runOnMainThread(() -> {
// Show the announcement. // Create the custom dialog and show the announcement.
var alert = new AlertDialog.Builder(context) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setTitle(finalTitle) context,
.setMessage(finalMessage) finalTitle, // Title.
.setIcon(finalLevel.icon) finalMessage, // Message.
.setPositiveButton(android.R.string.ok, (dialog, which) -> { null, // No EditText.
Settings.ANNOUNCEMENT_LAST_ID.save(finalId); null, // OK button text.
dialog.dismiss(); () -> Settings.ANNOUNCEMENT_LAST_ID.save(finalId), // OK button action.
}).setNegativeButton(str("revanced_announcements_dialog_dismiss"), (dialog, which) -> { () -> {}, // Cancel button action (dismiss only).
dialog.dismiss(); str("revanced_announcements_dialog_dismiss"), // Neutral button text.
}) () -> {}, // Neutral button action (dismiss only).
.setCancelable(false) true // Dismiss dialog when onNeutralClick.
.create(); );
Utils.showDialog(context, alert, false, (AlertDialog dialog) -> { Dialog dialog = dialogPair.first;
// Make links clickable. LinearLayout mainLayout = dialogPair.second;
((TextView) dialog.findViewById(android.R.id.message))
.setMovementMethod(LinkMovementMethod.getInstance()); // Set the icon for the title TextView
}); for (int i = 0, childCould = mainLayout.getChildCount(); i < childCould; i++) {
View child = mainLayout.getChildAt(i);
if (child instanceof TextView childTextView && finalTitle.equals(childTextView.getText().toString())) {
childTextView.setCompoundDrawablesWithIntrinsicBounds(
finalLevel.icon, 0, 0, 0);
childTextView.setCompoundDrawablePadding(dipToPixels(8));
}
}
// Set dialog as non-cancelable.
dialog.setCancelable(false);
// Show the dialog.
Utils.showDialog(context, dialog);
}); });
} catch (Exception e) { } catch (Exception e) {
final var message = "Failed to get announcement"; final var message = "Failed to get announcement";

View File

@ -6,7 +6,7 @@ import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQual
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
/** /**
* Abuse LithoFilter for {@link AdvancedVideoQualityMenuPatch}. * LithoFilter for {@link AdvancedVideoQualityMenuPatch}.
*/ */
public final class AdvancedVideoQualityMenuFilter extends Filter { public final class AdvancedVideoQualityMenuFilter extends Filter {
// Must be volatile or synchronized, as litho filtering runs off main thread // Must be volatile or synchronized, as litho filtering runs off main thread

View File

@ -13,8 +13,6 @@ import app.revanced.extension.youtube.settings.Settings;
/** /**
* This patch contains the logic to always open the advanced video quality menu. * This patch contains the logic to always open the advanced video quality menu.
* Two methods are required, because the quality menu is a RecyclerView in the new YouTube version
* and a ListView in the old one.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class AdvancedVideoQualityMenuPatch { public final class AdvancedVideoQualityMenuPatch {
@ -76,7 +74,7 @@ public final class AdvancedVideoQualityMenuPatch {
/** /**
* Injection point. * Injection point.
* *
* Used if spoofing to an old app version, and also used for the Shorts video quality flyout. * Shorts video quality flyout.
*/ */
public static void showAdvancedVideoQualityMenu(ListView listView) { public static void showAdvancedVideoQualityMenu(ListView listView) {
if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return; if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return;
@ -90,14 +88,12 @@ public final class AdvancedVideoQualityMenuPatch {
final var indexOfAdvancedQualityMenuItem = 4; final var indexOfAdvancedQualityMenuItem = 4;
if (listView.indexOfChild(child) != indexOfAdvancedQualityMenuItem) return; if (listView.indexOfChild(child) != indexOfAdvancedQualityMenuItem) return;
Logger.printDebug(() -> "Found advanced menu item in old type of quality menu");
listView.setSoundEffectsEnabled(false); listView.setSoundEffectsEnabled(false);
final var qualityItemMenuPosition = 4; final var qualityItemMenuPosition = 4;
listView.performItemClick(null, qualityItemMenuPosition, 0); listView.performItemClick(null, qualityItemMenuPosition, 0);
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "showOldVideoQualityMenu failure", ex); Logger.printException(() -> "showAdvancedVideoQualityMenu failure", ex);
} }
} }

View File

@ -19,13 +19,15 @@ import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape; import android.graphics.drawable.shapes.RoundRectShape;
import android.icu.text.NumberFormat; import android.icu.text.NumberFormat;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.animation.Animation;
import android.view.Gravity; import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewParent; import android.view.ViewParent;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.Button; import android.widget.Button;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.GridLayout; import android.widget.GridLayout;
@ -39,7 +41,6 @@ import java.util.function.Function;
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.youtube.ThemeHelper;
import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilterPatch; import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilterPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@ -240,6 +241,9 @@ public class CustomPlaybackSpeedPatch {
// Store the dialog reference. // Store the dialog reference.
currentDialog = new WeakReference<>(dialog); currentDialog = new WeakReference<>(dialog);
// Enable dismissing the dialog when tapping outside.
dialog.setCanceledOnTouchOutside(true);
// Create main vertical LinearLayout for dialog content. // Create main vertical LinearLayout for dialog content.
LinearLayout mainLayout = new LinearLayout(context); LinearLayout mainLayout = new LinearLayout(context);
mainLayout.setOrientation(LinearLayout.VERTICAL); mainLayout.setOrientation(LinearLayout.VERTICAL);
@ -259,15 +263,15 @@ public class CustomPlaybackSpeedPatch {
// Set rounded rectangle background for the main layout. // Set rounded rectangle background for the main layout.
RoundRectShape roundRectShape = new RoundRectShape( RoundRectShape roundRectShape = new RoundRectShape(
createCornerRadii(12), null, null); Utils.createCornerRadii(12), null, null);
ShapeDrawable background = new ShapeDrawable(roundRectShape); ShapeDrawable background = new ShapeDrawable(roundRectShape);
background.getPaint().setColor(ThemeHelper.getDialogBackgroundColor()); background.getPaint().setColor(Utils.getDialogBackgroundColor());
mainLayout.setBackground(background); mainLayout.setBackground(background);
// Add handle bar at the top. // Add handle bar at the top.
View handleBar = new View(context); View handleBar = new View(context);
ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape( ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(4), null, null)); Utils.createCornerRadii(4), null, null));
handleBackground.getPaint().setColor(getAdjustedBackgroundColor(true)); handleBackground.getPaint().setColor(getAdjustedBackgroundColor(true));
handleBar.setBackground(handleBackground); handleBar.setBackground(handleBackground);
LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(
@ -285,7 +289,7 @@ public class CustomPlaybackSpeedPatch {
float currentSpeed = VideoInformation.getPlaybackSpeed(); float currentSpeed = VideoInformation.getPlaybackSpeed();
// Initially show with only 0 minimum digits, so 1.0 shows as 1x // Initially show with only 0 minimum digits, so 1.0 shows as 1x
currentSpeedText.setText(formatSpeedStringX(currentSpeed, 0)); currentSpeedText.setText(formatSpeedStringX(currentSpeed, 0));
currentSpeedText.setTextColor(ThemeHelper.getForegroundColor()); currentSpeedText.setTextColor(Utils.getAppForegroundColor());
currentSpeedText.setTextSize(16); currentSpeedText.setTextSize(16);
currentSpeedText.setTypeface(Typeface.DEFAULT_BOLD); currentSpeedText.setTypeface(Typeface.DEFAULT_BOLD);
currentSpeedText.setGravity(Gravity.CENTER); currentSpeedText.setGravity(Gravity.CENTER);
@ -305,7 +309,8 @@ public class CustomPlaybackSpeedPatch {
// Create minus button. // Create minus button.
Button minusButton = new Button(context, null, 0); // Disable default theme style. Button minusButton = new Button(context, null, 0); // Disable default theme style.
minusButton.setText(""); // No text on button. minusButton.setText(""); // No text on button.
ShapeDrawable minusBackground = new ShapeDrawable(new RoundRectShape(createCornerRadii(20), null, null)); ShapeDrawable minusBackground = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(20), null, null));
minusBackground.getPaint().setColor(getAdjustedBackgroundColor(false)); minusBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
minusButton.setBackground(minusBackground); minusButton.setBackground(minusBackground);
OutlineSymbolDrawable minusDrawable = new OutlineSymbolDrawable(false); // Minus symbol. OutlineSymbolDrawable minusDrawable = new OutlineSymbolDrawable(false); // Minus symbol.
@ -319,9 +324,9 @@ public class CustomPlaybackSpeedPatch {
speedSlider.setMax(speedToProgressValue(customPlaybackSpeedsMax)); speedSlider.setMax(speedToProgressValue(customPlaybackSpeedsMax));
speedSlider.setProgress(speedToProgressValue(currentSpeed)); speedSlider.setProgress(speedToProgressValue(currentSpeed));
speedSlider.getProgressDrawable().setColorFilter( speedSlider.getProgressDrawable().setColorFilter(
ThemeHelper.getForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme progress bar. Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme progress bar.
speedSlider.getThumb().setColorFilter( speedSlider.getThumb().setColorFilter(
ThemeHelper.getForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme slider thumb. Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme slider thumb.
LinearLayout.LayoutParams sliderParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams sliderParams = new LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f); 0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f);
sliderParams.setMargins(dip5, 0, dip5, 0); // 5dp to -/+ buttons. sliderParams.setMargins(dip5, 0, dip5, 0); // 5dp to -/+ buttons.
@ -331,7 +336,7 @@ public class CustomPlaybackSpeedPatch {
Button plusButton = new Button(context, null, 0); // Disable default theme style. Button plusButton = new Button(context, null, 0); // Disable default theme style.
plusButton.setText(""); // No text on button. plusButton.setText(""); // No text on button.
ShapeDrawable plusBackground = new ShapeDrawable(new RoundRectShape( ShapeDrawable plusBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(20), null, null)); Utils.createCornerRadii(20), null, null));
plusBackground.getPaint().setColor(getAdjustedBackgroundColor(false)); plusBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
plusButton.setBackground(plusBackground); plusButton.setBackground(plusBackground);
OutlineSymbolDrawable plusDrawable = new OutlineSymbolDrawable(true); // Plus symbol. OutlineSymbolDrawable plusDrawable = new OutlineSymbolDrawable(true); // Plus symbol.
@ -418,13 +423,13 @@ public class CustomPlaybackSpeedPatch {
// Create speed button. // Create speed button.
Button speedButton = new Button(context, null, 0); Button speedButton = new Button(context, null, 0);
speedButton.setText(speedFormatter.format(speed)); // Do not use 'x' speed format. speedButton.setText(speedFormatter.format(speed)); // Do not use 'x' speed format.
speedButton.setTextColor(ThemeHelper.getForegroundColor()); speedButton.setTextColor(Utils.getAppForegroundColor());
speedButton.setTextSize(12); speedButton.setTextSize(12);
speedButton.setAllCaps(false); speedButton.setAllCaps(false);
speedButton.setGravity(Gravity.CENTER); speedButton.setGravity(Gravity.CENTER);
ShapeDrawable buttonBackground = new ShapeDrawable(new RoundRectShape( ShapeDrawable buttonBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(20), null, null)); Utils.createCornerRadii(20), null, null));
buttonBackground.getPaint().setColor(getAdjustedBackgroundColor(false)); buttonBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
speedButton.setBackground(buttonBackground); speedButton.setBackground(buttonBackground);
speedButton.setPadding(dip5, dip5, dip5, dip5); speedButton.setPadding(dip5, dip5, dip5, dip5);
@ -442,7 +447,7 @@ public class CustomPlaybackSpeedPatch {
TextView normalLabel = new TextView(context); TextView normalLabel = new TextView(context);
// Use same 'Normal' string as stock YouTube. // Use same 'Normal' string as stock YouTube.
normalLabel.setText(str("normal_playback_rate_label")); normalLabel.setText(str("normal_playback_rate_label"));
normalLabel.setTextColor(ThemeHelper.getForegroundColor()); normalLabel.setTextColor(Utils.getAppForegroundColor());
normalLabel.setTextSize(10); normalLabel.setTextSize(10);
normalLabel.setGravity(Gravity.CENTER); normalLabel.setGravity(Gravity.CENTER);
@ -489,6 +494,77 @@ public class CustomPlaybackSpeedPatch {
window.setBackgroundDrawable(null); // Remove default dialog background. window.setBackgroundDrawable(null); // Remove default dialog background.
} }
// Apply slide-in animation when showing the dialog.
final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast");
Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom");
slideInABottomAnimation.setDuration(fadeDurationFast);
mainLayout.startAnimation(slideInABottomAnimation);
// Set touch listener on mainLayout to enable drag-to-dismiss.
//noinspection ClickableViewAccessibility
mainLayout.setOnTouchListener(new View.OnTouchListener() {
/** Threshold for dismissing the dialog. */
final float dismissThreshold = dipToPixels(100); // Distance to drag to dismiss.
/** Store initial Y position of touch. */
float touchY;
/** Track current translation. */
float translationY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Capture initial Y position of touch.
touchY = event.getRawY();
translationY = mainLayout.getTranslationY();
return true;
case MotionEvent.ACTION_MOVE:
// Calculate drag distance and apply translation downwards only.
final float deltaY = event.getRawY() - touchY;
// Only allow downward drag (positive deltaY).
if (deltaY >= 0) {
mainLayout.setTranslationY(translationY + deltaY);
}
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// Check if dialog should be dismissed based on drag distance.
if (mainLayout.getTranslationY() > dismissThreshold) {
// Animate dialog off-screen and dismiss.
//noinspection ExtractMethodRecommender
final float remainingDistance = context.getResources().getDisplayMetrics().heightPixels
- mainLayout.getTop();
TranslateAnimation slideOut = new TranslateAnimation(
0, 0, mainLayout.getTranslationY(), remainingDistance);
slideOut.setDuration(fadeDurationFast);
slideOut.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
dialog.dismiss();
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
mainLayout.startAnimation(slideOut);
} else {
// Animate back to original position if not dragged far enough.
TranslateAnimation slideBack = new TranslateAnimation(
0, 0, mainLayout.getTranslationY(), 0);
slideBack.setDuration(fadeDurationFast);
mainLayout.startAnimation(slideBack);
mainLayout.setTranslationY(0);
}
return true;
default:
return false;
}
}
});
// Create observer for PlayerType changes. // Create observer for PlayerType changes.
Function1<PlayerType, Unit> playerTypeObserver = new Function1<>() { Function1<PlayerType, Unit> playerTypeObserver = new Function1<>() {
@Override @Override
@ -515,27 +591,9 @@ public class CustomPlaybackSpeedPatch {
Logger.printDebug(() -> "PlayerType observer removed on dialog dismiss"); Logger.printDebug(() -> "PlayerType observer removed on dialog dismiss");
}); });
// Apply slide-in animation when showing the dialog.
final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast");
Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom");
slideInABottomAnimation.setDuration(fadeDurationFast);
mainLayout.startAnimation(slideInABottomAnimation);
dialog.show(); // Display the dialog. dialog.show(); // Display the dialog.
} }
/**
* Creates an array of corner radii for a rounded rectangle shape.
*
* @param dp The radius in density-independent pixels (dp) to apply to all corners.
* @return An array of eight float values representing the corner radii
* (top-left, top-right, bottom-right, bottom-left).
*/
private static float[] createCornerRadii(float dp) {
final float radius = dipToPixels(dp);
return new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
}
/** /**
* @param speed The playback speed value to format. * @param speed The playback speed value to format.
* @return A string representation of the speed with 'x' (e.g. "1.25x" or "1.00x"). * @return A string representation of the speed with 'x' (e.g. "1.25x" or "1.00x").
@ -573,12 +631,12 @@ public class CustomPlaybackSpeedPatch {
* for light themes to ensure visual contrast. * for light themes to ensure visual contrast.
*/ */
public static int getAdjustedBackgroundColor(boolean isHandleBar) { public static int getAdjustedBackgroundColor(boolean isHandleBar) {
final int baseColor = ThemeHelper.getDialogBackgroundColor(); final int baseColor = Utils.getDialogBackgroundColor();
float darkThemeFactor = isHandleBar ? 1.25f : 1.115f; // 1.25f for handleBar, 1.115f for others in dark theme. float darkThemeFactor = isHandleBar ? 1.25f : 1.115f; // 1.25f for handleBar, 1.115f for others in dark theme.
float lightThemeFactor = isHandleBar ? 0.9f : 0.95f; // 0.9f for handleBar, 0.95f for others in light theme. float lightThemeFactor = isHandleBar ? 0.9f : 0.95f; // 0.9f for handleBar, 0.95f for others in light theme.
return ThemeHelper.isDarkTheme() return Utils.isDarkModeEnabled()
? ThemeHelper.adjustColorBrightness(baseColor, darkThemeFactor) // Lighten for dark theme. ? Utils.adjustColorBrightness(baseColor, darkThemeFactor) // Lighten for dark theme.
: ThemeHelper.adjustColorBrightness(baseColor, lightThemeFactor); // Darken for light theme. : Utils.adjustColorBrightness(baseColor, lightThemeFactor); // Darken for light theme.
} }
} }
@ -592,7 +650,7 @@ class OutlineSymbolDrawable extends Drawable {
OutlineSymbolDrawable(boolean isPlus) { OutlineSymbolDrawable(boolean isPlus) {
this.isPlus = isPlus; this.isPlus = isPlus;
paint = new Paint(Paint.ANTI_ALIAS_FLAG); // Enable anti-aliasing for smooth rendering. paint = new Paint(Paint.ANTI_ALIAS_FLAG); // Enable anti-aliasing for smooth rendering.
paint.setColor(ThemeHelper.getForegroundColor()); paint.setColor(Utils.getAppForegroundColor());
paint.setStyle(Paint.Style.STROKE); // Use stroke style for outline. paint.setStyle(Paint.Style.STROKE); // Use stroke style for outline.
paint.setStrokeWidth(dipToPixels(1)); // 1dp stroke width. paint.setStrokeWidth(dipToPixels(1)); // 1dp stroke width.
} }

View File

@ -6,7 +6,6 @@ import androidx.annotation.Nullable;
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.youtube.ThemeHelper;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -75,7 +74,7 @@ public class ThemePatch {
* @return The new or original color value * @return The new or original color value
*/ */
public static int getValue(int originalValue) { public static int getValue(int originalValue) {
if (ThemeHelper.isDarkTheme()) { if (Utils.isDarkModeEnabled()) {
if (anyEquals(originalValue, DARK_VALUES)) return BLACK_COLOR; if (anyEquals(originalValue, DARK_VALUES)) return BLACK_COLOR;
} else { } else {
if (anyEquals(originalValue, WHITE_VALUES)) return WHITE_COLOR; if (anyEquals(originalValue, WHITE_VALUES)) return WHITE_COLOR;

View File

@ -30,11 +30,15 @@ import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.*; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
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.youtube.ThemeHelper;
import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData; import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData;
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi; import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@ -177,7 +181,7 @@ public class ReturnYouTubeDislike {
* Ideally, this would be the actual color YT uses at runtime. * Ideally, this would be the actual color YT uses at runtime.
*/ */
private static int getSeparatorColor() { private static int getSeparatorColor() {
return ThemeHelper.isDarkTheme() return Utils.isDarkModeEnabled()
? 0x33FFFFFF ? 0x33FFFFFF
: 0xFFD9D9D9; : 0xFFD9D9D9;
} }

View File

@ -14,7 +14,6 @@ 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;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.patches.VersionCheckPatch; import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch; import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment; import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
@ -27,6 +26,8 @@ import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFrag
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class LicenseActivityHook { public class LicenseActivityHook {
private static int currentThemeValueOrdinal = -1; // Must initially be a non-valid enum ordinal value.
private static ViewGroup.LayoutParams toolbarLayoutParams; private static ViewGroup.LayoutParams toolbarLayoutParams;
public static void setToolbarLayoutParams(Toolbar toolbar) { public static void setToolbarLayoutParams(Toolbar toolbar) {
@ -78,8 +79,8 @@ public class LicenseActivityHook {
*/ */
public static void initialize(Activity licenseActivity) { public static void initialize(Activity licenseActivity) {
try { try {
ThemeHelper.setActivityTheme(licenseActivity); setActivityTheme(licenseActivity);
ThemeHelper.setNavigationBarColor(licenseActivity.getWindow()); ReVancedPreferenceFragment.setNavigationBarColor(licenseActivity.getWindow());
licenseActivity.setContentView(getResourceIdentifier( licenseActivity.setContentView(getResourceIdentifier(
"revanced_settings_with_toolbar", "layout")); "revanced_settings_with_toolbar", "layout"));
@ -114,7 +115,7 @@ public class LicenseActivityHook {
toolBarParent.removeView(dummyToolbar); toolBarParent.removeView(dummyToolbar);
Toolbar toolbar = new Toolbar(toolBarParent.getContext()); Toolbar toolbar = new Toolbar(toolBarParent.getContext());
toolbar.setBackgroundColor(ThemeHelper.getToolbarBackgroundColor()); toolbar.setBackgroundColor(getToolbarBackgroundColor());
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable()); toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string")); toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string"));
@ -124,7 +125,7 @@ public class LicenseActivityHook {
TextView toolbarTextView = Utils.getChildView(toolbar, false, TextView toolbarTextView = Utils.getChildView(toolbar, false,
view -> view instanceof TextView); view -> view instanceof TextView);
if (toolbarTextView != null) { if (toolbarTextView != null) {
toolbarTextView.setTextColor(ThemeHelper.getForegroundColor()); toolbarTextView.setTextColor(Utils.getAppForegroundColor());
} }
setToolbarLayoutParams(toolbar); setToolbarLayoutParams(toolbar);
@ -135,4 +136,34 @@ public class LicenseActivityHook {
toolBarParent.addView(toolbar, 0); 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.
*/
@SuppressWarnings("unused")
public static void updateLightDarkModeStatus(Enum<?> value) {
final int themeOrdinal = value.ordinal();
if (currentThemeValueOrdinal != themeOrdinal) {
currentThemeValueOrdinal = themeOrdinal;
Utils.setIsDarkModeEnabled(themeOrdinal == 1);
}
}
} }

View File

@ -4,9 +4,10 @@ import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier; import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.GradientDrawable;
import android.util.Pair;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
@ -31,7 +32,6 @@ import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage; import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.StringSetting; import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment; import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
/** /**
@ -58,10 +58,10 @@ public class SearchViewController {
GradientDrawable background = new GradientDrawable(); GradientDrawable background = new GradientDrawable();
background.setShape(GradientDrawable.RECTANGLE); background.setShape(GradientDrawable.RECTANGLE);
background.setCornerRadius(28 * context.getResources().getDisplayMetrics().density); // 28dp corner radius. background.setCornerRadius(28 * context.getResources().getDisplayMetrics().density); // 28dp corner radius.
int baseColor = ThemeHelper.getBackgroundColor(); int baseColor = Utils.getAppBackgroundColor();
int adjustedColor = ThemeHelper.isDarkTheme() int adjustedColor = Utils.isDarkModeEnabled()
? ThemeHelper.adjustColorBrightness(baseColor, 1.11f) // Lighten for dark theme. ? Utils.adjustColorBrightness(baseColor, 1.11f) // Lighten for dark theme.
: ThemeHelper.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme. : Utils.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme.
background.setColor(adjustedColor); background.setColor(adjustedColor);
return background; return background;
} }
@ -171,10 +171,6 @@ public class SearchViewController {
final int actionSearchId = getResourceIdentifier("action_search", "id"); final int actionSearchId = getResourceIdentifier("action_search", "id");
toolbar.inflateMenu(getResourceIdentifier("revanced_search_menu", "menu")); toolbar.inflateMenu(getResourceIdentifier("revanced_search_menu", "menu"));
MenuItem searchItem = toolbar.getMenu().findItem(actionSearchId); MenuItem searchItem = toolbar.getMenu().findItem(actionSearchId);
searchItem.setIcon(getResourceIdentifier(ThemeHelper.isDarkTheme()
? "yt_outline_search_white_24"
: "yt_outline_search_black_24",
"drawable")).setTooltipText(null);
// Set menu item click listener. // Set menu item click listener.
toolbar.setOnMenuItemClickListener(item -> { toolbar.setOnMenuItemClickListener(item -> {
@ -316,12 +312,7 @@ public class SearchViewController {
private void closeSearch() { private void closeSearch() {
isSearchActive = false; isSearchActive = false;
toolbar.getMenu().findItem(getResourceIdentifier( toolbar.getMenu().findItem(getResourceIdentifier(
"action_search", "id")) "action_search", "id")).setVisible(true);
.setIcon(getResourceIdentifier(ThemeHelper.isDarkTheme()
? "yt_outline_search_white_24"
: "yt_outline_search_black_24",
"drawable")
).setVisible(true);
toolbar.setTitle(originalTitle); toolbar.setTitle(originalTitle);
searchContainer.setVisibility(View.GONE); searchContainer.setVisibility(View.GONE);
searchView.setQuery("", false); searchView.setQuery("", false);
@ -365,13 +356,22 @@ public class SearchViewController {
// Set long click listener for deletion confirmation. // Set long click listener for deletion confirmation.
convertView.setOnLongClickListener(v -> { convertView.setOnLongClickListener(v -> {
new AlertDialog.Builder(activity) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setTitle(query) activity,
.setMessage(str("revanced_settings_search_remove_message")) query, // Title.
.setPositiveButton(android.R.string.ok, str("revanced_settings_search_remove_message"), // Message.
(dialog, which) -> removeSearchQuery(query)) null, // No EditText.
.setNegativeButton(android.R.string.cancel, null) null, // OK button text.
.show(); () -> removeSearchQuery(query), // OK button action.
() -> {}, // Cancel button action (dismiss only).
null, // No Neutral button text.
() -> {}, // Neutral button action (dismiss only).
true // Dismiss dialog when onNeutralClick.
);
Dialog dialog = dialogPair.first;
dialog.setCancelable(true); // Allow dismissal via back button.
dialog.show(); // Show the dialog.
return true; return true;
}); });

View File

@ -3,17 +3,18 @@ package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.sf; import static app.revanced.extension.shared.StringRef.sf;
import android.content.Context; import android.content.Context;
import android.preference.ListPreference;
import android.util.AttributeSet; import android.util.AttributeSet;
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch; import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
/** /**
* A custom ListPreference that uses a styled custom dialog with a custom checkmark indicator.
* Custom video speeds used by {@link CustomPlaybackSpeedPatch}. * Custom video speeds used by {@link CustomPlaybackSpeedPatch}.
*/ */
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public final class CustomVideoSpeedListPreference extends ListPreference { public final class CustomVideoSpeedListPreference extends CustomDialogListPreference {
/** /**
* Initialize a settings preference list with the available playback speeds. * Initialize a settings preference list with the available playback speeds.
@ -59,4 +60,5 @@ public final class CustomVideoSpeedListPreference extends ListPreference {
public CustomVideoSpeedListPreference(Context context) { public CustomVideoSpeedListPreference(Context context) {
super(context); super(context);
} }
} }

View File

@ -18,6 +18,7 @@ import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.BackgroundColorSpan; import android.text.style.BackgroundColorSpan;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toolbar; import android.widget.Toolbar;
@ -40,7 +41,6 @@ import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment; import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment;
import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory; import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.settings.LicenseActivityHook; import app.revanced.extension.youtube.settings.LicenseActivityHook;
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup; import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
@ -71,11 +71,27 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
@SuppressLint("UseCompatLoadingForDrawables") @SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getBackButtonDrawable() { public static Drawable getBackButtonDrawable() {
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme() final int backButtonResource = getResourceIdentifier("revanced_settings_toolbar_arrow_left", "drawable");
? "yt_outline_arrow_left_white_24" Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource);
: "yt_outline_arrow_left_black_24", drawable.setTint(Utils.getAppForegroundColor());
"drawable"); return drawable;
return Utils.getContext().getResources().getDrawable(backButtonResource); }
/**
* 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);
}
} }
/** /**
@ -201,9 +217,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
// Set icon for the placeholder preference. // Set icon for the placeholder preference.
noResultsPreference.setLayoutResource(getResourceIdentifier( noResultsPreference.setLayoutResource(getResourceIdentifier(
"revanced_preference_with_icon_no_search_result", "layout")); "revanced_preference_with_icon_no_search_result", "layout"));
noResultsPreference.setIcon(getResourceIdentifier( noResultsPreference.setIcon(getResourceIdentifier("revanced_settings_search_icon", "drawable"));
ThemeHelper.isDarkTheme() ? "yt_outline_search_white_24" : "yt_outline_search_black_24",
"drawable"));
preferenceScreen.addPreference(noResultsPreference); preferenceScreen.addPreference(noResultsPreference);
} }
} }
@ -226,7 +240,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
.getParent(); .getParent();
// Fix the system navigation bar color for submenus. // Fix the system navigation bar color for submenus.
ThemeHelper.setNavigationBarColor(preferenceScreenDialog.getWindow()); setNavigationBarColor(preferenceScreenDialog.getWindow());
// Fix edge-to-edge screen with Android 15 and YT 19.45+ // 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 // https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets
@ -250,7 +264,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
TextView toolbarTextView = Utils.getChildView(toolbar, TextView toolbarTextView = Utils.getChildView(toolbar,
true, TextView.class::isInstance); true, TextView.class::isInstance);
if (toolbarTextView != null) { if (toolbarTextView != null) {
toolbarTextView.setTextColor(ThemeHelper.getForegroundColor()); toolbarTextView.setTextColor(Utils.getAppForegroundColor());
} }
LicenseActivityHook.setToolbarLayoutParams(toolbar); LicenseActivityHook.setToolbarLayoutParams(toolbar);
@ -304,10 +318,10 @@ class AbstractPreferenceSearchData<T extends Preference> {
return text; return text;
} }
final int baseColor = ThemeHelper.getBackgroundColor(); final int baseColor = Utils.getAppBackgroundColor();
final int adjustedColor = ThemeHelper.isDarkTheme() final int adjustedColor = Utils.isDarkModeEnabled()
? ThemeHelper.adjustColorBrightness(baseColor, 1.20f) // Lighten for dark theme. ? Utils.adjustColorBrightness(baseColor, 1.20f) // Lighten for dark theme.
: ThemeHelper.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme. : Utils.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme.
BackgroundColorSpan highlightSpan = new BackgroundColorSpan(adjustedColor); BackgroundColorSpan highlightSpan = new BackgroundColorSpan(adjustedColor);
SpannableStringBuilder spannable = new SpannableStringBuilder(text); SpannableStringBuilder spannable = new SpannableStringBuilder(text);

View File

@ -1,32 +0,0 @@
package app.revanced.extension.youtube.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
import app.revanced.extension.youtube.ThemeHelper;
@SuppressWarnings("unused")
public class ReVancedYouTubeAboutPreference extends ReVancedAboutPreference {
public int getLightColor() {
return ThemeHelper.getLightThemeColor();
}
public int getDarkColor() {
return ThemeHelper.getDarkThemeColor();
}
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReVancedYouTubeAboutPreference(Context context) {
super(context);
}
}

View File

@ -2,9 +2,11 @@ package app.revanced.extension.youtube.sponsorblock;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.util.Pair;
import android.util.Patterns; import android.util.Patterns;
import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -56,7 +58,7 @@ public class SponsorBlockSettings {
} }
} }
for (int i = 0; i < categorySelectionsArray.length(); i++) { for (int i = 0, length = categorySelectionsArray.length(); i < length; i++) {
JSONObject categorySelectionObject = categorySelectionsArray.getJSONObject(i); JSONObject categorySelectionObject = categorySelectionsArray.getJSONObject(i);
String categoryKey = categorySelectionObject.getString("name"); String categoryKey = categorySelectionObject.getString("name");
@ -181,13 +183,25 @@ public class SponsorBlockSettings {
// If user has a SponsorBlock user id then show a warning. // If user has a SponsorBlock user id then show a warning.
if (dialogContext != null && SponsorBlockSettings.userHasSBPrivateId() if (dialogContext != null && SponsorBlockSettings.userHasSBPrivateId()
&& !Settings.SB_HIDE_EXPORT_WARNING.get()) { && !Settings.SB_HIDE_EXPORT_WARNING.get()) {
new AlertDialog.Builder(dialogContext) // Create the custom dialog.
.setMessage(str("revanced_sb_settings_revanced_export_user_id_warning")) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setNeutralButton(str("revanced_sb_settings_revanced_export_user_id_warning_dismiss"), dialogContext,
(dialog, which) -> Settings.SB_HIDE_EXPORT_WARNING.save(true)) null, // No title.
.setPositiveButton(android.R.string.ok, null) str("revanced_sb_settings_revanced_export_user_id_warning"), // Message.
.setCancelable(false) null, // No EditText.
.show(); null, // OK button text.
() -> {}, // OK button action (dismiss only).
null, // No cancel button action.
str("revanced_sb_settings_revanced_export_user_id_warning_dismiss"), // Neutral button text.
() -> Settings.SB_HIDE_EXPORT_WARNING.save(true), // Neutral button action.
true // Dismiss dialog when onNeutralClick.
);
// Set dialog as non-cancelable.
dialogPair.first.setCancelable(false);
// Show the dialog.
dialogPair.first.show();
} }
} }

View File

@ -2,12 +2,12 @@ package app.revanced.extension.youtube.sponsorblock.objects;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier; import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import static app.revanced.extension.shared.Utils.dipToPixels;
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString; import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString;
import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor; import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.Bundle; import android.os.Bundle;
@ -15,12 +15,14 @@ import android.preference.ListPreference;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Pair;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.GridLayout; import android.widget.GridLayout;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
@ -52,6 +54,7 @@ public class SegmentCategoryListPreference extends ListPreference {
private EditText dialogColorEditText; private EditText dialogColorEditText;
private EditText dialogOpacityEditText; private EditText dialogOpacityEditText;
private ColorPickerView dialogColorPickerView; private ColorPickerView dialogColorPickerView;
private Dialog dialog;
public SegmentCategoryListPreference(Context context, SegmentCategory category) { public SegmentCategoryListPreference(Context context, SegmentCategory category) {
super(context); super(context);
@ -75,30 +78,47 @@ public class SegmentCategoryListPreference extends ListPreference {
} }
@Override @Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { protected void showDialog(Bundle state) {
try { try {
Utils.setEditTextDialogTheme(builder); Context context = getContext();
categoryColor = category.getColorNoOpacity(); categoryColor = category.getColorNoOpacity();
categoryOpacity = category.getOpacity(); categoryOpacity = category.getOpacity();
selectedDialogEntryIndex = findIndexOfValue(getValue());
Context context = builder.getContext(); // Create the main layout for the dialog content.
LinearLayout mainLayout = new LinearLayout(context); LinearLayout contentLayout = new LinearLayout(context);
mainLayout.setOrientation(LinearLayout.VERTICAL); contentLayout.setOrientation(LinearLayout.VERTICAL);
mainLayout.setPadding(70, 0, 70, 0); final int dip10 = dipToPixels(10);
contentLayout.setPadding(0, 0, 0, dip10);
// Add behavior selection radio buttons.
RadioGroup radioGroup = new RadioGroup(context);
radioGroup.setOrientation(RadioGroup.VERTICAL);
CharSequence[] entries = getEntries();
for (int i = 0; i < entries.length; i++) {
RadioButton radioButton = new RadioButton(context);
radioButton.setText(entries[i]);
radioButton.setId(i);
radioButton.setChecked(i == selectedDialogEntryIndex);
radioGroup.addView(radioButton);
}
radioGroup.setOnCheckedChangeListener((group, checkedId) -> selectedDialogEntryIndex = checkedId);
radioGroup.setPadding(dip10, 0, 0, 0);
contentLayout.addView(radioGroup);
// Inflate the color picker view. // Inflate the color picker view.
View colorPickerContainer = LayoutInflater.from(context) View colorPickerContainer = LayoutInflater.from(context)
.inflate(getResourceIdentifier("revanced_color_picker", "layout"), null); .inflate(getResourceIdentifier("revanced_color_picker", "layout"), null);
dialogColorPickerView = colorPickerContainer.findViewById( dialogColorPickerView = colorPickerContainer.findViewById(
getResourceIdentifier("color_picker_view", "id")); getResourceIdentifier("revanced_color_picker_view", "id"));
dialogColorPickerView.setColor(categoryColor); dialogColorPickerView.setColor(categoryColor);
mainLayout.addView(colorPickerContainer); contentLayout.addView(colorPickerContainer);
// Grid layout for color and opacity inputs. // Grid layout for color and opacity inputs.
GridLayout gridLayout = new GridLayout(context); GridLayout gridLayout = new GridLayout(context);
gridLayout.setColumnCount(3); gridLayout.setColumnCount(3);
gridLayout.setRowCount(2); gridLayout.setRowCount(2);
gridLayout.setPadding(dipToPixels(16), 0, 0, 0);
GridLayout.LayoutParams gridParams = new GridLayout.LayoutParams(); GridLayout.LayoutParams gridParams = new GridLayout.LayoutParams();
gridParams.rowSpec = GridLayout.spec(0); // First row. gridParams.rowSpec = GridLayout.spec(0); // First row.
@ -111,7 +131,7 @@ public class SegmentCategoryListPreference extends ListPreference {
gridParams = new GridLayout.LayoutParams(); gridParams = new GridLayout.LayoutParams();
gridParams.rowSpec = GridLayout.spec(0); // First row. gridParams.rowSpec = GridLayout.spec(0); // First row.
gridParams.columnSpec = GridLayout.spec(1); // Second column. gridParams.columnSpec = GridLayout.spec(1); // Second column.
gridParams.setMargins(0, 0, 10, 0); gridParams.setMargins(0, 0, dip10, 0);
dialogColorDotView = new TextView(context); dialogColorDotView = new TextView(context);
dialogColorDotView.setLayoutParams(gridParams); dialogColorDotView.setLayoutParams(gridParams);
gridLayout.addView(dialogColorDotView); gridLayout.addView(dialogColorDotView);
@ -162,8 +182,7 @@ public class SegmentCategoryListPreference extends ListPreference {
} }
} }
}); });
dialogColorEditText.setLayoutParams(gridParams); gridLayout.addView(dialogColorEditText, gridParams);
gridLayout.addView(dialogColorEditText);
gridParams = new GridLayout.LayoutParams(); gridParams = new GridLayout.LayoutParams();
gridParams.rowSpec = GridLayout.spec(1); // Second row. gridParams.rowSpec = GridLayout.spec(1); // Second row.
@ -226,11 +245,10 @@ public class SegmentCategoryListPreference extends ListPreference {
} }
} }
}); });
dialogOpacityEditText.setLayoutParams(gridParams); gridLayout.addView(dialogOpacityEditText, gridParams);
gridLayout.addView(dialogOpacityEditText);
updateOpacityText(); updateOpacityText();
mainLayout.addView(gridLayout); contentLayout.addView(gridLayout);
// Set up color picker listener. // Set up color picker listener.
// Do last to prevent listener callbacks while setting up view. // Do last to prevent listener callbacks while setting up view.
@ -247,46 +265,16 @@ public class SegmentCategoryListPreference extends ListPreference {
dialogColorEditText.setSelection(hexColor.length()); dialogColorEditText.setSelection(hexColor.length());
}); });
builder.setView(mainLayout); // Create the custom dialog.
builder.setTitle(category.title.toString()); Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { category.title.toString(), // Title.
onClick(dialog, DialogInterface.BUTTON_POSITIVE); null, // No message (replaced by contentLayout).
}); null, // No EditText.
builder.setNeutralButton(str("revanced_settings_reset_color"), null); null, // OK button text.
builder.setNegativeButton(android.R.string.cancel, null); () -> {
// OK button action.
selectedDialogEntryIndex = findIndexOfValue(getValue()); if (selectedDialogEntryIndex >= 0 && getEntryValues() != null) {
builder.setSingleChoiceItems(getEntries(), selectedDialogEntryIndex,
(dialog, which) -> selectedDialogEntryIndex = which);
} catch (Exception ex) {
Logger.printException(() -> "onPrepareDialogBuilder failure", ex);
}
}
@Override
protected void showDialog(Bundle state) {
super.showDialog(state);
// Do not close dialog when reset is pressed.
Button button = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEUTRAL);
button.setOnClickListener(view -> {
try {
// Setting view color causes callback to update the UI.
dialogColorPickerView.setColor(category.getColorNoOpacityDefault());
categoryOpacity = category.getOpacityDefault();
updateOpacityText();
} catch (Exception ex) {
Logger.printException(() -> "setOnClickListener failure", ex);
}
});
}
@Override
protected void onDialogClosed(boolean positiveResult) {
try {
if (positiveResult && selectedDialogEntryIndex >= 0 && getEntryValues() != null) {
String value = getEntryValues()[selectedDialogEntryIndex].toString(); String value = getEntryValues()[selectedDialogEntryIndex].toString();
if (callChangeListener(value)) { if (callChangeListener(value)) {
setValue(value); setValue(value);
@ -303,13 +291,48 @@ public class SegmentCategoryListPreference extends ListPreference {
updateUI(); updateUI();
} }
},
() -> {}, // Cancel button action (dismiss only).
str("revanced_settings_reset_color"), // Neutral button text.
() -> {
// Neutral button action (Reset).
try {
// Setting view color causes callback to update the UI.
dialogColorPickerView.setColor(category.getColorNoOpacityDefault());
categoryOpacity = category.getOpacityDefault();
updateOpacityText();
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "onDialogClosed failure", ex); Logger.printException(() -> "resetButton onClick failure", ex);
} finally { }
},
false // Do not dismiss dialog on Neutral button click.
);
dialog = dialogPair.first;
LinearLayout dialogMainLayout = dialogPair.second;
// Add the custom content to the dialog's main layout.
dialogMainLayout.addView(contentLayout, 1); // Add after title, before buttons.
// Show the dialog.
dialog.show();
} catch (Exception ex) {
Logger.printException(() -> "showDialog failure", ex);
}
}
@Override
protected void onDialogClosed(boolean positiveResult) {
// Nullify dialog references.
dialogColorDotView = null; dialogColorDotView = null;
dialogColorEditText = null; dialogColorEditText = null;
dialogOpacityEditText = null; dialogOpacityEditText = null;
dialogColorPickerView = null; dialogColorPickerView = null;
if (dialog != null) {
dialog.dismiss();
dialog = null;
} }
} }

View File

@ -3,19 +3,25 @@ package app.revanced.extension.youtube.sponsorblock.ui;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.preference.*; import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
import android.preference.SwitchPreference;
import android.text.Html; import android.text.Html;
import android.text.InputType; import android.text.InputType;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -279,14 +285,26 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
addNewSegment.setOnPreferenceChangeListener((preference1, o) -> { addNewSegment.setOnPreferenceChangeListener((preference1, o) -> {
Boolean newValue = (Boolean) o; Boolean newValue = (Boolean) o;
if (newValue && !Settings.SB_SEEN_GUIDELINES.get()) { if (newValue && !Settings.SB_SEEN_GUIDELINES.get()) {
new AlertDialog.Builder(preference1.getContext()) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setTitle(str("revanced_sb_guidelines_popup_title")) preference1.getContext(),
.setMessage(str("revanced_sb_guidelines_popup_content")) str("revanced_sb_guidelines_popup_title"), // Title.
.setNegativeButton(str("revanced_sb_guidelines_popup_already_read"), null) str("revanced_sb_guidelines_popup_content"), // Message.
.setPositiveButton(str("revanced_sb_guidelines_popup_open"), (dialogInterface, i) -> openGuidelines()) null, // No EditText.
.setOnDismissListener(dialog -> Settings.SB_SEEN_GUIDELINES.save(true)) str("revanced_sb_guidelines_popup_open"), // OK button text.
.setCancelable(false) () -> openGuidelines(), // OK button action.
.show(); null, // Cancel button action.
str("revanced_sb_guidelines_popup_already_read"), // Neutral button text.
() -> {}, // Neutral button action (dismiss only).
true // Dismiss dialog when onNeutralClick.
);
// Set dialog as non-cancelable.
dialogPair.first.setCancelable(false);
dialogPair.first.setOnDismissListener(dialog -> Settings.SB_SEEN_GUIDELINES.save(true));
// Show the dialog.
dialogPair.first.show();
} }
Settings.SB_CREATE_NEW_SEGMENT.save(newValue); Settings.SB_CREATE_NEW_SEGMENT.save(newValue);
updateUI(); updateUI();
@ -372,16 +390,52 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
generalCategory.addPreference(minSegmentDuration); generalCategory.addPreference(minSegmentDuration);
privateUserId = new EditTextPreference(context) { privateUserId = new EditTextPreference(context) {
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { @Override
Utils.setEditTextDialogTheme(builder); protected void showDialog(Bundle state) {
try {
Context context = getContext();
EditText editText = getEditText();
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> { // Set initial EditText value to the current persisted value or empty string.
String initialValue = getText() != null ? getText() : "";
editText.setText(initialValue);
editText.setSelection(initialValue.length()); // Move cursor to end.
// Create custom dialog.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
getTitle() != null ? getTitle().toString() : "", // Title.
null, // Message is replaced by EditText.
editText, // Pass the EditText.
null, // OK button text.
() -> {
// OK button action. Persist the EditText value when OK is clicked.
String newValue = editText.getText().toString();
if (callChangeListener(newValue)) {
setText(newValue);
}
},
() -> {}, // Cancel button action (dismiss only).
str("revanced_sb_settings_copy"), // Neutral button text (Copy).
() -> {
// Neutral button action (Copy).
try { try {
Utils.setClipboard(getEditText().getText()); Utils.setClipboard(getEditText().getText());
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "Copy settings failure", ex); Logger.printException(() -> "Copy settings failure", ex);
} }
}); },
true // Dismiss dialog when onNeutralClick.
);
// Set dialog as cancelable.
dialogPair.first.setCancelable(true);
// Show the dialog.
dialogPair.first.show();
} catch (Exception ex) {
Logger.printException(() -> "showDialog failure", ex);
}
} }
}; };
privateUserId.setTitle(str("revanced_sb_general_uuid")); privateUserId.setTitle(str("revanced_sb_general_uuid"));
@ -407,11 +461,15 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
editText.setText(Settings.SB_API_URL.get()); editText.setText(Settings.SB_API_URL.get());
DialogInterface.OnClickListener urlChangeListener = (dialog, buttonPressed) -> { // Create a custom dialog.
if (buttonPressed == DialogInterface.BUTTON_NEUTRAL) { Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
Settings.SB_API_URL.resetToDefault(); context,
Utils.showToastLong(str("revanced_sb_api_url_reset")); str("revanced_sb_general_api_url"), // Title.
} else if (buttonPressed == DialogInterface.BUTTON_POSITIVE) { null, // No message, EditText replaces it.
editText, // Pass the EditText.
null, // OK button text.
() -> {
// OK button action.
String serverAddress = editText.getText().toString(); String serverAddress = editText.getText().toString();
if (!SponsorBlockSettings.isValidSBServerAddress(serverAddress)) { if (!SponsorBlockSettings.isValidSBServerAddress(serverAddress)) {
Utils.showToastLong(str("revanced_sb_api_url_invalid")); Utils.showToastLong(str("revanced_sb_api_url_invalid"));
@ -419,39 +477,74 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
Settings.SB_API_URL.save(serverAddress); Settings.SB_API_URL.save(serverAddress);
Utils.showToastLong(str("revanced_sb_api_url_changed")); Utils.showToastLong(str("revanced_sb_api_url_changed"));
} }
} },
}; () -> {}, // Cancel button action (dismiss dialog).
new AlertDialog.Builder(context) str("revanced_settings_reset"), // Neutral (Reset) button text.
.setTitle(apiUrl.getTitle()) () -> {
.setView(editText) // Neutral button action.
.setNegativeButton(android.R.string.cancel, null) Settings.SB_API_URL.resetToDefault();
.setNeutralButton(str("revanced_settings_reset"), urlChangeListener) Utils.showToastLong(str("revanced_sb_api_url_reset"));
.setPositiveButton(android.R.string.ok, urlChangeListener) },
.show(); true // Dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
return true; return true;
}); });
generalCategory.addPreference(apiUrl); generalCategory.addPreference(apiUrl);
importExport = new EditTextPreference(context) { importExport = new EditTextPreference(context) {
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { @Override
Utils.setEditTextDialogTheme(builder); protected void showDialog(Bundle state) {
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
try { try {
Utils.setClipboard(getEditText().getText()); Context context = getContext();
EditText editText = getEditText();
// Create a custom dialog.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
str("revanced_sb_settings_ie"), // Title.
null, // No message, EditText replaces it.
editText, // Pass the EditText.
str("revanced_settings_import"), // OK button text.
() -> {
// OK button action. Trigger OnPreferenceChangeListener.
String newValue = editText.getText().toString();
if (getOnPreferenceChangeListener() != null) {
getOnPreferenceChangeListener().onPreferenceChange(this, newValue);
}
},
() -> {}, // Cancel button action (dismiss only).
str("revanced_sb_settings_copy"), // Neutral button text (Copy).
() -> {
// Neutral button action (Copy).
try {
Utils.setClipboard(editText.getText());
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "Copy settings failure", ex); Logger.printException(() -> "Copy settings failure", ex);
} }
}); },
true // Dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
} catch (Exception ex) {
Logger.printException(() -> "showDialog failure", ex);
}
} }
}; };
importExport.setTitle(str("revanced_sb_settings_ie")); importExport.setTitle(str("revanced_sb_settings_ie"));
// Summary is set in updateUI() // Summary is set in updateUI().
importExport.getEditText().setInputType(InputType.TYPE_CLASS_TEXT EditText editText = importExport.getEditText();
editText.setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_MULTI_LINE
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
importExport.getEditText().setAutofillHints((String) null); editText.setAutofillHints((String) null);
importExport.getEditText().setTextSize(TypedValue.COMPLEX_UNIT_PT, 8); editText.setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
// Set preference listeners.
importExport.setOnPreferenceClickListener(preference1 -> { importExport.setOnPreferenceClickListener(preference1 -> {
importExport.getEditText().setText(SponsorBlockSettings.exportDesktopSettings()); importExport.getEditText().setText(SponsorBlockSettings.exportDesktopSettings());
return true; return true;

View File

@ -3,7 +3,7 @@ package app.revanced.extension.youtube.sponsorblock.ui;
import static android.text.Html.fromHtml; import static android.text.Html.fromHtml;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
@ -11,6 +11,8 @@ import android.preference.EditTextPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceCategory; import android.preference.PreferenceCategory;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.widget.LinearLayout;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -194,14 +196,26 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
updateStatsSelfSaved.run(); updateStatsSelfSaved.run();
preference.setOnPreferenceClickListener(preference1 -> { preference.setOnPreferenceClickListener(preference1 -> {
new AlertDialog.Builder(preference1.getContext()) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setTitle(str("revanced_sb_stats_self_saved_reset_title")) preference.getContext(),
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> { str("revanced_sb_stats_self_saved_reset_title"), // Title.
null, // No message.
null, // No EditText.
null, // OK button text.
() -> {
// OK button action.
Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.resetToDefault(); Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.resetToDefault();
Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.resetToDefault(); Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.resetToDefault();
updateStatsSelfSaved.run(); updateStatsSelfSaved.run();
}) },
.setNegativeButton(android.R.string.no, null).show(); () -> {}, // Cancel button action (dismiss only).
null, // No neutral button.
null, // No neutral button action.
true // Dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
return true; return true;
}); });

View File

@ -694,6 +694,7 @@ public final class app/revanced/patches/shared/misc/pairip/license/DisableLicens
} }
public final class app/revanced/patches/shared/misc/settings/SettingsPatchKt { public final class app/revanced/patches/shared/misc/settings/SettingsPatchKt {
public static final fun overrideThemeColors (Ljava/lang/String;Ljava/lang/String;)V
public static final fun settingsPatch (Ljava/util/List;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch; public static final fun settingsPatch (Ljava/util/List;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch;
public static final fun settingsPatch (Lkotlin/Pair;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch; public static final fun settingsPatch (Lkotlin/Pair;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch;
public static synthetic fun settingsPatch$default (Ljava/util/List;Ljava/util/Set;ILjava/lang/Object;)Lapp/revanced/patcher/patch/ResourcePatch; public static synthetic fun settingsPatch$default (Ljava/util/List;Ljava/util/Set;ILjava/lang/Object;)Lapp/revanced/patcher/patch/ResourcePatch;
@ -1678,6 +1679,7 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;F)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;F)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;J)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;J)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/String;)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V
public static synthetic fun returnEarly$default (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ZILjava/lang/Object;)V public static synthetic fun returnEarly$default (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ZILjava/lang/Object;)V
@ -1687,6 +1689,7 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;F)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;F)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;J)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;J)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/String;)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V
public static final fun transformMethods (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V public static final fun transformMethods (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V

View File

@ -1,7 +1,7 @@
package app.revanced.patches.idaustria.detection.signature package app.revanced.patches.idaustria.detection.signature
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused") @Suppress("unused")
val spoofSignaturePatch = bytecodePatch( val spoofSignaturePatch = bytecodePatch(
@ -24,12 +24,6 @@ val spoofSignaturePatch = bytecodePatch(
"77ef1be61b2c01ebdabddcbf53cc4b6fd9a3c445606ee77b3758162c80ad8f8137b3c6864e92db904807dcb2be9d7717dd21" + "77ef1be61b2c01ebdabddcbf53cc4b6fd9a3c445606ee77b3758162c80ad8f8137b3c6864e92db904807dcb2be9d7717dd21" +
"bf42c121d620ddfb7914f7a95c713d9e1c1b7bdb4a03d618e40cf7e9e235c0b5687e03b7ab3,publicExponent=10001}" "bf42c121d620ddfb7914f7a95c713d9e1c1b7bdb4a03d618e40cf7e9e235c0b5687e03b7ab3,publicExponent=10001}"
spoofSignatureFingerprint.method.addInstructions( spoofSignatureFingerprint.method.returnEarly(expectedSignature)
0,
"""
const-string v0, "$expectedSignature"
return-object v0
""",
)
} }
} }

View File

@ -1,7 +1,7 @@
package app.revanced.patches.nunl.firebase package app.revanced.patches.nunl.firebase
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused") @Suppress("unused")
val spoofCertificatePatch = bytecodePatch( val spoofCertificatePatch = bytecodePatch(
@ -12,13 +12,7 @@ val spoofCertificatePatch = bytecodePatch(
execute { execute {
getFingerprintHashForPackageFingerprints.forEach { fingerprint -> getFingerprintHashForPackageFingerprints.forEach { fingerprint ->
fingerprint.method.addInstructions( fingerprint.method.returnEarly("eae41fc018df2731a9b6ae1ac327da44a288667b")
0,
"""
const-string v0, "eae41fc018df2731a9b6ae1ac327da44a288667b"
return-object v0
""",
)
} }
} }
} }

View File

@ -1,8 +1,8 @@
package app.revanced.patches.photomath.detection.deviceid package app.revanced.patches.photomath.detection.deviceid
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.photomath.detection.signature.signatureDetectionPatch import app.revanced.patches.photomath.detection.signature.signatureDetectionPatch
import app.revanced.util.returnEarly
import kotlin.random.Random import kotlin.random.Random
@Suppress("unused") @Suppress("unused")
@ -15,12 +15,6 @@ val getDeviceIdPatch = bytecodePatch(
compatibleWith("com.microblink.photomath") compatibleWith("com.microblink.photomath")
execute { execute {
getDeviceIdFingerprint.method.replaceInstructions( getDeviceIdFingerprint.method.returnEarly(Random.nextLong().toString(16))
0,
"""
const-string v0, "${Random.nextLong().toString(16)}"
return-object v0
""",
)
} }
} }

View File

@ -3,6 +3,7 @@ package app.revanced.patches.piccomafr.misc
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.stringOption import app.revanced.patcher.patch.stringOption
import app.revanced.util.returnEarly
@Suppress("unused") @Suppress("unused")
val spoofAndroidDeviceIdPatch = bytecodePatch( val spoofAndroidDeviceIdPatch = bytecodePatch(
@ -39,12 +40,6 @@ val spoofAndroidDeviceIdPatch = bytecodePatch(
) { it!!.matches("[A-Fa-f0-9]{16}".toRegex()) } ) { it!!.matches("[A-Fa-f0-9]{16}".toRegex()) }
execute { execute {
getAndroidIdFingerprint.method.addInstructions( getAndroidIdFingerprint.method.returnEarly(androidDeviceId!!)
0,
"""
const-string v0, "$androidDeviceId"
return-object v0
""",
)
} }
} }

View File

@ -1,9 +1,9 @@
package app.revanced.patches.reddit.customclients.boostforreddit.api package app.revanced.patches.reddit.customclients.boostforreddit.api
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patches.reddit.customclients.spoofClientPatch import app.revanced.patches.reddit.customclients.spoofClientPatch
import app.revanced.util.returnEarly
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
val spoofClientPatch = spoofClientPatch(redirectUri = "http://rubenmayayo.com") { clientIdOption -> val spoofClientPatch = spoofClientPatch(redirectUri = "http://rubenmayayo.com") { clientIdOption ->
@ -14,13 +14,7 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "http://rubenmayayo.com")
execute { execute {
// region Patch client id. // region Patch client id.
getClientIdFingerprint.method.addInstructions( getClientIdFingerprint.method.returnEarly(clientId!!)
0,
"""
const-string v0, "$clientId"
return-object v0
""",
)
// endregion // endregion

View File

@ -1,9 +1,8 @@
package app.revanced.patches.reddit.customclients.joeyforreddit.api package app.revanced.patches.reddit.customclients.joeyforreddit.api
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.disablePiracyDetectionPatch import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.disablePiracyDetectionPatch
import app.revanced.patches.reddit.customclients.spoofClientPatch import app.revanced.patches.reddit.customclients.spoofClientPatch
import app.revanced.util.returnEarly
val spoofClientPatch = spoofClientPatch(redirectUri = "https://127.0.0.1:65023/authorize_callback") { clientIdOption -> val spoofClientPatch = spoofClientPatch(redirectUri = "https://127.0.0.1:65023/authorize_callback") { clientIdOption ->
dependsOn(disablePiracyDetectionPatch) dependsOn(disablePiracyDetectionPatch)
@ -19,13 +18,7 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "https://127.0.0.1:65023/a
execute { execute {
// region Patch client id. // region Patch client id.
getClientIdFingerprint.method.addInstructions( getClientIdFingerprint.method.returnEarly(clientId!!)
0,
"""
const-string v0, "$clientId"
return-object v0
""",
)
// endregion // endregion
@ -35,13 +28,7 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "https://127.0.0.1:65023/a
val randomName = (0..100000).random() val randomName = (0..100000).random()
val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)" val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
authUtilityUserAgentFingerprint.method.replaceInstructions( authUtilityUserAgentFingerprint.method.returnEarly(userAgent)
0,
"""
const-string v0, "$userAgent"
return-object v0
""",
)
// endregion // endregion
} }

View File

@ -2,12 +2,12 @@ package app.revanced.patches.reddit.customclients.redditisfun.api
import app.revanced.patcher.Fingerprint import app.revanced.patcher.Fingerprint
import app.revanced.patcher.Match import app.revanced.patcher.Match
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patches.reddit.customclients.spoofClientPatch import app.revanced.patches.reddit.customclients.spoofClientPatch
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.returnEarly
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference import com.android.tools.smali.dexlib2.iface.reference.StringReference
@ -54,13 +54,7 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "redditisfun://auth") { cl
val randomName = (0..100000).random() val randomName = (0..100000).random()
val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)" val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
getUserAgentFingerprint.method.addInstructions( getUserAgentFingerprint.method.returnEarly(userAgent)
0,
"""
const-string v0, "$userAgent"
return-object v0
""",
)
// endregion // endregion

View File

@ -1,7 +1,7 @@
package app.revanced.patches.reddit.customclients.slide.api package app.revanced.patches.reddit.customclients.slide.api
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patches.reddit.customclients.spoofClientPatch import app.revanced.patches.reddit.customclients.spoofClientPatch
import app.revanced.util.returnEarly
val spoofClientPatch = spoofClientPatch(redirectUri = "http://www.ccrama.me") { clientIdOption -> val spoofClientPatch = spoofClientPatch(redirectUri = "http://www.ccrama.me") { clientIdOption ->
compatibleWith("me.ccrama.redditslide") compatibleWith("me.ccrama.redditslide")
@ -9,12 +9,6 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "http://www.ccrama.me") {
val clientId by clientIdOption val clientId by clientIdOption
execute { execute {
getClientIdFingerprint.method.addInstructions( getClientIdFingerprint.method.returnEarly(clientId!!)
0,
"""
const-string v0, "$clientId"
return-object v0
""",
)
} }
} }

View File

@ -1,14 +1,14 @@
package app.revanced.patches.reddit.customclients.sync.syncforreddit.api package app.revanced.patches.reddit.customclients.sync.syncforreddit.api
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patches.reddit.customclients.spoofClientPatch import app.revanced.patches.reddit.customclients.spoofClientPatch
import app.revanced.patches.reddit.customclients.sync.detection.piracy.disablePiracyDetectionPatch import app.revanced.patches.reddit.customclients.sync.detection.piracy.disablePiracyDetectionPatch
import app.revanced.util.returnEarly
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference import com.android.tools.smali.dexlib2.iface.reference.StringReference
import java.util.* import java.util.Base64
val spoofClientPatch = spoofClientPatch( val spoofClientPatch = spoofClientPatch(
redirectUri = "http://redditsync/auth", redirectUri = "http://redditsync/auth",
@ -28,13 +28,8 @@ val spoofClientPatch = spoofClientPatch(
getBearerTokenFingerprint.match(getAuthorizationStringFingerprint.originalClassDef).method.apply { getBearerTokenFingerprint.match(getAuthorizationStringFingerprint.originalClassDef).method.apply {
val auth = Base64.getEncoder().encodeToString("$clientId:".toByteArray(Charsets.UTF_8)) val auth = Base64.getEncoder().encodeToString("$clientId:".toByteArray(Charsets.UTF_8))
addInstructions( returnEarly("Basic $auth")
0,
"""
const-string v0, "Basic $auth"
return-object v0
""",
)
val occurrenceIndex = val occurrenceIndex =
getAuthorizationStringFingerprint.stringMatches!!.first().index getAuthorizationStringFingerprint.stringMatches!!.first().index
@ -63,23 +58,19 @@ val spoofClientPatch = spoofClientPatch(
val randomName = (0..100000).random() val randomName = (0..100000).random()
val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)" val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
getUserAgentFingerprint.method.replaceInstruction( getUserAgentFingerprint.method.returnEarly(userAgent)
0,
"""
const-string v0, "$userAgent"
return-object v0
""",
)
// endregion // endregion
// region Patch Imgur API URL. // region Patch Imgur API URL.
val apiUrlIndex = imgurImageAPIFingerprint.stringMatches!!.first().index imgurImageAPIFingerprint.let {
imgurImageAPIFingerprint.method.replaceInstruction( val apiUrlIndex = it.stringMatches!!.first().index
it.method.replaceInstruction(
apiUrlIndex, apiUrlIndex,
"const-string v1, \"https://api.imgur.com/3/image\"", "const-string v1, \"https://api.imgur.com/3/image\"",
) )
}
// endregion // endregion
} }

View File

@ -84,7 +84,7 @@ fun checkEnvironmentPatch(
fun invokeCheck() = mainActivityOnCreateFingerprint.method.addInstruction( fun invokeCheck() = mainActivityOnCreateFingerprint.method.addInstruction(
0, 0,
"invoke-static/range { p0 .. p0 },$EXTENSION_CLASS_DESCRIPTOR->check(Landroid/app/Activity;)V", "invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->check(Landroid/app/Activity;)V",
) )
setPatchInfo() setPatchInfo()

View File

@ -3,11 +3,11 @@ package app.revanced.patches.shared.misc.extension
import app.revanced.patcher.Fingerprint import app.revanced.patcher.Fingerprint
import app.revanced.patcher.FingerprintBuilder import app.revanced.patcher.FingerprintBuilder
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
import java.net.URLDecoder import java.net.URLDecoder
import java.util.jar.JarFile import java.util.jar.JarFile
@ -80,14 +80,7 @@ fun sharedExtensionPatch(
} }
val manifestValue = getPatchesManifestEntry("Version") val manifestValue = getPatchesManifestEntry("Version")
returnEarly(manifestValue)
addInstructions(
0,
"""
const-string v0, "$manifestValue"
return-object v0
""",
)
} }
} }
} }

View File

@ -0,0 +1,23 @@
package app.revanced.patches.shared.misc.settings
import app.revanced.patcher.fingerprint
import app.revanced.patches.shared.misc.extension.EXTENSION_CLASS_DESCRIPTOR
import com.android.tools.smali.dexlib2.AccessFlags
internal val themeLightColorResourceNameFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("Ljava/lang/String;")
parameters()
custom { method, classDef ->
method.name == "getThemeLightColorResourceName" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
}
}
internal val themeDarkColorResourceNameFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("Ljava/lang/String;")
parameters()
custom { method, classDef ->
method.name == "getThemeDarkColorResourceName" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
}
}

View File

@ -1,6 +1,7 @@
package app.revanced.patches.shared.misc.settings package app.revanced.patches.shared.misc.settings
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResource import app.revanced.patches.all.misc.resources.addResource
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
@ -13,6 +14,7 @@ import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources import app.revanced.util.copyResources
import app.revanced.util.getNode import app.revanced.util.getNode
import app.revanced.util.insertFirst import app.revanced.util.insertFirst
import app.revanced.util.returnEarly
import org.w3c.dom.Node import org.w3c.dom.Node
// TODO: Delete this on next major version bump. // TODO: Delete this on next major version bump.
@ -22,6 +24,30 @@ fun settingsPatch (
preferences: Set<BasePreference>, preferences: Set<BasePreference>,
) = settingsPatch(listOf(rootPreference), preferences) ) = settingsPatch(listOf(rootPreference), preferences)
private var themeForegroundColor : String? = null
private var themeBackgroundColor : String? = null
/**
* Sets the default theme colors used in various ReVanced specific settings menus.
* By default these colors are white and black, but instead can be set to the
* same color the target app uses for it's own settings.
*/
fun overrideThemeColors(foregroundColor: String, backgroundColor: String) {
themeForegroundColor = foregroundColor
themeBackgroundColor = backgroundColor
}
private val settingsColorPatch = bytecodePatch {
finalize {
if (themeForegroundColor != null) {
themeLightColorResourceNameFingerprint.method.returnEarly(themeForegroundColor!!)
}
if (themeBackgroundColor != null) {
themeDarkColorResourceNameFingerprint.method.returnEarly(themeBackgroundColor!!)
}
}
}
/** /**
* A resource patch that adds settings to a settings fragment. * A resource patch that adds settings to a settings fragment.
* *
@ -33,12 +59,28 @@ fun settingsPatch (
rootPreferences: List<Pair<BasePreference, String>>? = null, rootPreferences: List<Pair<BasePreference, String>>? = null,
preferences: Set<BasePreference>, preferences: Set<BasePreference>,
) = resourcePatch { ) = resourcePatch {
dependsOn(addResourcesPatch) dependsOn(addResourcesPatch, settingsColorPatch)
execute { execute {
copyResources( copyResources(
"settings", "settings",
ResourceGroup("xml", "revanced_prefs.xml", "revanced_prefs_icons.xml"), ResourceGroup("xml", "revanced_prefs.xml", "revanced_prefs_icons.xml"),
ResourceGroup("drawable",
// CustomListPreference resources.
"revanced_ic_dialog_alert.xml",
"revanced_settings_arrow_time.xml",
"revanced_settings_circle_background.xml",
"revanced_settings_cursor.xml",
"revanced_settings_custom_checkmark.xml",
"revanced_settings_search_icon.xml",
"revanced_settings_toolbar_arrow_left.xml",
),
ResourceGroup("layout",
"revanced_custom_list_item_checked.xml",
// Color picker.
"revanced_color_dot_widget.xml",
"revanced_color_picker.xml",
)
) )
addResources("shared", "misc.settings.settingsResourcePatch") addResources("shared", "misc.settings.settingsResourcePatch")

View File

@ -12,7 +12,7 @@ import org.w3c.dom.Document
* @param summaryKey The preference summary key. * @param summaryKey The preference summary key.
* @param icon The preference icon resource name. * @param icon The preference icon resource name.
* @param layout Layout declaration. * @param layout Layout declaration.
* @param tag The preference tag. * @param tag The preference class type.
* @param entriesKey The entries array key. * @param entriesKey The entries array key.
* @param entryValuesKey The entry values array key. * @param entryValuesKey The entry values array key.
*/ */
@ -20,10 +20,12 @@ import org.w3c.dom.Document
class ListPreference( class ListPreference(
key: String? = null, key: String? = null,
titleKey: String = "${key}_title", titleKey: String = "${key}_title",
summaryKey: String? = "${key}_summary", /** Summary key is ignored and will be removed soon */
//@Deprecated
summaryKey: String? = null,
icon: String? = null, icon: String? = null,
layout: String? = null, layout: String? = null,
tag: String = "ListPreference", tag: String = "app.revanced.extension.shared.settings.preference.CustomDialogListPreference",
val entriesKey: String? = "${key}_entries", val entriesKey: String? = "${key}_entries",
val entryValuesKey: String? = "${key}_entry_values" val entryValuesKey: String? = "${key}_entry_values"
) : BasePreference(key, titleKey, summaryKey, icon, layout, tag) { ) : BasePreference(key, titleKey, summaryKey, icon, layout, tag) {

View File

@ -25,7 +25,7 @@ val embeddedAdsPatch = bytecodePatch(
addResources("twitch", "ad.embedded.embeddedAdsPatch") addResources("twitch", "ad.embedded.embeddedAdsPatch")
PreferenceScreen.ADS.SURESTREAM.addPreferences( PreferenceScreen.ADS.SURESTREAM.addPreferences(
ListPreference("revanced_block_embedded_ads", summaryKey = null), ListPreference("revanced_block_embedded_ads"),
) )
// Inject OkHttp3 application interceptor // Inject OkHttp3 application interceptor

View File

@ -34,10 +34,7 @@ val showDeletedMessagesPatch = bytecodePatch(
addResources("twitch", "chat.antidelete.showDeletedMessagesPatch") addResources("twitch", "chat.antidelete.showDeletedMessagesPatch")
PreferenceScreen.CHAT.GENERAL.addPreferences( PreferenceScreen.CHAT.GENERAL.addPreferences(
ListPreference( ListPreference("revanced_show_deleted_messages")
key = "revanced_show_deleted_messages",
summaryKey = null,
),
) )
// Spoiler mode: Force set hasModAccess member to true in constructor // Spoiler mode: Force set hasModAccess member to true in constructor

View File

@ -1,7 +1,7 @@
package app.revanced.patches.warnwetter.misc.firebasegetcert package app.revanced.patches.warnwetter.misc.firebasegetcert
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
val firebaseGetCertPatch = bytecodePatch( val firebaseGetCertPatch = bytecodePatch(
description = "Spoofs the X-Android-Cert header.", description = "Spoofs the X-Android-Cert header.",
@ -10,13 +10,7 @@ val firebaseGetCertPatch = bytecodePatch(
execute { execute {
listOf(getRegistrationCertFingerprint, getMessagingCertFingerprint).forEach { match -> listOf(getRegistrationCertFingerprint, getMessagingCertFingerprint).forEach { match ->
match.method.addInstructions( match.method.returnEarly("0799DDF0414D3B3475E88743C91C0676793ED450")
0,
"""
const-string v0, "0799DDF0414D3B3475E88743C91C0676793ED450"
return-object v0
""",
)
} }
} }
} }

View File

@ -11,10 +11,14 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPref
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.settings.preference.TextPreference import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.youtube.misc.playercontrols.* import app.revanced.patches.youtube.misc.playercontrols.addBottomControl
import app.revanced.patches.youtube.misc.playercontrols.initializeBottomControl
import app.revanced.patches.youtube.misc.playercontrols.injectVisibilityCheckCall
import app.revanced.patches.youtube.misc.playercontrols.playerControlsPatch
import app.revanced.patches.youtube.misc.playercontrols.playerControlsResourcePatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityFingerprint import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
import app.revanced.patches.youtube.video.information.videoInformationPatch import app.revanced.patches.youtube.video.information.videoInformationPatch
import app.revanced.util.ResourceGroup import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources import app.revanced.util.copyResources
@ -83,12 +87,10 @@ val downloadsPatch = bytecodePatch(
injectVisibilityCheckCall(BUTTON_DESCRIPTOR) injectVisibilityCheckCall(BUTTON_DESCRIPTOR)
// Main activity is used to launch downloader intent. // Main activity is used to launch downloader intent.
mainActivityFingerprint.method.apply { mainActivityOnCreateFingerprint.method.addInstruction(
addInstruction( 1,
implementation!!.instructions.lastIndex, "invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->activityCreated(Landroid/app/Activity;)V"
"invoke-static { p0 }, $EXTENSION_CLASS_DESCRIPTOR->activityCreated(Landroid/app/Activity;)V",
) )
}
offlineVideoEndpointFingerprint.method.apply { offlineVideoEndpointFingerprint.method.apply {
addInstructionsWithLabels( addInstructionsWithLabels(

View File

@ -14,7 +14,7 @@ import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.playservice.is_19_43_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_43_or_greater
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityFingerprint import app.revanced.patches.youtube.shared.mainActivityConstructorFingerprint
import app.revanced.util.* import app.revanced.util.*
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
@ -43,10 +43,7 @@ private val swipeControlsResourcePatch = resourcePatch {
SwitchPreference("revanced_swipe_haptic_feedback"), SwitchPreference("revanced_swipe_haptic_feedback"),
SwitchPreference("revanced_swipe_save_and_restore_brightness"), SwitchPreference("revanced_swipe_save_and_restore_brightness"),
SwitchPreference("revanced_swipe_lowest_value_enable_auto_brightness"), SwitchPreference("revanced_swipe_lowest_value_enable_auto_brightness"),
ListPreference( ListPreference("revanced_swipe_overlay_style"),
"revanced_swipe_overlay_style",
summaryKey = null,
),
TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER), TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER),
TextPreference("revanced_swipe_overlay_progress_brightness_color", TextPreference("revanced_swipe_overlay_progress_brightness_color",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference", tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
@ -103,7 +100,7 @@ val swipeControlsPatch = bytecodePatch(
execute { execute {
val wrapperClass = swipeControlsHostActivityFingerprint.classDef val wrapperClass = swipeControlsHostActivityFingerprint.classDef
val targetClass = mainActivityFingerprint.classDef val targetClass = mainActivityConstructorFingerprint.classDef
// Inject the wrapper class from the extension into the class hierarchy of MainActivity. // Inject the wrapper class from the extension into the class hierarchy of MainActivity.
wrapperClass.setSuperClass(targetClass.superclass) wrapperClass.setSuperClass(targetClass.superclass)

View File

@ -47,10 +47,7 @@ val changeFormFactorPatch = bytecodePatch(
addResources("youtube", "layout.formfactor.changeFormFactorPatch") addResources("youtube", "layout.formfactor.changeFormFactorPatch")
PreferenceScreen.GENERAL_LAYOUT.addPreferences( PreferenceScreen.GENERAL_LAYOUT.addPreferences(
ListPreference( ListPreference("revanced_change_form_factor")
"revanced_change_form_factor",
summaryKey = null,
)
) )
hookNavigationButtonCreated(EXTENSION_CLASS_DESCRIPTOR) hookNavigationButtonCreated(EXTENSION_CLASS_DESCRIPTOR)

View File

@ -184,23 +184,18 @@ val miniplayerPatch = bytecodePatch(
preferences += preferences +=
if (is_20_03_or_greater) { if (is_20_03_or_greater) {
ListPreference( ListPreference("revanced_miniplayer_type")
"revanced_miniplayer_type",
summaryKey = null,
)
} else if (is_19_43_or_greater) { } else if (is_19_43_or_greater) {
ListPreference( ListPreference(
"revanced_miniplayer_type", key = "revanced_miniplayer_type",
summaryKey = null,
entriesKey = "revanced_miniplayer_type_legacy_19_43_entries", entriesKey = "revanced_miniplayer_type_legacy_19_43_entries",
entryValuesKey = "revanced_miniplayer_type_legacy_19_43_entry_values", entryValuesKey = "revanced_miniplayer_type_legacy_19_43_entry_values"
) )
} else { } else {
ListPreference( ListPreference(
"revanced_miniplayer_type", key = "revanced_miniplayer_type",
summaryKey = null,
entriesKey = "revanced_miniplayer_type_legacy_19_16_entries", entriesKey = "revanced_miniplayer_type_legacy_19_16_entries",
entryValuesKey = "revanced_miniplayer_type_legacy_19_16_entry_values", entryValuesKey = "revanced_miniplayer_type_legacy_19_16_entry_values"
) )
} }

View File

@ -49,10 +49,7 @@ internal val exitFullscreenPatch = bytecodePatch(
addResources("youtube", "layout.player.fullscreen.exitFullscreenPatch") addResources("youtube", "layout.player.fullscreen.exitFullscreenPatch")
PreferenceScreen.PLAYER.addPreferences( PreferenceScreen.PLAYER.addPreferences(
ListPreference( ListPreference("revanced_exit_fullscreen")
"revanced_exit_fullscreen",
summaryKey = null,
)
) )
autoRepeatFingerprint.match(autoRepeatParentFingerprint.originalClassDef).method.apply { autoRepeatFingerprint.match(autoRepeatParentFingerprint.originalClassDef).method.apply {

View File

@ -70,8 +70,7 @@ val shortsAutoplayPatch = bytecodePatch(
// Main activity is used to check if app is in pip mode. // Main activity is used to check if app is in pip mode.
mainActivityOnCreateFingerprint.method.addInstruction( mainActivityOnCreateFingerprint.method.addInstruction(
1, 1,
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" + "invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->setMainActivity(Landroid/app/Activity;)V",
"setMainActivity(Landroid/app/Activity;)V",
) )
val reelEnumClass = reelEnumConstructorFingerprint.originalClassDef.type val reelEnumClass = reelEnumConstructorFingerprint.originalClassDef.type

View File

@ -79,14 +79,10 @@ val openShortsInRegularPlayerPatch = bytecodePatch(
PreferenceScreen.SHORTS.addPreferences( PreferenceScreen.SHORTS.addPreferences(
if (is_19_46_or_greater) { if (is_19_46_or_greater) {
ListPreference( ListPreference("revanced_shorts_player_type")
key = "revanced_shorts_player_type",
summaryKey = null,
)
} else { } else {
ListPreference( ListPreference(
key = "revanced_shorts_player_type", key = "revanced_shorts_player_type",
summaryKey = null,
entriesKey = "revanced_shorts_player_type_legacy_entries", entriesKey = "revanced_shorts_player_type_legacy_entries",
entryValuesKey = "revanced_shorts_player_type_legacy_entry_values" entryValuesKey = "revanced_shorts_player_type_legacy_entry_values"
) )

View File

@ -83,7 +83,7 @@ val spoofAppVersionPatch = bytecodePatch(
if (is_19_43_or_greater) { if (is_19_43_or_greater) {
ListPreference( ListPreference(
key = "revanced_spoof_app_version_target", key = "revanced_spoof_app_version_target",
summaryKey = null, summaryKey = null
) )
} else { } else {
ListPreference( ListPreference(

View File

@ -53,7 +53,6 @@ val changeStartPagePatch = bytecodePatch(
preferences = setOf( preferences = setOf(
ListPreference( ListPreference(
key = "revanced_change_start_page", key = "revanced_change_start_page",
summaryKey = null,
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference" tag = "app.revanced.extension.shared.settings.preference.SortedListPreference"
), ),
SwitchPreference("revanced_change_start_page_always") SwitchPreference("revanced_change_start_page_always")

View File

@ -24,26 +24,6 @@ internal val lithoThemeFingerprint = fingerprint {
} }
} }
internal val themeHelperDarkColorFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("Ljava/lang/String;")
parameters()
custom { method, _ ->
method.name == "darkThemeResourceName" &&
method.definingClass == "Lapp/revanced/extension/youtube/ThemeHelper;"
}
}
internal val themeHelperLightColorFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("Ljava/lang/String;")
parameters()
custom { method, _ ->
method.name == "lightThemeResourceName" &&
method.definingClass == "Lapp/revanced/extension/youtube/ThemeHelper;"
}
}
internal const val GRADIENT_LOADING_SCREEN_AB_CONSTANT = 45412406L internal const val GRADIENT_LOADING_SCREEN_AB_CONSTANT = 45412406L
internal val useGradientLoadingScreenFingerprint = fingerprint { internal val useGradientLoadingScreenFingerprint = fingerprint {

View File

@ -1,6 +1,5 @@
package app.revanced.patches.youtube.layout.theme package app.revanced.patches.youtube.layout.theme
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
@ -8,6 +7,7 @@ import app.revanced.patcher.patch.stringOption
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.overrideThemeColors
import app.revanced.patches.shared.misc.settings.preference.BasePreference import app.revanced.patches.shared.misc.settings.preference.BasePreference
import app.revanced.patches.shared.misc.settings.preference.InputType import app.revanced.patches.shared.misc.settings.preference.InputType
import app.revanced.patches.shared.misc.settings.preference.ListPreference import app.revanced.patches.shared.misc.settings.preference.ListPreference
@ -22,16 +22,17 @@ import app.revanced.patches.youtube.misc.playservice.is_19_47_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.childElementsSequence
import app.revanced.util.forEachChildElement import app.revanced.util.forEachChildElement
import app.revanced.util.insertLiteralOverride import app.revanced.util.insertLiteralOverride
import org.w3c.dom.Element import org.w3c.dom.Element
private const val EXTENSION_CLASS_DESCRIPTOR = private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/theme/ThemePatch;"
"Lapp/revanced/extension/youtube/patches/theme/ThemePatch;"
val themePatch = bytecodePatch( val themePatch = bytecodePatch(
name = "Theme", name = "Theme",
description = "Adds options for theming and applies a custom background theme (dark background theme defaults to amoled black).", description = "Adds options for theming and applies a custom background theme " +
"(dark background theme defaults to amoled black).",
) { ) {
val amoledBlackColor = "@android:color/black" val amoledBlackColor = "@android:color/black"
val whiteColor = "@android:color/white" val whiteColor = "@android:color/white"
@ -109,26 +110,22 @@ val themePatch = bytecodePatch(
) )
) )
overrideThemeColors(lightThemeBackgroundColor!!, darkThemeBackgroundColor!!)
// Edit theme colors via resources. // Edit theme colors via resources.
document("res/values/colors.xml").use { document -> document("res/values/colors.xml").use { document ->
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
val children = resourcesNode.childNodes resourcesNode.childElementsSequence().forEach { node ->
for (i in 0 until children.length) {
val node = children.item(i) as? Element ?: continue
node.textContent =
when (node.getAttribute("name")) { when (node.getAttribute("name")) {
"yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3", "yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98",
"yt_black4", "yt_status_bar_background_dark", "material_grey_850", "yt_black2", "yt_black3", "yt_black4", "yt_status_bar_background_dark",
-> darkThemeBackgroundColor ?: continue "material_grey_850",
-> node.textContent = darkThemeBackgroundColor
"yt_white1", "yt_white1_opacity95", "yt_white1_opacity98", "yt_white1", "yt_white1_opacity95", "yt_white1_opacity98",
"yt_white2", "yt_white3", "yt_white4", "yt_white2", "yt_white3", "yt_white4",
-> lightThemeBackgroundColor ?: continue -> node.textContent = lightThemeBackgroundColor
else -> continue
} }
} }
} }
@ -151,30 +148,20 @@ val themePatch = bytecodePatch(
} }
} }
val splashBackgroundColor = "revanced_splash_background_color"
// Add a dynamic background color to the colors.xml file. // Add a dynamic background color to the colors.xml file.
lightThemeBackgroundColor?.let { val splashBackgroundColorKey = "revanced_splash_background_color"
addColorResource("res/values/colors.xml", splashBackgroundColor, it) addColorResource("res/values/colors.xml", splashBackgroundColorKey, lightThemeBackgroundColor!!)
} addColorResource("res/values-night/colors.xml", splashBackgroundColorKey, darkThemeBackgroundColor!!)
darkThemeBackgroundColor?.let {
addColorResource("res/values-night/colors.xml", splashBackgroundColor, it)
}
// Edit splash screen files and change the background color, // Edit splash screen files and change the background color,
// if the background colors are set. arrayOf(
if (darkThemeBackgroundColor != null && lightThemeBackgroundColor != null) {
val splashScreenResourceFiles = listOf(
"res/drawable/quantum_launchscreen_youtube.xml", "res/drawable/quantum_launchscreen_youtube.xml",
"res/drawable-sw600dp/quantum_launchscreen_youtube.xml", "res/drawable-sw600dp/quantum_launchscreen_youtube.xml",
) ).forEach editSplashScreen@{ resourceFileName ->
document(resourceFileName).use { document ->
splashScreenResourceFiles.forEach editSplashScreen@{ resourceFile ->
document(resourceFile).use { document ->
document.getElementsByTagName("layer-list").item(0).forEachChildElement { node -> document.getElementsByTagName("layer-list").item(0).forEachChildElement { node ->
if (node.hasAttribute("android:drawable")) { if (node.hasAttribute("android:drawable")) {
node.setAttribute("android:drawable", "@color/$splashBackgroundColor") node.setAttribute("android:drawable", "@color/$splashBackgroundColorKey")
return@editSplashScreen return@editSplashScreen
} }
} }
@ -194,7 +181,7 @@ val themePatch = bytecodePatch(
// Fix status and navigation bar showing white on some Android devices, // Fix status and navigation bar showing white on some Android devices,
// such as SDK 28 Android 10 medium tablet. // such as SDK 28 Android 10 medium tablet.
val colorSplashBackgroundColor = "@color/$splashBackgroundColor" val colorSplashBackgroundColor = "@color/$splashBackgroundColorKey"
arrayOf( arrayOf(
"android:navigationBarColor" to colorSplashBackgroundColor, "android:navigationBarColor" to colorSplashBackgroundColor,
"android:windowBackground" to colorSplashBackgroundColor, "android:windowBackground" to colorSplashBackgroundColor,
@ -213,7 +200,6 @@ val themePatch = bytecodePatch(
} }
} }
} }
}
) )
compatibleWith( compatibleWith(
@ -237,10 +223,7 @@ val themePatch = bytecodePatch(
if (is_19_47_or_greater) { if (is_19_47_or_greater) {
PreferenceScreen.GENERAL_LAYOUT.addPreferences( PreferenceScreen.GENERAL_LAYOUT.addPreferences(
ListPreference( ListPreference("splash_screen_animation_style")
key = "splash_screen_animation_style",
summaryKey = null
)
) )
} }
@ -257,21 +240,6 @@ val themePatch = bytecodePatch(
) )
} }
arrayOf(
themeHelperLightColorFingerprint to lightThemeBackgroundColor,
themeHelperDarkColorFingerprint to darkThemeBackgroundColor,
).forEach { (fingerprint, color) ->
fingerprint.method.apply {
addInstructions(
0,
"""
const-string v0, "$color"
return-object v0
""",
)
}
}
lithoColorOverrideHook(EXTENSION_CLASS_DESCRIPTOR, "getValue") lithoColorOverrideHook(EXTENSION_CLASS_DESCRIPTOR, "getValue")
} }
} }

View File

@ -50,34 +50,29 @@ val alternativeThumbnailsPatch = bytecodePatch(
val values = "revanced_alt_thumbnail_options_entry_values" val values = "revanced_alt_thumbnail_options_entry_values"
PreferenceScreen.ALTERNATIVE_THUMBNAILS.addPreferences( PreferenceScreen.ALTERNATIVE_THUMBNAILS.addPreferences(
ListPreference( ListPreference(
"revanced_alt_thumbnail_home", key = "revanced_alt_thumbnail_home",
summaryKey = null,
entriesKey = entries, entriesKey = entries,
entryValuesKey = values, entryValuesKey = values
), ),
ListPreference( ListPreference(
"revanced_alt_thumbnail_subscription", key = "revanced_alt_thumbnail_subscription",
summaryKey = null,
entriesKey = entries, entriesKey = entries,
entryValuesKey = values, entryValuesKey = values
), ),
ListPreference( ListPreference(
"revanced_alt_thumbnail_library", key = "revanced_alt_thumbnail_library",
summaryKey = null,
entriesKey = entries, entriesKey = entries,
entryValuesKey = values, entryValuesKey = values
), ),
ListPreference( ListPreference(
"revanced_alt_thumbnail_player", key = "revanced_alt_thumbnail_player",
summaryKey = null,
entriesKey = entries, entriesKey = entries,
entryValuesKey = values, entryValuesKey = values
), ),
ListPreference( ListPreference(
"revanced_alt_thumbnail_search", key = "revanced_alt_thumbnail_search",
summaryKey = null,
entriesKey = entries, entriesKey = entries,
entryValuesKey = values, entryValuesKey = values
), ),
NonInteractivePreference( NonInteractivePreference(
"revanced_alt_thumbnail_dearrow_about", "revanced_alt_thumbnail_dearrow_about",
@ -89,7 +84,7 @@ val alternativeThumbnailsPatch = bytecodePatch(
TextPreference("revanced_alt_thumbnail_dearrow_api_url"), TextPreference("revanced_alt_thumbnail_dearrow_api_url"),
NonInteractivePreference("revanced_alt_thumbnail_stills_about"), NonInteractivePreference("revanced_alt_thumbnail_stills_about"),
SwitchPreference("revanced_alt_thumbnail_stills_fast"), SwitchPreference("revanced_alt_thumbnail_stills_fast"),
ListPreference("revanced_alt_thumbnail_stills_time", summaryKey = null), ListPreference("revanced_alt_thumbnail_stills_time"),
) )
addImageUrlHook(EXTENSION_CLASS_DESCRIPTOR) addImageUrlHook(EXTENSION_CLASS_DESCRIPTOR)

View File

@ -4,15 +4,6 @@ import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
internal val onBackPressedFingerprint = fingerprint {
returns("V")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
opcodes(Opcode.RETURN_VOID)
custom { method, classDef ->
method.name == "onBackPressed" && classDef.endsWith("MainActivity;")
}
}
internal val scrollPositionFingerprint = fingerprint { internal val scrollPositionFingerprint = fingerprint {
accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL) accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL)
returns("V") returns("V")

View File

@ -2,6 +2,7 @@ package app.revanced.patches.youtube.misc.fix.backtoexitgesture
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.youtube.shared.mainActivityOnBackPressedFingerprint
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -10,7 +11,7 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/FixBackToExitGesturePatch;" private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/FixBackToExitGesturePatch;"
internal val fixBackToExitGesturePatch = bytecodePatch( internal val fixBackToExitGesturePatch = bytecodePatch(
description = "Fixes the swipe back to exit gesture.", description = "Fixes the swipe back to exit gesture."
) { ) {
execute { execute {
@ -36,12 +37,12 @@ internal val fixBackToExitGesturePatch = bytecodePatch(
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->onScrollingViews()V" "invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->onScrollingViews()V"
) )
} }
} }
onBackPressedFingerprint.let { mainActivityOnBackPressedFingerprint.method.apply {
it.method.addInstruction( val index = indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID)
it.patternMatch!!.endIndex, addInstruction(
index,
"invoke-static { p0 }, $EXTENSION_CLASS_DESCRIPTOR->onBackPressed(Landroid/app/Activity;)V" "invoke-static { p0 }, $EXTENSION_CLASS_DESCRIPTOR->onBackPressed(Landroid/app/Activity;)V"
) )
} }

View File

@ -42,15 +42,6 @@ internal val initializeButtonsFingerprint = fingerprint {
literal { imageOnlyTabResourceId } literal { imageOnlyTabResourceId }
} }
internal val mainActivityOnBackPressedFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters()
custom { method, classDef ->
method.name == "onBackPressed" && classDef.endsWith("MainActivity;")
}
}
/** /**
* Extension method, used for callback into to other patches. * Extension method, used for callback into to other patches.
* Specifically, [navigationButtonsPatch]. * Specifically, [navigationButtonsPatch].

View File

@ -15,6 +15,7 @@ import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.playservice.is_19_35_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_35_or_greater
import app.revanced.patches.youtube.shared.mainActivityOnBackPressedFingerprint
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.indexOfFirstInstructionReversedOrThrow

View File

@ -3,7 +3,6 @@ package app.revanced.patches.youtube.misc.settings
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.util.literal import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val licenseActivityOnCreateFingerprint = fingerprint { internal val licenseActivityOnCreateFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
@ -18,13 +17,9 @@ internal val setThemeFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("L") returns("L")
parameters() parameters()
opcodes(Opcode.RETURN_OBJECT)
literal { appearanceStringId } literal { appearanceStringId }
} }
/**
* Added in YouTube v19.04.38.
*/
internal const val CAIRO_CONFIG_LITERAL_VALUE = 45532100L internal const val CAIRO_CONFIG_LITERAL_VALUE = 45532100L
internal val cairoFragmentConfigFingerprint = fingerprint { internal val cairoFragmentConfigFingerprint = fingerprint {

View File

@ -1,9 +1,7 @@
package app.revanced.patches.youtube.misc.settings package app.revanced.patches.youtube.misc.settings
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
@ -13,15 +11,32 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.mapping.get import app.revanced.patches.shared.misc.mapping.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.shared.misc.settings.preference.* import app.revanced.patches.shared.misc.settings.overrideThemeColors
import app.revanced.patches.shared.misc.settings.preference.BasePreference
import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen
import app.revanced.patches.shared.misc.settings.preference.InputType
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.shared.misc.settings.settingsPatch import app.revanced.patches.shared.misc.settings.settingsPatch
import app.revanced.patches.youtube.misc.check.checkEnvironmentPatch import app.revanced.patches.youtube.misc.check.checkEnvironmentPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.fix.playbackspeed.fixPlaybackSpeedWhilePlayingPatch import app.revanced.patches.youtube.misc.fix.playbackspeed.fixPlaybackSpeedWhilePlayingPatch
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.util.* import app.revanced.util.ResourceGroup
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.copyResources
import app.revanced.util.copyXmlNode
import app.revanced.util.findElementByAttributeValueOrThrow
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.inputStreamFromBundledResource
import app.revanced.util.insertLiteralOverride
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
@ -30,7 +45,9 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
import com.android.tools.smali.dexlib2.util.MethodUtil import com.android.tools.smali.dexlib2.util.MethodUtil
// Used by a fingerprint() from SettingsPatch. private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/settings/LicenseActivityHook;"
internal var appearanceStringId = -1L internal var appearanceStringId = -1L
private set private set
@ -69,13 +86,13 @@ private val settingsResourcePatch = resourcePatch {
) )
execute { execute {
// Used for a fingerprint from SettingsPatch.
appearanceStringId = resourceMappings["string", "app_theme_appearance_dark"] appearanceStringId = resourceMappings["string", "app_theme_appearance_dark"]
// Use same colors as stock YouTube.
overrideThemeColors("@color/yt_white1", "@color/yt_black3")
arrayOf( arrayOf(
ResourceGroup("drawable", ResourceGroup("drawable",
"revanced_settings_circle_background.xml",
"revanced_settings_cursor.xml",
"revanced_settings_icon.xml", "revanced_settings_icon.xml",
"revanced_settings_screen_00_about.xml", "revanced_settings_screen_00_about.xml",
"revanced_settings_screen_01_ads.xml", "revanced_settings_screen_01_ads.xml",
@ -92,11 +109,10 @@ private val settingsResourcePatch = resourcePatch {
"revanced_settings_screen_12_video.xml", "revanced_settings_screen_12_video.xml",
), ),
ResourceGroup("layout", ResourceGroup("layout",
"revanced_color_dot_widget.xml",
"revanced_color_picker.xml",
"revanced_preference_with_icon_no_search_result.xml", "revanced_preference_with_icon_no_search_result.xml",
"revanced_search_suggestion_item.xml", "revanced_search_suggestion_item.xml",
"revanced_settings_with_toolbar.xml"), "revanced_settings_with_toolbar.xml"
),
ResourceGroup("menu", "revanced_search_menu.xml") ResourceGroup("menu", "revanced_search_menu.xml")
).forEach { resourceGroup -> ).forEach { resourceGroup ->
copyResources("settings", resourceGroup) copyResources("settings", resourceGroup)
@ -170,12 +186,6 @@ val settingsPatch = bytecodePatch(
checkEnvironmentPatch, checkEnvironmentPatch,
) )
val extensionPackage = "app/revanced/extension/youtube"
val activityHookClassDescriptor = "L$extensionPackage/settings/LicenseActivityHook;"
val themeHelperDescriptor = "L$extensionPackage/ThemeHelper;"
val setThemeMethodName = "setTheme"
execute { execute {
addResources("youtube", "misc.settings.settingsPatch") addResources("youtube", "misc.settings.settingsPatch")
@ -185,7 +195,7 @@ val settingsPatch = bytecodePatch(
icon = "@drawable/revanced_settings_screen_00_about", icon = "@drawable/revanced_settings_screen_00_about",
layout = "@layout/preference_with_icon", layout = "@layout/preference_with_icon",
summaryKey = null, summaryKey = null,
tag = "app.revanced.extension.youtube.settings.preference.ReVancedYouTubeAboutPreference", tag = "app.revanced.extension.shared.settings.preference.ReVancedAboutPreference",
selectable = true, selectable = true,
) )
@ -210,31 +220,10 @@ val settingsPatch = bytecodePatch(
), ),
ListPreference( ListPreference(
key = "revanced_language", key = "revanced_language",
summaryKey = null,
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference" tag = "app.revanced.extension.shared.settings.preference.SortedListPreference"
) )
) )
setThemeFingerprint.method.let { setThemeMethod ->
setThemeMethod.implementation!!.instructions.mapIndexedNotNull { i, instruction ->
if (instruction.opcode == Opcode.RETURN_OBJECT) i else null
}.asReversed().forEach { returnIndex ->
// The following strategy is to replace the return instruction with the setTheme instruction,
// then add a return instruction after the setTheme instruction.
// This is done because the return instruction is a target of another instruction.
setThemeMethod.apply {
// This register is returned by the setTheme method.
val register = getInstruction<OneRegisterInstruction>(returnIndex).registerA
replaceInstruction(
returnIndex,
"invoke-static { v$register }, " +
"$themeHelperDescriptor->$setThemeMethodName(Ljava/lang/Enum;)V",
)
addInstruction(returnIndex + 1, "return-object v$register")
}
}
}
// Modify the license activity and remove all existing layout code. // Modify the license activity and remove all existing layout code.
// Must modify an existing activity and cannot add a new activity to the manifest, // Must modify an existing activity and cannot add a new activity to the manifest,
@ -243,9 +232,9 @@ val settingsPatch = bytecodePatch(
licenseActivityOnCreateFingerprint.method.addInstructions( licenseActivityOnCreateFingerprint.method.addInstructions(
1, 1,
""" """
invoke-static { p0 }, $activityHookClassDescriptor->initialize(Landroid/app/Activity;)V invoke-static { p0 }, $EXTENSION_CLASS_DESCRIPTOR->initialize(Landroid/app/Activity;)V
return-void return-void
""", """
) )
// Remove other methods as they will break as the onCreate method is modified above. // Remove other methods as they will break as the onCreate method is modified above.
@ -267,7 +256,7 @@ val settingsPatch = bytecodePatch(
).toMutable().apply { ).toMutable().apply {
addInstructions( addInstructions(
""" """
invoke-static { p1 }, $activityHookClassDescriptor->getAttachBaseContext(Landroid/content/Context;)Landroid/content/Context; invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->getAttachBaseContext(Landroid/content/Context;)Landroid/content/Context;
move-result-object p1 move-result-object p1
invoke-super { p0, p1 }, $superclass->attachBaseContext(Landroid/content/Context;)V invoke-super { p0, p1 }, $superclass->attachBaseContext(Landroid/content/Context;)V
return-void return-void
@ -278,10 +267,23 @@ val settingsPatch = bytecodePatch(
methods.add(attachBaseContext) methods.add(attachBaseContext)
} }
// Update shared dark mode status based on YT theme.
// This is needed because YT allows forcing light/dark mode
// which then differs from the system dark mode status.
setThemeFingerprint.method.apply {
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructionsAtControlFlowLabel(
index,
"invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->updateLightDarkModeStatus(Ljava/lang/Enum;)V",
)
}
}
// Add setting to force cairo settings fragment on/off. // Add setting to force cairo settings fragment on/off.
cairoFragmentConfigFingerprint.method.insertLiteralOverride( cairoFragmentConfigFingerprint.method.insertLiteralOverride(
CAIRO_CONFIG_LITERAL_VALUE, CAIRO_CONFIG_LITERAL_VALUE,
"$activityHookClassDescriptor->useCairoSettingsFragment(Z)Z" "$EXTENSION_CLASS_DESCRIPTOR->useCairoSettingsFragment(Z)Z"
) )
} }

View File

@ -48,10 +48,7 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
sorting = PreferenceScreenPreference.Sorting.UNSORTED, sorting = PreferenceScreenPreference.Sorting.UNSORTED,
preferences = setOf( preferences = setOf(
SwitchPreference("revanced_spoof_video_streams"), SwitchPreference("revanced_spoof_video_streams"),
ListPreference( ListPreference("revanced_spoof_video_streams_client_type"),
"revanced_spoof_video_streams_client_type",
summaryKey = null,
),
NonInteractivePreference( NonInteractivePreference(
// Requires a key and title but the actual text is chosen at runtime. // Requires a key and title but the actual text is chosen at runtime.
key = "revanced_spoof_video_streams_about_android", key = "revanced_spoof_video_streams_about_android",
@ -59,7 +56,6 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
), ),
ListPreference( ListPreference(
key = "revanced_spoof_video_streams_language", key = "revanced_spoof_video_streams_language",
summaryKey = null,
// Language strings are declared in Setting patch. // Language strings are declared in Setting patch.
entriesKey = "revanced_language_entries", entriesKey = "revanced_language_entries",
entryValuesKey = "revanced_language_entry_values", entryValuesKey = "revanced_language_entry_values",

View File

@ -44,11 +44,20 @@ internal val layoutConstructorFingerprint = fingerprint {
strings("1.0x") strings("1.0x")
} }
internal val mainActivityFingerprint = fingerprint { internal val mainActivityConstructorFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
parameters() parameters()
custom { _, classDef -> custom { _, classDef ->
classDef.endsWith("MainActivity;") classDef.endsWith("/MainActivity;")
}
}
internal val mainActivityOnBackPressedFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters()
custom { method, classDef ->
method.name == "onBackPressed" && classDef.endsWith("/MainActivity;")
} }
} }
@ -56,7 +65,7 @@ internal val mainActivityOnCreateFingerprint = fingerprint {
returns("V") returns("V")
parameters("Landroid/os/Bundle;") parameters("Landroid/os/Bundle;")
custom { method, classDef -> custom { method, classDef ->
method.name == "onCreate" && classDef.endsWith("MainActivity;") method.name == "onCreate" && classDef.endsWith("/MainActivity;")
} }
} }

View File

@ -37,29 +37,25 @@ val rememberVideoQualityPatch = bytecodePatch {
settingsMenuVideoQualityGroup.addAll(listOf( settingsMenuVideoQualityGroup.addAll(listOf(
ListPreference( ListPreference(
key = "revanced_video_quality_default_mobile", key = "revanced_video_quality_default_mobile",
summaryKey = null,
entriesKey = "revanced_video_quality_default_entries", entriesKey = "revanced_video_quality_default_entries",
entryValuesKey = "revanced_video_quality_default_entry_values", entryValuesKey = "revanced_video_quality_default_entry_values"
), ),
ListPreference( ListPreference(
key = "revanced_video_quality_default_wifi", key = "revanced_video_quality_default_wifi",
summaryKey = null,
entriesKey = "revanced_video_quality_default_entries", entriesKey = "revanced_video_quality_default_entries",
entryValuesKey = "revanced_video_quality_default_entry_values", entryValuesKey = "revanced_video_quality_default_entry_values"
), ),
SwitchPreference("revanced_remember_video_quality_last_selected"), SwitchPreference("revanced_remember_video_quality_last_selected"),
ListPreference( ListPreference(
key = "revanced_shorts_quality_default_mobile", key = "revanced_shorts_quality_default_mobile",
summaryKey = null,
entriesKey = "revanced_shorts_quality_default_entries", entriesKey = "revanced_shorts_quality_default_entries",
entryValuesKey = "revanced_shorts_quality_default_entry_values", entryValuesKey = "revanced_shorts_quality_default_entry_values",
), ),
ListPreference( ListPreference(
key = "revanced_shorts_quality_default_wifi", key = "revanced_shorts_quality_default_wifi",
summaryKey = null,
entriesKey = "revanced_shorts_quality_default_entries", entriesKey = "revanced_shorts_quality_default_entries",
entryValuesKey = "revanced_shorts_quality_default_entry_values", entryValuesKey = "revanced_shorts_quality_default_entry_values"
), ),
SwitchPreference("revanced_remember_shorts_quality_last_selected") SwitchPreference("revanced_remember_shorts_quality_last_selected")
)) ))

View File

@ -34,7 +34,6 @@ internal val rememberPlaybackSpeedPatch = bytecodePatch {
listOf( listOf(
ListPreference( ListPreference(
key = "revanced_playback_speed_default", key = "revanced_playback_speed_default",
summaryKey = null,
// Entries and values are set by the extension code based on the actual speeds available. // Entries and values are set by the extension code based on the actual speeds available.
entriesKey = null, entriesKey = null,
entryValuesKey = null, entryValuesKey = null,

View File

@ -742,16 +742,22 @@ private const val RETURN_TYPE_MISMATCH = "Mismatch between override type and Met
* *
* For methods that return an object or any array type, calling this method with `false` * For methods that return an object or any array type, calling this method with `false`
* will force the method to return a `null` value. * will force the method to return a `null` value.
*
* @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Boolean = false) { fun MutableMethod.returnEarly(value: Boolean = false) {
val returnType = returnType.first() val returnType = returnType.first()
check(returnType == 'Z' || (!value && (returnType in setOf('V', 'L', '[')))) { RETURN_TYPE_MISMATCH } check(returnType == 'Z' || (!value && (returnType == 'V' || returnType == 'L' || returnType != '['))) {
RETURN_TYPE_MISMATCH
}
overrideReturnValue(value.toHexString(), false) overrideReturnValue(value.toHexString(), false)
} }
/** /**
* Overrides the first instruction of a method with a constant `Byte` return value. * Overrides the first instruction of a method with a constant `Byte` return value.
* None of the method code will ever execute. * None of the method code will ever execute.
*
* @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Byte) { fun MutableMethod.returnEarly(value: Byte) {
check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH } check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH }
@ -761,6 +767,8 @@ fun MutableMethod.returnEarly(value: Byte) {
/** /**
* Overrides the first instruction of a method with a constant `Short` return value. * Overrides the first instruction of a method with a constant `Short` return value.
* None of the method code will ever execute. * None of the method code will ever execute.
*
* @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Short) { fun MutableMethod.returnEarly(value: Short) {
check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH } check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH }
@ -770,6 +778,8 @@ fun MutableMethod.returnEarly(value: Short) {
/** /**
* Overrides the first instruction of a method with a constant `Char` return value. * Overrides the first instruction of a method with a constant `Char` return value.
* None of the method code will ever execute. * None of the method code will ever execute.
*
* @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Char) { fun MutableMethod.returnEarly(value: Char) {
check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH } check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH }
@ -779,6 +789,8 @@ fun MutableMethod.returnEarly(value: Char) {
/** /**
* Overrides the first instruction of a method with a constant `Int` return value. * Overrides the first instruction of a method with a constant `Int` return value.
* None of the method code will ever execute. * None of the method code will ever execute.
*
* @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Int) { fun MutableMethod.returnEarly(value: Int) {
check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH } check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH }
@ -788,6 +800,8 @@ fun MutableMethod.returnEarly(value: Int) {
/** /**
* Overrides the first instruction of a method with a constant `Long` return value. * Overrides the first instruction of a method with a constant `Long` return value.
* None of the method code will ever execute. * None of the method code will ever execute.
*
* @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Long) { fun MutableMethod.returnEarly(value: Long) {
check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH } check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
@ -797,6 +811,8 @@ fun MutableMethod.returnEarly(value: Long) {
/** /**
* Overrides the first instruction of a method with a constant `Float` return value. * Overrides the first instruction of a method with a constant `Float` return value.
* None of the method code will ever execute. * None of the method code will ever execute.
*
* @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Float) { fun MutableMethod.returnEarly(value: Float) {
check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH } check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH }
@ -806,12 +822,30 @@ fun MutableMethod.returnEarly(value: Float) {
/** /**
* Overrides the first instruction of a method with a constant `Double` return value. * Overrides the first instruction of a method with a constant `Double` return value.
* None of the method code will ever execute. * None of the method code will ever execute.
*
* @see returnLate
*/ */
fun MutableMethod.returnEarly(value: Double) { fun MutableMethod.returnEarly(value: Double) {
check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH } check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false) overrideReturnValue(value.toString(), false)
} }
/**
* Overrides the first instruction of a method with a constant String return value.
* None of the method code will ever execute.
*
* Target method must have return type
* Ljava/lang/String; or Ljava/lang/CharSequence;
*
* @see returnLate
*/
fun MutableMethod.returnEarly(value: String) {
check(returnType == "Ljava/lang/String;" || returnType == "Ljava/lang/CharSequence;") {
RETURN_TYPE_MISMATCH
}
overrideReturnValue(value, false)
}
/** /**
* Overrides all return statements with a constant `Boolean` value. * Overrides all return statements with a constant `Boolean` value.
* All method code is executed the same as unpatched. * All method code is executed the same as unpatched.
@ -826,7 +860,9 @@ fun MutableMethod.returnLate(value: Boolean) {
if (returnType == 'V') { if (returnType == 'V') {
error("Cannot return late for Method of void type") error("Cannot return late for Method of void type")
} }
check(returnType == 'Z' || (!value && returnType in setOf('L', '['))) { RETURN_TYPE_MISMATCH } check(returnType == 'Z' || (!value && (returnType == 'L' || returnType == '['))) {
RETURN_TYPE_MISMATCH
}
overrideReturnValue(value.toHexString(), true) overrideReturnValue(value.toHexString(), true)
} }
@ -908,8 +944,29 @@ fun MutableMethod.returnLate(value: Double) {
overrideReturnValue(value.toString(), true) overrideReturnValue(value.toString(), true)
} }
/**
* Overrides all return statements with a constant String value.
* All method code is executed the same as unpatched.
*
* Target method must have return type
* Ljava/lang/String; or Ljava/lang/CharSequence;
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: String) {
check(returnType == "Ljava/lang/String;" || returnType == "Ljava/lang/CharSequence;") {
RETURN_TYPE_MISMATCH
}
overrideReturnValue(value, true)
}
private fun MutableMethod.overrideReturnValue(value: String, returnLate: Boolean) { private fun MutableMethod.overrideReturnValue(value: String, returnLate: Boolean) {
val instructions = when (returnType.first()) { val instructions = if (returnType == "Ljava/lang/String;" || returnType == "Ljava/lang/CharSequence;" ) {
"""
const-string v0, "$value"
return-object v0
"""
} else when (returnType.first()) {
// If return type is an object, always return null. // If return type is an object, always return null.
'L', '[' -> { 'L', '[' -> {
""" """

View File

@ -33,11 +33,12 @@ Second \"item\" text"</string>
<patch id="misc.settings.settingsResourcePatch"> <patch id="misc.settings.settingsResourcePatch">
<string name="revanced_settings_submenu_title">Settings</string> <string name="revanced_settings_submenu_title">Settings</string>
<string name="revanced_settings_title" translatable="false">ReVanced</string> <string name="revanced_settings_title" translatable="false">ReVanced</string>
<string name="revanced_settings_confirm_user_dialog_title">Do you wish to proceed?</string> <string name="revanced_settings_confirm_user_dialog_title">Are you sure you want to proceed?</string>
<string name="revanced_settings_reset">Reset</string> <string name="revanced_settings_reset">Reset</string>
<string name="revanced_settings_reset_color">Reset color</string> <string name="revanced_settings_reset_color">Reset color</string>
<string name="revanced_settings_color_invalid">Invalid color</string> <string name="revanced_settings_color_invalid">Invalid color</string>
<string name="revanced_settings_restart_title">Refresh and restart</string> <string name="revanced_settings_restart_title">Restart required</string>
<string name="revanced_settings_restart_dialog_message">Restart the app for this change to take affect.</string>
<string name="revanced_settings_restart">Restart</string> <string name="revanced_settings_restart">Restart</string>
<string name="revanced_settings_import">Import</string> <string name="revanced_settings_import">Import</string>
<string name="revanced_settings_import_copy">Copy</string> <string name="revanced_settings_import_copy">Copy</string>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#000000"
android:fillType="evenOdd"
android:pathData="M26.5977,6.29688 L43.8672,36.2031 C45.0195,38.2031,43.5781,40.7031,41.2695,40.7031 L6.73047,40.7031 C4.42188,40.7031,2.98047,38.2031,4.13281,36.2031 L21.4023,6.29688 C22.5547,4.29688,25.4453,4.29688,26.5977,6.29688 Z M24,30 C22.8945,30,22,30.8945,22,32 C22,33.1055,22.8945,34,24,34 C25.1055,34,26,33.1055,26,32 C26,30.8945,25.1055,30,24,30 Z M24,16 C22.9727,16,22.1289,16.7734,22.0117,17.7656 L22,18 L22,26 C22,27.1055,22.8945,28,24,28 C25.0273,28,25.8711,27.2266,25.9883,26.2344 L26,26 L26,18 C26,16.8945,25.1055,16,24,16 Z M24,16" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?android:attr/textColorPrimary"
android:pathData="M14.97,16.95L10,13.87V7h2v5.76l4.03,2.49L14.97,16.95zM22,12c0,5.51 -4.49,10 -10,10S2,17.51 2,12h1c0,4.96 4.04,9 9,9s9,-4.04 9,-9s-4.04,-9 -9,-9C8.81,3 5.92,4.64 4.28,7.38C4.17,7.56 4.06,7.75 3.97,7.94C3.96,7.96 3.95,7.98 3.94,8H8v1H1.96V3h1v4.74C3,7.65 3.03,7.57 3.07,7.49C3.18,7.27 3.3,7.07 3.42,6.86C5.22,3.86 8.51,2 12,2C17.51,2 22,6.49 22,12z"/>
</vector>

View File

@ -2,6 +2,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" android:shape="rectangle"
android:id="@+id/revanced_settings_cursor"> android:id="@+id/revanced_settings_cursor">
<solid android:color="?ytTextPrimary" /> <solid android:color="?android:attr/textColorPrimary" />
<size android:width="1dp" /> <size android:width="1dp" />
</shape> </shape>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?android:attr/textColorPrimary"
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
</vector>

View File

@ -11,14 +11,14 @@
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:fillType="evenOdd" android:fillType="evenOdd"
android:pathData="M19.5872,3.99233 C19.6801,3.77185,19.8789,3.44672,19.7613,3.20362 C19.6963,3.06937,19.5861,3.00019,19.3558,3.00019 L19.1405,3.00019 C18.9457,3.00019,18.773,3.11962,18.6933,3.30335 C17.9185,5.13604,13.8142,14.8277,12.4461,18.066 C12.3664,18.2497,12.1937,18.3646,11.9989,18.3646 C11.8085,18.3646,11.6314,18.2498,11.5562,18.066 C10.1837,14.8278,6.08386,5.13603,5.30905,3.30335 C5.22936,3.11962,5.05669,3.00019,4.86188,3.00019 L4.64558,3.00019 C4.41535,3.00019,4.30543,3.06946,4.24052,3.20362 C4.1229,3.44674,4.32165,3.77185,4.41463,3.99233 C5.88012,7.46938,10.2054,17.7051,11.206,20.066 C11.3123,20.3232,11.5285,20.6,11.803,20.6 L12.2,20.6 C12.4701,20.6,12.6907,20.3232,12.797,20.066 C13.7932,17.7051,18.1217,7.4694,19.5872,3.9923 Z" android:pathData="M19.5872,3.99233 C19.6801,3.77185,19.8789,3.44672,19.7613,3.20362 C19.6963,3.06937,19.5861,3.00019,19.3558,3.00019 L19.1405,3.00019 C18.9457,3.00019,18.773,3.11962,18.6933,3.30335 C17.9185,5.13604,13.8142,14.8277,12.4461,18.066 C12.3664,18.2497,12.1937,18.3646,11.9989,18.3646 C11.8085,18.3646,11.6314,18.2498,11.5562,18.066 C10.1837,14.8278,6.08386,5.13603,5.30905,3.30335 C5.22936,3.11962,5.05669,3.00019,4.86188,3.00019 L4.64558,3.00019 C4.41535,3.00019,4.30543,3.06946,4.24052,3.20362 C4.1229,3.44674,4.32165,3.77185,4.41463,3.99233 C5.88012,7.46938,10.2054,17.7051,11.206,20.066 C11.3123,20.3232,11.5285,20.6,11.803,20.6 L12.2,20.6 C12.4701,20.6,12.6907,20.3232,12.797,20.066 C13.7932,17.7051,18.1217,7.4694,19.5872,3.9923 Z"
android:strokeWidth="1" android:strokeWidth="1"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeMiterLimit="2" /> android:strokeMiterLimit="2" />
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:fillType="evenOdd" android:fillType="evenOdd"
android:pathData="M7.44206,3 C7.23397,3,7.04036,3.11405,6.9341,3.30237 C6.83227,3.49069,6.83227,3.72094,6.9341,3.90926 C7.99669,5.81085,10.4352,10.1975,11.4933,12.0991 C11.5996,12.2874,11.7911,12.4015,11.9992,12.4015 C12.2117,12.4015,12.4009,12.2875,12.5072,12.0991 C13.5654,10.1975,16.0062,5.81085,17.0643,3.90926 C17.1706,3.72094,17.1706,3.49069,17.0643,3.30237 C16.9625,3.11405,16.7665,3,16.5584,3 Z M8.91225,4.09059 L15.1052,4.09059 C15.2375,4.09059,15.361,4.16305,15.4258,4.28281 C15.4934,4.40257,15.4934,4.54965,15.4258,4.66941 C14.7529,5.8787,13.0031,8.89953,12.3302,10.1088 C12.2626,10.2286,12.1427,10.3032,12.0075,10.3032 C11.8752,10.3032,11.7545,10.2286,11.6869,10.1088 C11.014,8.89952,9.2653,5.8787,8.58956,4.66941 C8.52481,4.54965,8.52481,4.40257,8.58956,4.28281 C8.65713,4.16305,8.77992,4.09059,8.91225,4.09059 Z" android:pathData="M7.44206,3 C7.23397,3,7.04036,3.11405,6.9341,3.30237 C6.83227,3.49069,6.83227,3.72094,6.9341,3.90926 C7.99669,5.81085,10.4352,10.1975,11.4933,12.0991 C11.5996,12.2874,11.7911,12.4015,11.9992,12.4015 C12.2117,12.4015,12.4009,12.2875,12.5072,12.0991 C13.5654,10.1975,16.0062,5.81085,17.0643,3.90926 C17.1706,3.72094,17.1706,3.49069,17.0643,3.30237 C16.9625,3.11405,16.7665,3,16.5584,3 Z M8.91225,4.09059 L15.1052,4.09059 C15.2375,4.09059,15.361,4.16305,15.4258,4.28281 C15.4934,4.40257,15.4934,4.54965,15.4258,4.66941 C14.7529,5.8787,13.0031,8.89953,12.3302,10.1088 C12.2626,10.2286,12.1427,10.3032,12.0075,10.3032 C11.8752,10.3032,11.7545,10.2286,11.6869,10.1088 C11.014,8.89952,9.2653,5.8787,8.58956,4.66941 C8.52481,4.54965,8.52481,4.40257,8.58956,4.28281 C8.65713,4.16305,8.77992,4.09059,8.91225,4.09059 Z"
android:strokeWidth="1" android:strokeWidth="1"

View File

@ -22,6 +22,6 @@ Copyright 2022 Google
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M13,17h-2v-6h2V17zM13,7h-2v2h2V7zM12,3c-4.96,0 -9,4.04 -9,9s4.04,9 9,9c4.96,0 9,-4.04 9,-9S16.96,3 12,3M12,2c5.52,0 10,4.48 10,10s-4.48,10 -10,10C6.48,22 2,17.52 2,12S6.48,2 12,2L12,2z" /> android:pathData="M13,17h-2v-6h2V17zM13,7h-2v2h2V7zM12,3c-4.96,0 -9,4.04 -9,9s4.04,9 9,9c4.96,0 9,-4.04 9,-9S16.96,3 12,3M12,2c5.52,0 10,4.48 10,10s-4.48,10 -10,10C6.48,22 2,17.52 2,12S6.48,2 12,2L12,2z" />
</vector> </vector>

View File

@ -22,6 +22,6 @@ Copyright 2022 Google
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M17.9219,12.5 L17.9219,11.5 L21.1523,11.5 L21.1523,12.5 Z M19.0078,18.9609 L16.4219,17.0234 L17.0469,16.2305 L19.6289,18.168 Z M16.9688,7.69141 L16.3477,6.89844 L18.9297,4.96094 L19.5547,5.75391 Z M5.5,17.9609 L5.5,14.1523 L4.46094,14.1523 C4.01563,14.1523,3.63281,13.9961,3.31641,13.6836 C3.00391,13.3672,2.84766,12.9844,2.84766,12.5391 L2.84766,11.4609 C2.84766,11.0156,3.00391,10.6328,3.31641,10.3164 C3.63281,10.0039,4.01563,9.84766,4.46094,9.84766 L8.19141,9.84766 L12.1523,7.5 L12.1523,16.5 L8.19141,14.1523 L6.5,14.1523 L6.5,17.9609 Z M11.1523,14.7188 L11.1523,9.28125 L8.47266,10.8477 L4.46094,10.8477 C4.30859,10.8477,4.16797,10.9102,4.03906,11.0391 C3.91016,11.168,3.84766,11.3086,3.84766,11.4609 L3.84766,12.5391 C3.84766,12.6914,3.91016,12.832,4.03906,12.9609 C4.16797,13.0898,4.30859,13.1523,4.46094,13.1523 L8.47266,13.1523 Z M13.9219,14.8867 L13.9219,9.11328 C14.2578,9.42188,14.5273,9.82813,14.7305,10.332 C14.9375,10.8398,15.0391,11.3945,15.0391,12 C15.0391,12.6055,14.9375,13.1602,14.7305,13.668 C14.5273,14.1719,14.2578,14.5781,13.9219,14.8867 Z M7.5,12 Z M7.5,12" /> android:pathData="M17.9219,12.5 L17.9219,11.5 L21.1523,11.5 L21.1523,12.5 Z M19.0078,18.9609 L16.4219,17.0234 L17.0469,16.2305 L19.6289,18.168 Z M16.9688,7.69141 L16.3477,6.89844 L18.9297,4.96094 L19.5547,5.75391 Z M5.5,17.9609 L5.5,14.1523 L4.46094,14.1523 C4.01563,14.1523,3.63281,13.9961,3.31641,13.6836 C3.00391,13.3672,2.84766,12.9844,2.84766,12.5391 L2.84766,11.4609 C2.84766,11.0156,3.00391,10.6328,3.31641,10.3164 C3.63281,10.0039,4.01563,9.84766,4.46094,9.84766 L8.19141,9.84766 L12.1523,7.5 L12.1523,16.5 L8.19141,14.1523 L6.5,14.1523 L6.5,17.9609 Z M11.1523,14.7188 L11.1523,9.28125 L8.47266,10.8477 L4.46094,10.8477 C4.30859,10.8477,4.16797,10.9102,4.03906,11.0391 C3.91016,11.168,3.84766,11.3086,3.84766,11.4609 L3.84766,12.5391 C3.84766,12.6914,3.91016,12.832,4.03906,12.9609 C4.16797,13.0898,4.30859,13.1523,4.46094,13.1523 L8.47266,13.1523 Z M13.9219,14.8867 L13.9219,9.11328 C14.2578,9.42188,14.5273,9.82813,14.7305,10.332 C14.9375,10.8398,15.0391,11.3945,15.0391,12 C15.0391,12.6055,14.9375,13.1602,14.7305,13.668 C14.5273,14.1719,14.2578,14.5781,13.9219,14.8867 Z M7.5,12 Z M7.5,12" />
</vector> </vector>

View File

@ -12,10 +12,10 @@ Copyright 2023 Ajay Ramachandran <dev@ajay.app>
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:strokeColor="?ytTextPrimary" android:strokeColor="?android:attr/textColorPrimary"
android:strokeWidth="1" android:strokeWidth="1"
android:pathData="M12.0814,1.99794 C9.37251,1.99794,6.97872,3.32778,5.20335,5.14149 C3.42798,6.9552,1.99794,9.39989,1.99794,12.1677 C1.99794,14.9355,3.10403,17.7107,4.87939,19.5244 C6.65476,21.3381,9.37195,21.9549,12.0814,21.9549 C14.7908,21.9549,17.0848,20.9067,18.8601,19.093 C20.6355,17.2793,22.002,14.9355,22.002,12.1677 C22.002,9.40046,20.8525,6.83638,19.0766,5.02267 C17.3013,3.20894,14.7903,1.99794,12.0814,1.99794 Z M11.9105,5.02102 C13.838,5.02102,15.5196,6.09439,16.7829,7.35711 C18.0462,8.61984,18.8878,10.3004,18.8878,12.2279 C18.8878,14.1554,18.2513,16.0427,16.988,17.3054 C15.7247,18.5681,13.8374,18.9333,11.9105,18.9333 C9.98355,18.9333,8.36976,18.2962,7.10645,17.0335 C5.84314,15.7708,5.11222,14.1554,5.11222,12.2278 C5.11222,10.3003,5.63697,8.47868,6.8997,7.21537 C8.16239,5.95218,9.98293,5.02102,11.9105,5.02102 Z" /> android:pathData="M12.0814,1.99794 C9.37251,1.99794,6.97872,3.32778,5.20335,5.14149 C3.42798,6.9552,1.99794,9.39989,1.99794,12.1677 C1.99794,14.9355,3.10403,17.7107,4.87939,19.5244 C6.65476,21.3381,9.37195,21.9549,12.0814,21.9549 C14.7908,21.9549,17.0848,20.9067,18.8601,19.093 C20.6355,17.2793,22.002,14.9355,22.002,12.1677 C22.002,9.40046,20.8525,6.83638,19.0766,5.02267 C17.3013,3.20894,14.7903,1.99794,12.0814,1.99794 Z M11.9105,5.02102 C13.838,5.02102,15.5196,6.09439,16.7829,7.35711 C18.0462,8.61984,18.8878,10.3004,18.8878,12.2279 C18.8878,14.1554,18.2513,16.0427,16.988,17.3054 C15.7247,18.5681,13.8374,18.9333,11.9105,18.9333 C9.98355,18.9333,8.36976,18.2962,7.10645,17.0335 C5.84314,15.7708,5.11222,14.1554,5.11222,12.2278 C5.11222,10.3003,5.63697,8.47868,6.8997,7.21537 C8.16239,5.95218,9.98293,5.02102,11.9105,5.02102 Z" />
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M15.3108,11.899 C15.3108,13.6514,13.8411,15.1264,12.0887,15.1264 C10.3363,15.1264,8.97704,13.6515,8.97704,11.899 C8.97704,10.1466,10.3363,8.71961,12.0887,8.71961 C13.8411,8.71961,15.3108,10.1466,15.3108,11.899 Z" /> android:pathData="M15.3108,11.899 C15.3108,13.6514,13.8411,15.1264,12.0887,15.1264 C10.3363,15.1264,8.97704,13.6515,8.97704,11.899 C8.97704,10.1466,10.3363,8.71961,12.0887,8.71961 C13.8411,8.71961,15.3108,10.1466,15.3108,11.899 Z" />
</vector> </vector>

View File

@ -22,6 +22,6 @@ Copyright 2022 Google
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M4.85,19.775Q4.15,19.775 3.688,19.312Q3.225,18.85 3.225,18.15V12.55H4.225V18.15Q4.225,18.375 4.425,18.575Q4.625,18.775 4.85,18.775H12.45V19.775ZM8.625,16Q7.925,16 7.463,15.537Q7,15.075 7,14.375V8.775H8V14.375Q8,14.625 8.188,14.812Q8.375,15 8.625,15H16.225V16ZM12.375,12.225Q11.7,12.225 11.238,11.762Q10.775,11.3 10.775,10.625V5.85Q10.775,5.15 11.238,4.687Q11.7,4.225 12.375,4.225H19.15Q19.85,4.225 20.312,4.687Q20.775,5.15 20.775,5.85V10.625Q20.775,11.3 20.312,11.762Q19.85,12.225 19.15,12.225ZM12.375,11.225H19.15Q19.375,11.225 19.575,11.037Q19.775,10.85 19.775,10.625V7.225H11.775V10.625Q11.775,10.85 11.963,11.037Q12.15,11.225 12.375,11.225Z"/> android:pathData="M4.85,19.775Q4.15,19.775 3.688,19.312Q3.225,18.85 3.225,18.15V12.55H4.225V18.15Q4.225,18.375 4.425,18.575Q4.625,18.775 4.85,18.775H12.45V19.775ZM8.625,16Q7.925,16 7.463,15.537Q7,15.075 7,14.375V8.775H8V14.375Q8,14.625 8.188,14.812Q8.375,15 8.625,15H16.225V16ZM12.375,12.225Q11.7,12.225 11.238,11.762Q10.775,11.3 10.775,10.625V5.85Q10.775,5.15 11.238,4.687Q11.7,4.225 12.375,4.225H19.15Q19.85,4.225 20.312,4.687Q20.775,5.15 20.775,5.85V10.625Q20.775,11.3 20.312,11.762Q19.85,12.225 19.15,12.225ZM12.375,11.225H19.15Q19.375,11.225 19.575,11.037Q19.775,10.85 19.775,10.625V7.225H11.775V10.625Q11.775,10.85 11.963,11.037Q12.15,11.225 12.375,11.225Z"/>
</vector> </vector>

View File

@ -22,6 +22,6 @@ Copyright 2022 Google
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M12,9.5c1.38,0 2.5,1.12 2.5,2.5s-1.12,2.5 -2.5,2.5S9.5,13.38 9.5,12S10.62,9.5 12,9.5M12,8.5c-1.93,0 -3.5,1.57 -3.5,3.5s1.57,3.5 3.5,3.5s3.5,-1.57 3.5,-3.5S13.93,8.5 12,8.5L12,8.5zM13.22,3l0.55,2.2l0.13,0.51l0.5,0.18c0.61,0.23 1.19,0.56 1.72,0.98l0.4,0.32l0.5,-0.14l2.17,-0.62l1.22,2.11l-1.63,1.59l-0.37,0.36l0.08,0.51c0.05,0.32 0.08,0.64 0.08,0.98s-0.03,0.66 -0.08,0.98l-0.08,0.51l0.37,0.36l1.63,1.59l-1.22,2.11l-2.17,-0.62l-0.5,-0.14l-0.4,0.32c-0.53,0.43 -1.11,0.76 -1.72,0.98l-0.5,0.18l-0.13,0.51L13.22,21h-2.44l-0.55,-2.2l-0.13,-0.51l-0.5,-0.18C9,17.88 8.42,17.55 7.88,17.12l-0.4,-0.32l-0.5,0.14l-2.17,0.62L3.6,15.44l1.63,-1.59l0.37,-0.36l-0.08,-0.51C5.47,12.66 5.44,12.33 5.44,12s0.03,-0.66 0.08,-0.98l0.08,-0.51l-0.37,-0.36L3.6,8.56l1.22,-2.11l2.17,0.62l0.5,0.14l0.4,-0.32C8.42,6.45 9,6.12 9.61,5.9l0.5,-0.18l0.13,-0.51L10.78,3H13.22M14,2h-4L9.26,4.96c-0.73,0.27 -1.4,0.66 -2,1.14L4.34,5.27l-2,3.46l2.19,2.13C4.47,11.23 4.44,11.61 4.44,12s0.03,0.77 0.09,1.14l-2.19,2.13l2,3.46l2.92,-0.83c0.6,0.48 1.27,0.87 2,1.14L10,22h4l0.74,-2.96c0.73,-0.27 1.4,-0.66 2,-1.14l2.92,0.83l2,-3.46l-2.19,-2.13c0.06,-0.37 0.09,-0.75 0.09,-1.14s-0.03,-0.77 -0.09,-1.14l2.19,-2.13l-2,-3.46L16.74,6.1c-0.6,-0.48 -1.27,-0.87 -2,-1.14L14,2L14,2z" /> android:pathData="M12,9.5c1.38,0 2.5,1.12 2.5,2.5s-1.12,2.5 -2.5,2.5S9.5,13.38 9.5,12S10.62,9.5 12,9.5M12,8.5c-1.93,0 -3.5,1.57 -3.5,3.5s1.57,3.5 3.5,3.5s3.5,-1.57 3.5,-3.5S13.93,8.5 12,8.5L12,8.5zM13.22,3l0.55,2.2l0.13,0.51l0.5,0.18c0.61,0.23 1.19,0.56 1.72,0.98l0.4,0.32l0.5,-0.14l2.17,-0.62l1.22,2.11l-1.63,1.59l-0.37,0.36l0.08,0.51c0.05,0.32 0.08,0.64 0.08,0.98s-0.03,0.66 -0.08,0.98l-0.08,0.51l0.37,0.36l1.63,1.59l-1.22,2.11l-2.17,-0.62l-0.5,-0.14l-0.4,0.32c-0.53,0.43 -1.11,0.76 -1.72,0.98l-0.5,0.18l-0.13,0.51L13.22,21h-2.44l-0.55,-2.2l-0.13,-0.51l-0.5,-0.18C9,17.88 8.42,17.55 7.88,17.12l-0.4,-0.32l-0.5,0.14l-2.17,0.62L3.6,15.44l1.63,-1.59l0.37,-0.36l-0.08,-0.51C5.47,12.66 5.44,12.33 5.44,12s0.03,-0.66 0.08,-0.98l0.08,-0.51l-0.37,-0.36L3.6,8.56l1.22,-2.11l2.17,0.62l0.5,0.14l0.4,-0.32C8.42,6.45 9,6.12 9.61,5.9l0.5,-0.18l0.13,-0.51L10.78,3H13.22M14,2h-4L9.26,4.96c-0.73,0.27 -1.4,0.66 -2,1.14L4.34,5.27l-2,3.46l2.19,2.13C4.47,11.23 4.44,11.61 4.44,12s0.03,0.77 0.09,1.14l-2.19,2.13l2,3.46l2.92,-0.83c0.6,0.48 1.27,0.87 2,1.14L10,22h4l0.74,-2.96c0.73,-0.27 1.4,-0.66 2,-1.14l2.92,0.83l2,-3.46l-2.19,-2.13c0.06,-0.37 0.09,-0.75 0.09,-1.14s-0.03,-0.77 -0.09,-1.14l2.19,-2.13l-2,-3.46L16.74,6.1c-0.6,-0.48 -1.27,-0.87 -2,-1.14L14,2L14,2z" />
</vector> </vector>

View File

@ -22,6 +22,6 @@ Copyright 2022 Google
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M11.5,20.5 L11.5,15.5 L12.5,15.5 L12.5,17.5 L20.5,17.5 L20.5,18.5 L12.5,18.5 L12.5,20.5 Z M3.5,18.5 L3.5,17.5 L8.5,17.5 L8.5,18.5 Z M7.5,14.5 L7.5,12.5 L3.5,12.5 L3.5,11.5 L7.5,11.5 L7.5,9.5 L8.5,9.5 L8.5,14.5 Z M11.5,12.5 L11.5,11.5 L20.5,11.5 L20.5,12.5 Z M15.5,8.5 L15.5,3.5 L16.5,3.5 L16.5,5.5 L20.5,5.5 L20.5,6.5 L16.5,6.5 L16.5,8.5 Z M3.5,6.5 L3.5,5.5 L12.5,5.5 L12.5,6.5 Z M3.5,6.5" /> android:pathData="M11.5,20.5 L11.5,15.5 L12.5,15.5 L12.5,17.5 L20.5,17.5 L20.5,18.5 L12.5,18.5 L12.5,20.5 Z M3.5,18.5 L3.5,17.5 L8.5,17.5 L8.5,18.5 Z M7.5,14.5 L7.5,12.5 L3.5,12.5 L3.5,11.5 L7.5,11.5 L7.5,9.5 L8.5,9.5 L8.5,14.5 Z M11.5,12.5 L11.5,11.5 L20.5,11.5 L20.5,12.5 Z M15.5,8.5 L15.5,3.5 L16.5,3.5 L16.5,5.5 L20.5,5.5 L20.5,6.5 L16.5,6.5 L16.5,8.5 Z M3.5,6.5 L3.5,5.5 L12.5,5.5 L12.5,6.5 Z M3.5,6.5" />
</vector> </vector>

View File

@ -21,9 +21,9 @@ Copyright 2022 Google
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M8.54688,22.2031 C7.00781,22.2031,5.51172,21.375,4.73047,19.9219 C3.66797,17.9414,4.32031,15.4531,6.21875,14.25 C6.23047,14.2422,6.24219,14.2344,6.25391,14.2305 C6.40625,14.1523,6.55469,14.0703,6.70703,13.9844 C6.59375,13.9219,6.48438,13.8594,6.37109,13.793 C5.20703,13.1211,4.49219,12.1758,4.29688,11.0508 C4.04297,9.55078,4.04297,7.65625,6.32031,6.28516 C8.07031,5.23828,9.86719,4.21484,11.6016,3.22266 C12.1797,2.89453,12.7539,2.56641,13.332,2.23438 C14.5313,1.54688,16.0195,1.51953,17.3008,2.16016 C18.5977,2.80469,19.4805,4.01563,19.6641,5.40625 C19.9297,7.17578,19.0469,8.94531,17.4609,9.80469 C17.3594,9.86328,17.2578,9.91797,17.1602,9.97656 C17.2539,10.0313,17.3477,10.0859,17.4414,10.1406 C18.7422,10.8711,19.5664,12.1172,19.6953,13.5547 C19.8242,14.9766,19.25,16.332,18.1211,17.2734 C17.7617,17.5664,17.3633,17.793,16.9805,18.0078 C16.8672,18.0703,16.7578,18.1328,16.6484,18.1953 C14.8711,19.2344,12.7617,20.457,10.5859,21.6875 C9.9375,22.0352,9.23828,22.2031,8.54688,22.2031 Z M6.69141,15.0313 C5.20703,15.9805,4.69922,17.9375,5.53125,19.4922 C6.42578,21.1484,8.49609,21.7773,10.1445,20.8906 C12.3086,19.668,14.4141,18.4453,16.1875,17.4102 C16.3008,17.3438,16.418,17.2773,16.5313,17.2148 C16.8984,17.0078,17.2461,16.8125,17.543,16.5703 C18.4336,15.8281,18.8906,14.7578,18.7891,13.6367 C18.6836,12.5039,18.0313,11.5156,16.9922,10.9336 C16.8281,10.8359,16.6641,10.7383,16.4961,10.6367 C16.3516,10.5508,16.2031,10.4609,16.043,10.3711 C15.9063,10.2891,15.8203,10.1445,15.8164,9.98438 C15.8125,9.82422,15.8984,9.67188,16.0352,9.58984 C16.3516,9.39063,16.6641,9.20703,17.0195,9.00781 C18.2773,8.32813,18.9727,6.92969,18.7617,5.53125 C18.6172,4.4375,17.9219,3.48438,16.8984,2.97656 C15.8828,2.47266,14.7188,2.49219,13.7813,3.02344 C13.207,3.35547,12.6289,3.68359,12.0547,4.01172 C10.3242,5.00391,8.53125,6.02344,6.78906,7.06641 C5.34375,7.9375,4.88281,9.04688,5.19531,10.8984 C5.34375,11.7539,5.89063,12.4648,6.82422,13 C7.15234,13.1875,7.47656,13.375,7.83984,13.5898 C7.97656,13.6719,8.0625,13.8203,8.0625,13.9805 C8.0625,14.1406,7.98047,14.2891,7.83984,14.375 C7.44531,14.6133,7.08203,14.8281,6.69141,15.0313 Z M6.69141,15.0313" /> android:pathData="M8.54688,22.2031 C7.00781,22.2031,5.51172,21.375,4.73047,19.9219 C3.66797,17.9414,4.32031,15.4531,6.21875,14.25 C6.23047,14.2422,6.24219,14.2344,6.25391,14.2305 C6.40625,14.1523,6.55469,14.0703,6.70703,13.9844 C6.59375,13.9219,6.48438,13.8594,6.37109,13.793 C5.20703,13.1211,4.49219,12.1758,4.29688,11.0508 C4.04297,9.55078,4.04297,7.65625,6.32031,6.28516 C8.07031,5.23828,9.86719,4.21484,11.6016,3.22266 C12.1797,2.89453,12.7539,2.56641,13.332,2.23438 C14.5313,1.54688,16.0195,1.51953,17.3008,2.16016 C18.5977,2.80469,19.4805,4.01563,19.6641,5.40625 C19.9297,7.17578,19.0469,8.94531,17.4609,9.80469 C17.3594,9.86328,17.2578,9.91797,17.1602,9.97656 C17.2539,10.0313,17.3477,10.0859,17.4414,10.1406 C18.7422,10.8711,19.5664,12.1172,19.6953,13.5547 C19.8242,14.9766,19.25,16.332,18.1211,17.2734 C17.7617,17.5664,17.3633,17.793,16.9805,18.0078 C16.8672,18.0703,16.7578,18.1328,16.6484,18.1953 C14.8711,19.2344,12.7617,20.457,10.5859,21.6875 C9.9375,22.0352,9.23828,22.2031,8.54688,22.2031 Z M6.69141,15.0313 C5.20703,15.9805,4.69922,17.9375,5.53125,19.4922 C6.42578,21.1484,8.49609,21.7773,10.1445,20.8906 C12.3086,19.668,14.4141,18.4453,16.1875,17.4102 C16.3008,17.3438,16.418,17.2773,16.5313,17.2148 C16.8984,17.0078,17.2461,16.8125,17.543,16.5703 C18.4336,15.8281,18.8906,14.7578,18.7891,13.6367 C18.6836,12.5039,18.0313,11.5156,16.9922,10.9336 C16.8281,10.8359,16.6641,10.7383,16.4961,10.6367 C16.3516,10.5508,16.2031,10.4609,16.043,10.3711 C15.9063,10.2891,15.8203,10.1445,15.8164,9.98438 C15.8125,9.82422,15.8984,9.67188,16.0352,9.58984 C16.3516,9.39063,16.6641,9.20703,17.0195,9.00781 C18.2773,8.32813,18.9727,6.92969,18.7617,5.53125 C18.6172,4.4375,17.9219,3.48438,16.8984,2.97656 C15.8828,2.47266,14.7188,2.49219,13.7813,3.02344 C13.207,3.35547,12.6289,3.68359,12.0547,4.01172 C10.3242,5.00391,8.53125,6.02344,6.78906,7.06641 C5.34375,7.9375,4.88281,9.04688,5.19531,10.8984 C5.34375,11.7539,5.89063,12.4648,6.82422,13 C7.15234,13.1875,7.47656,13.375,7.83984,13.5898 C7.97656,13.6719,8.0625,13.8203,8.0625,13.9805 C8.0625,14.1406,7.98047,14.2891,7.83984,14.375 C7.44531,14.6133,7.08203,14.8281,6.69141,15.0313 Z M6.69141,15.0313" />
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M10.0703,15.3555 C9.99219,15.3555,9.91406,15.3359,9.84375,15.2969 C9.69922,15.2148,9.61328,15.0625,9.61328,14.9023 L9.61328,9.08594 C9.61328,8.92188,9.69922,8.76953,9.83984,8.69141 C9.98438,8.60938,10.1563,8.60938,10.2969,8.69141 L15.3281,11.5898 C15.4688,11.6719,15.5547,11.8242,15.5547,11.9844 C15.5586,12.1484,15.4688,12.3008,15.3281,12.3789 L10.2969,15.2969 C10.2266,15.3359,10.1484,15.3555,10.0703,15.3555 Z M10.5234,9.875 L10.5234,14.1094 L14.1914,11.9883 Z M10.5234,9.875" /> android:pathData="M10.0703,15.3555 C9.99219,15.3555,9.91406,15.3359,9.84375,15.2969 C9.69922,15.2148,9.61328,15.0625,9.61328,14.9023 L9.61328,9.08594 C9.61328,8.92188,9.69922,8.76953,9.83984,8.69141 C9.98438,8.60938,10.1563,8.60938,10.2969,8.69141 L15.3281,11.5898 C15.4688,11.6719,15.5547,11.8242,15.5547,11.9844 C15.5586,12.1484,15.4688,12.3008,15.3281,12.3789 L10.2969,15.2969 C10.2266,15.3359,10.1484,15.3555,10.0703,15.3555 Z M10.5234,9.875 L10.5234,14.1094 L14.1914,11.9883 Z M10.5234,9.875" />
</vector> </vector>

View File

@ -22,6 +22,6 @@ Copyright 2022 Google
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M4.5,14Q3.65,14 3.075,13.425Q2.5,12.85 2.5,12Q2.5,11.15 3.075,10.575Q3.65,10 4.5,10Q5.2,10 5.738,10.425Q6.275,10.85 6.425,11.5H21.5V12.5H6.425Q6.275,13.15 5.738,13.575Q5.2,14 4.5,14Z"/> android:pathData="M4.5,14Q3.65,14 3.075,13.425Q2.5,12.85 2.5,12Q2.5,11.15 3.075,10.575Q3.65,10 4.5,10Q5.2,10 5.738,10.425Q6.275,10.85 6.425,11.5H21.5V12.5H6.425Q6.275,13.15 5.738,13.575Q5.2,14 4.5,14Z"/>
</vector> </vector>

View File

@ -22,6 +22,6 @@ Copyright 2022 Google
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M3,21 L3,20.1328 L5.3125,20.1328 C4.33203,18.9844,3.54297,17.7266,2.95703,16.3516 C2.37109,14.9805,2.07813,13.5313,2.07813,12.0078 C2.07813,10.4883,2.37109,9.04297,2.95703,7.67578 C3.54297,6.30859,4.33203,5.04688,5.3125,3.88281 L3,3.88281 L3,3 L7.09766,3 L7.09766,7.11719 L6.23047,7.11719 L6.23047,4.17578 C5.25,5.28906,4.45703,6.50391,3.85156,7.82422 C3.24609,9.14063,2.94141,10.5352,2.94141,12.0078 C2.94141,13.4844,3.24609,14.875,3.85156,16.1875 C4.45703,17.4961,5.25,18.7031,6.23047,19.8047 L6.23047,16.9023 L7.09766,16.9023 L7.09766,21 Z M16.2969,19.8047 C15.9883,19.9141,15.668,19.9648,15.3359,19.9531 C15,19.9453,14.6836,19.8711,14.3789,19.7266 L8.0625,16.7891 L8.35938,16.1953 C8.46094,16.0156,8.59375,15.8711,8.75781,15.7656 C8.92188,15.6563,9.10547,15.5938,9.30859,15.5781 L12.1602,15.2578 L9.30469,7.44922 C9.25391,7.3125,9.26172,7.18359,9.32031,7.0625 C9.38281,6.94531,9.48047,6.85938,9.61719,6.80859 C9.75391,6.76172,9.88281,6.76563,10.0039,6.82813 C10.1211,6.88672,10.207,6.98438,10.2539,7.12109 L13.5508,16.1797 L9.80078,16.5078 L14.8086,18.8242 C14.9766,18.8984,15.1602,18.9453,15.3672,18.957 C15.5703,18.9727,15.7617,18.9453,15.9414,18.8711 L19.3867,17.6211 C20.043,17.3867,20.5195,16.957,20.8086,16.332 C21.1016,15.7109,21.1289,15.0703,20.8945,14.4102 L19.5195,10.6602 C19.4688,10.5234,19.4727,10.3945,19.5234,10.2734 C19.5781,10.1523,19.6719,10.0703,19.8125,10.0195 C19.9492,9.97266,20.0781,9.97266,20.1992,10.0273 C20.3203,10.0781,20.4023,10.1758,20.4531,10.3125 L21.8281,14.0625 C22.1719,14.9844,22.1406,15.8789,21.7383,16.75 C21.332,17.6172,20.668,18.2188,19.7422,18.5547 Z M14.5273,13.5469 L13.1563,9.76953 C13.1094,9.63281,13.1133,9.50391,13.1758,9.38281 C13.2344,9.26563,13.332,9.17969,13.4727,9.13281 C13.6094,9.08203,13.7383,9.08594,13.8555,9.14844 C13.9766,9.20703,14.0586,9.30469,14.1094,9.44141 L15.4844,13.1914 Z M17.1992,12.5586 L16.1719,9.73438 C16.125,9.59766,16.1289,9.46875,16.1914,9.35547 C16.25,9.23828,16.3477,9.16016,16.4844,9.11328 C16.625,9.0625,16.7539,9.06641,16.8711,9.12109 C16.9922,9.17188,17.0781,9.26563,17.125,9.40625 L18.1484,12.2266 Z M17.0898,15.2578 Z M17.0898,15.2578" /> android:pathData="M3,21 L3,20.1328 L5.3125,20.1328 C4.33203,18.9844,3.54297,17.7266,2.95703,16.3516 C2.37109,14.9805,2.07813,13.5313,2.07813,12.0078 C2.07813,10.4883,2.37109,9.04297,2.95703,7.67578 C3.54297,6.30859,4.33203,5.04688,5.3125,3.88281 L3,3.88281 L3,3 L7.09766,3 L7.09766,7.11719 L6.23047,7.11719 L6.23047,4.17578 C5.25,5.28906,4.45703,6.50391,3.85156,7.82422 C3.24609,9.14063,2.94141,10.5352,2.94141,12.0078 C2.94141,13.4844,3.24609,14.875,3.85156,16.1875 C4.45703,17.4961,5.25,18.7031,6.23047,19.8047 L6.23047,16.9023 L7.09766,16.9023 L7.09766,21 Z M16.2969,19.8047 C15.9883,19.9141,15.668,19.9648,15.3359,19.9531 C15,19.9453,14.6836,19.8711,14.3789,19.7266 L8.0625,16.7891 L8.35938,16.1953 C8.46094,16.0156,8.59375,15.8711,8.75781,15.7656 C8.92188,15.6563,9.10547,15.5938,9.30859,15.5781 L12.1602,15.2578 L9.30469,7.44922 C9.25391,7.3125,9.26172,7.18359,9.32031,7.0625 C9.38281,6.94531,9.48047,6.85938,9.61719,6.80859 C9.75391,6.76172,9.88281,6.76563,10.0039,6.82813 C10.1211,6.88672,10.207,6.98438,10.2539,7.12109 L13.5508,16.1797 L9.80078,16.5078 L14.8086,18.8242 C14.9766,18.8984,15.1602,18.9453,15.3672,18.957 C15.5703,18.9727,15.7617,18.9453,15.9414,18.8711 L19.3867,17.6211 C20.043,17.3867,20.5195,16.957,20.8086,16.332 C21.1016,15.7109,21.1289,15.0703,20.8945,14.4102 L19.5195,10.6602 C19.4688,10.5234,19.4727,10.3945,19.5234,10.2734 C19.5781,10.1523,19.6719,10.0703,19.8125,10.0195 C19.9492,9.97266,20.0781,9.97266,20.1992,10.0273 C20.3203,10.0781,20.4023,10.1758,20.4531,10.3125 L21.8281,14.0625 C22.1719,14.9844,22.1406,15.8789,21.7383,16.75 C21.332,17.6172,20.668,18.2188,19.7422,18.5547 Z M14.5273,13.5469 L13.1563,9.76953 C13.1094,9.63281,13.1133,9.50391,13.1758,9.38281 C13.2344,9.26563,13.332,9.17969,13.4727,9.13281 C13.6094,9.08203,13.7383,9.08594,13.8555,9.14844 C13.9766,9.20703,14.0586,9.30469,14.1094,9.44141 L15.4844,13.1914 Z M17.1992,12.5586 L16.1719,9.73438 C16.125,9.59766,16.1289,9.46875,16.1914,9.35547 C16.25,9.23828,16.3477,9.16016,16.4844,9.11328 C16.625,9.0625,16.7539,9.06641,16.8711,9.12109 C16.9922,9.17188,17.0781,9.26563,17.125,9.40625 L18.1484,12.2266 Z M17.0898,15.2578 Z M17.0898,15.2578" />
</vector> </vector>

View File

@ -22,6 +22,6 @@ Copyright 2022 Google
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M3.625,15Q3,15 2.5,14.5Q2,14 2,13.375V12.15Q2,12.025 2.025,11.862Q2.05,11.7 2.1,11.55L4.85,5.075Q5.05,4.625 5.538,4.312Q6.025,4 6.55,4H16.575V15L10.3,21.2L9.875,20.75Q9.725,20.625 9.638,20.4Q9.55,20.175 9.55,20V19.85L10.575,15ZM15.575,5H6.55Q6.325,5 6.1,5.112Q5.875,5.225 5.775,5.5L3,12V13.375Q3,13.65 3.175,13.825Q3.35,14 3.625,14H11.8L10.65,19.45L15.575,14.575ZM15.575,14.575V14Q15.575,14 15.575,13.825Q15.575,13.65 15.575,13.375V12V5.5Q15.575,5.225 15.575,5.112Q15.575,5 15.575,5ZM16.575,15V14H20V5H16.575V4H21V15Z"/> android:pathData="M3.625,15Q3,15 2.5,14.5Q2,14 2,13.375V12.15Q2,12.025 2.025,11.862Q2.05,11.7 2.1,11.55L4.85,5.075Q5.05,4.625 5.538,4.312Q6.025,4 6.55,4H16.575V15L10.3,21.2L9.875,20.75Q9.725,20.625 9.638,20.4Q9.55,20.175 9.55,20V19.85L10.575,15ZM15.575,5H6.55Q6.325,5 6.1,5.112Q5.875,5.225 5.775,5.5L3,12V13.375Q3,13.65 3.175,13.825Q3.35,14 3.625,14H11.8L10.65,19.45L15.575,14.575ZM15.575,14.575V14Q15.575,14 15.575,13.825Q15.575,13.65 15.575,13.375V12V5.5Q15.575,5.225 15.575,5.112Q15.575,5 15.575,5ZM16.575,15V14H20V5H16.575V4H21V15Z"/>
</vector> </vector>

View File

@ -11,6 +11,6 @@ Copyright 2021 Ajay Ramachandran <dev@ajay.app>
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M 12.000145,2.0000008 C 8.8230689,1.9990926 5.6959192,2.7864027 2.9017488,4.2906678 2.3373945,4.5948398 1.9899198,5.1860103 2.000223,5.8244635 2.0930396,12.358829 5.4926743,18.31271 11.094442,21.749998 c 0.557183,0.333336 1.253849,0.333336 1.811031,0 5.601767,-3.438045 9.001096,-9.391169 9.094295,-15.9255345 0.01052,-0.6386247 -0.337035,-1.2300179 -0.9016,-1.5341683 -2.794107,-1.5040456 -5.92111,-2.2912233 -9.098023,-2.2902944 z m 0.08082,0.8705548 c 3.003625,0.013255 5.957553,0.7636027 8.599879,2.1845129 0.277414,0.151228 0.448533,0.4421907 0.44513,0.7568723 C 21.034684,12.23921 17.58825,17.8544 12.446767,21.009378 c -0.274165,0.167124 -0.619386,0.167124 -0.893551,0 C 6.4117365,17.854399 2.9652339,12.239209 2.8739372,5.8119397 2.8705209,5.4972741 3.0416092,5.2063196 3.3189962,5.0550685 6.0095892,3.608201 9.0224769,2.8570356 12.080969,2.8705556 Z M 9.6351953,6.7701615 v 8.3406435 l 7.2606727,-4.170358 z"/> android:pathData="M 12.000145,2.0000008 C 8.8230689,1.9990926 5.6959192,2.7864027 2.9017488,4.2906678 2.3373945,4.5948398 1.9899198,5.1860103 2.000223,5.8244635 2.0930396,12.358829 5.4926743,18.31271 11.094442,21.749998 c 0.557183,0.333336 1.253849,0.333336 1.811031,0 5.601767,-3.438045 9.001096,-9.391169 9.094295,-15.9255345 0.01052,-0.6386247 -0.337035,-1.2300179 -0.9016,-1.5341683 -2.794107,-1.5040456 -5.92111,-2.2912233 -9.098023,-2.2902944 z m 0.08082,0.8705548 c 3.003625,0.013255 5.957553,0.7636027 8.599879,2.1845129 0.277414,0.151228 0.448533,0.4421907 0.44513,0.7568723 C 21.034684,12.23921 17.58825,17.8544 12.446767,21.009378 c -0.274165,0.167124 -0.619386,0.167124 -0.893551,0 C 6.4117365,17.854399 2.9652339,12.239209 2.8739372,5.8119397 2.8705209,5.4972741 3.0416092,5.2063196 3.3189962,5.0550685 6.0095892,3.608201 9.0224769,2.8570356 12.080969,2.8705556 Z M 9.6351953,6.7701615 v 8.3406435 l 7.2606727,-4.170358 z"/>
</vector> </vector>

View File

@ -22,6 +22,6 @@ Copyright 2022 Google
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M16.3,11.95 L12.075,7.725 16.3,3.5 20.525,7.725ZM4.625,10.625V4.625H10.625V10.625ZM13.375,19.375V13.375H19.375V19.375ZM4.625,19.375V13.375H10.625V19.375ZM5.625,9.625H9.625V5.625H5.625ZM16.325,10.575 L19.15,7.75 16.325,4.925 13.5,7.75ZM14.375,18.375H18.375V14.375H14.375ZM5.625,18.375H9.625V14.375H5.625ZM9.625,9.625ZM13.5,7.75ZM9.625,14.375ZM14.375,14.375Z"/> android:pathData="M16.3,11.95 L12.075,7.725 16.3,3.5 20.525,7.725ZM4.625,10.625V4.625H10.625V10.625ZM13.375,19.375V13.375H19.375V19.375ZM4.625,19.375V13.375H10.625V19.375ZM5.625,9.625H9.625V5.625H5.625ZM16.325,10.575 L19.15,7.75 16.325,4.925 13.5,7.75ZM14.375,18.375H18.375V14.375H14.375ZM5.625,18.375H9.625V14.375H5.625ZM9.625,9.625ZM13.5,7.75ZM9.625,14.375ZM14.375,14.375Z"/>
</vector> </vector>

View File

@ -22,6 +22,6 @@ Copyright 2022 Google
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="?ytTextPrimary" android:fillColor="?android:attr/textColorPrimary"
android:pathData="M12,19H4.625Q3.925,19 3.463,18.538Q3,18.075 3,17.375V6.625Q3,5.925 3.463,5.463Q3.925,5 4.625,5H19.375Q20.075,5 20.538,5.463Q21,5.925 21,6.625V11H20V6.625Q20,6.35 19.825,6.175Q19.65,6 19.375,6H4.625Q4.35,6 4.175,6.175Q4,6.35 4,6.625V17.375Q4,17.65 4.175,17.825Q4.35,18 4.625,18H12ZM10,15.575V8.425L15.575,12ZM17.85,20.8 L17.75,19.95Q17.175,19.825 16.8,19.6Q16.425,19.375 16.1,19.025L15.3,19.4L14.725,18.525L15.45,17.95Q15.25,17.425 15.25,16.925Q15.25,16.425 15.45,15.9L14.725,15.3L15.3,14.45L16.1,14.8Q16.425,14.475 16.8,14.25Q17.175,14.025 17.75,13.9L17.85,13.05H18.85L18.95,13.9Q19.525,14.025 19.9,14.25Q20.275,14.475 20.6,14.825L21.4,14.45L21.975,15.325L21.25,15.9Q21.45,16.425 21.45,16.925Q21.45,17.425 21.25,17.95L21.975,18.525L21.4,19.4L20.6,19.025Q20.275,19.375 19.9,19.6Q19.525,19.825 18.95,19.95L18.85,20.8ZM18.35,19.075Q19.225,19.075 19.863,18.438Q20.5,17.8 20.5,16.925Q20.5,16.05 19.863,15.413Q19.225,14.775 18.35,14.775Q17.475,14.775 16.837,15.413Q16.2,16.05 16.2,16.925Q16.2,17.8 16.837,18.438Q17.475,19.075 18.35,19.075Z"/> android:pathData="M12,19H4.625Q3.925,19 3.463,18.538Q3,18.075 3,17.375V6.625Q3,5.925 3.463,5.463Q3.925,5 4.625,5H19.375Q20.075,5 20.538,5.463Q21,5.925 21,6.625V11H20V6.625Q20,6.35 19.825,6.175Q19.65,6 19.375,6H4.625Q4.35,6 4.175,6.175Q4,6.35 4,6.625V17.375Q4,17.65 4.175,17.825Q4.35,18 4.625,18H12ZM10,15.575V8.425L15.575,12ZM17.85,20.8 L17.75,19.95Q17.175,19.825 16.8,19.6Q16.425,19.375 16.1,19.025L15.3,19.4L14.725,18.525L15.45,17.95Q15.25,17.425 15.25,16.925Q15.25,16.425 15.45,15.9L14.725,15.3L15.3,14.45L16.1,14.8Q16.425,14.475 16.8,14.25Q17.175,14.025 17.75,13.9L17.85,13.05H18.85L18.95,13.9Q19.525,14.025 19.9,14.25Q20.275,14.475 20.6,14.825L21.4,14.45L21.975,15.325L21.25,15.9Q21.45,16.425 21.45,16.925Q21.45,17.425 21.25,17.95L21.975,18.525L21.4,19.4L20.6,19.025Q20.275,19.375 19.9,19.6Q19.525,19.825 18.95,19.95L18.85,20.8ZM18.35,19.075Q19.225,19.075 19.863,18.438Q20.5,17.8 20.5,16.925Q20.5,16.05 19.863,15.413Q19.225,14.775 18.35,14.775Q17.475,14.775 16.837,15.413Q16.2,16.05 16.2,16.925Q16.2,17.8 16.837,18.438Q17.475,19.075 18.35,19.075Z"/>
</vector> </vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?android:attr/textColorPrimary"
android:pathData="M11.0882,3 C6.62072,3,3,6.62368,3,11.0911 C3,15.5615,6.62368,19.1822,11.0911,19.1822 C13.172,19.1822,15.0691,18.3954,16.5029,17.1039 L20.094,20.695 C21.1138,19.6754,19.8953,20.8968,21,19.7921 L17.3822,16.1773 C18.5074,14.7874,19.1822,13.018,19.1822,11.0911 C19.1793,6.62072,15.5556,3,11.0882,3 Z M11.0882,4.27895 C14.851,4.27895,17.9004,7.32829,17.9004,11.0911 C17.9004,14.851,14.8511,17.9003,11.0882,17.9003 C7.32537,17.9003,4.27603,14.851,4.27603,11.0911 C4.27898,7.32827,7.32833,4.28189,11.0882,4.27893 Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M11.5,3.5 L3.5,11.5 L11.5,19.5 L12.3145,18.6914 L5.69531,12.0723 L21,12.0723 L21,10.9277 L5.69531,10.9277 L12.3145,4.30859 Z"/>
</vector>

View File

@ -1,27 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
'android.R.style.ThemeOverlay_Material_Dialog' is a theme added in Android 6.0+.
'android:windowBackground' is the background color of the alert dialog.
If not overridden, the background color of the parent theme 'android.R.style.ThemeOverlay_Material_Dialog' is used.
'R.attr.ytRaisedBackground' is the background color of the YouTube alert dialog.
'android:windowIsTranslucent' makes the dialog window transparent.
'android:background' needs to be transparent for the 'contextual action bar' to be displayed properly.
Referenced documentation and framework source code:
https://android.googlesource.com/platform/frameworks/base/+/android-6.0.1_r81/core/res/res/values/themes_material.xml#1142
https://developer.android.com/reference/android/R.style#ThemeOverlay_Material_Dialog
https://developer.android.com/about/versions/marshmallow/android-6.0-changes#behavior-text-selection
-->
<resources> <resources>
<style name="revanced_edit_text_dialog_style" parent="@android:style/ThemeOverlay.Material.Dialog">
<item name="android:windowBackground">?ytRaisedBackground</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:background">@android:color/transparent</item>
</style>
<style name="revanced_searchbar_cursor"> <style name="revanced_searchbar_cursor">
<item name="android:textCursorDrawable">@drawable/revanced_settings_cursor</item> <item name="android:textCursorDrawable">@drawable/revanced_settings_cursor</item>
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>

View File

@ -6,6 +6,7 @@
android:layout_gravity="center" android:layout_gravity="center"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false"> android:clipToPadding="false">
<View <View
android:id="@+id/revanced_color_dot_widget" android:id="@+id/revanced_color_dot_widget"
android:layout_width="20dp" android:layout_width="20dp"
@ -15,4 +16,5 @@
android:elevation="2dp" android:elevation="2dp"
android:translationZ="2dp" android:translationZ="2dp"
android:outlineProvider="background" /> android:outlineProvider="background" />
</FrameLayout> </FrameLayout>

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<app.revanced.extension.shared.settings.preference.ColorPickerView <app.revanced.extension.shared.settings.preference.ColorPickerView
android:id="@+id/color_picker_view" android:id="@+id/revanced_color_picker_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" /> android:layout_height="0dp" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/revanced_check_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:src="@drawable/revanced_settings_custom_checkmark"
android:visibility="gone"
android:contentDescription="@null" />
<View
android:id="@+id/revanced_check_icon_placeholder"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:visibility="invisible" />
<TextView
android:id="@+id/revanced_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>

View File

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="54dp" android:minHeight="54dp">
xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView <ImageView
android:id="@android:id/icon" android:id="@android:id/icon"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -12,6 +13,7 @@
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginHorizontal="18dp" android:layout_marginHorizontal="18dp"
android:contentDescription="@null" /> android:contentDescription="@null" />
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="0dp" android:layout_width="0dp"
@ -19,21 +21,24 @@
android:layout_weight="1" android:layout_weight="1"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:paddingVertical="8dp"> android:paddingVertical="8dp">
<TextView <TextView
android:id="@android:id/title" android:id="@android:id/title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size" android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?ytTextPrimary" android:textColor="?android:attr/textColorPrimary"
android:textAlignment="viewStart" /> android:textAlignment="viewStart" />
<TextView <TextView
android:id="@android:id/summary" android:id="@android:id/summary"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/small_font_size" android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?ytTextSecondary" android:textColor="?android:attr/textColorSecondary"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:maxLines="2" android:maxLines="2"
android:ellipsize="end" /> android:ellipsize="end" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -17,7 +17,7 @@
android:layout_height="24dp" android:layout_height="24dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:contentDescription="History icon" android:contentDescription="History icon"
android:src="@drawable/yt_outline_arrow_time_vd_theme_24" /> android:src="@drawable/revanced_settings_arrow_time" />
<!-- Suggestion text --> <!-- Suggestion text -->
<TextView <TextView

View File

@ -13,17 +13,17 @@
<FrameLayout <FrameLayout
android:id="@+id/revanced_toolbar_parent" android:id="@+id/revanced_toolbar_parent"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?android:attr/actionBarSize"
android:background="@color/yt_white1" android:background="@android:color/white"
android:elevation="0dp"> android:elevation="0dp">
<!-- Toolbar --> <!-- Toolbar -->
<android.support.v7.widget.Toolbar <android.support.v7.widget.Toolbar
android:id="@+id/revanced_toolbar" android:id="@+id/revanced_toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?android:attr/actionBarSize"
android:background="?attr/ytBrandBackgroundSolid" android:background="@android:color/white"
app:navigationIcon="@drawable/yt_outline_arrow_left_black_24" app:navigationIcon="@drawable/revanced_settings_toolbar_arrow_left"
app:title="@string/revanced_settings_title" /> app:title="@string/revanced_settings_title" />
<!-- Container for SearchView --> <!-- Container for SearchView -->

View File

@ -2,7 +2,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item <item
android:id="@+id/action_search" android:id="@+id/action_search"
android:icon="@drawable/yt_outline_search_black_24" android:icon="@drawable/revanced_settings_search_icon"
android:title="" android:title=""
android:showAsAction="always"/> android:showAsAction="always"/>
</menu> </menu>