chore: Merge branch dev
to main
(#5160)
This commit is contained in:
56
CHANGELOG.md
56
CHANGELOG.md
@ -1,3 +1,59 @@
|
|||||||
|
# [5.28.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.7...v5.28.0-dev.8) (2025-06-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([4ad4887](https://github.com/ReVanced/revanced-patches/commit/4ad488744d87543c31e453dc7b6d8182b3a7f440))
|
||||||
|
|
||||||
|
# [5.28.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.6...v5.28.0-dev.7) (2025-06-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([c9e54e1](https://github.com/ReVanced/revanced-patches/commit/c9e54e1d36243945ac1ec3108fe38edf0e15d772))
|
||||||
|
|
||||||
|
# [5.28.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.5...v5.28.0-dev.6) (2025-06-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Threads - Hide ads:** Constrain patch to the last working app target ([#5189](https://github.com/ReVanced/revanced-patches/issues/5189)) ([3558c44](https://github.com/ReVanced/revanced-patches/commit/3558c44a05c13f19fefdbbf14b364181a79f17c0))
|
||||||
|
|
||||||
|
# [5.28.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.4...v5.28.0-dev.5) (2025-06-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Pandora - Disable ads:** Support latest app target ([#5185](https://github.com/ReVanced/revanced-patches/issues/5185)) ([ca83047](https://github.com/ReVanced/revanced-patches/commit/ca83047f5c4acbb267d5b98db80ad111999086e0))
|
||||||
|
|
||||||
|
# [5.28.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.3...v5.28.0-dev.4) (2025-06-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Use modern style settings dialogs ([#5109](https://github.com/ReVanced/revanced-patches/issues/5109)) ([312b6dc](https://github.com/ReVanced/revanced-patches/commit/312b6dc04e01c2758cd304ca8606306027aa2f01))
|
||||||
|
|
||||||
|
# [5.28.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.2...v5.28.0-dev.3) (2025-06-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify:** Fix `Hide Create button` and `Sanitize sharing links` for older but supported app targets ([#5159](https://github.com/ReVanced/revanced-patches/issues/5159)) ([e7dd061](https://github.com/ReVanced/revanced-patches/commit/e7dd061c513af90861c0ab0d7adc6ee43be57ce2))
|
||||||
|
|
||||||
|
# [5.28.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.1...v5.28.0-dev.2) (2025-06-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([a93d74d](https://github.com/ReVanced/revanced-patches/commit/a93d74d26e7ef87a3745df2b9fe82722d65a0e59))
|
||||||
|
|
||||||
|
# [5.28.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0-dev.1) (2025-06-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([8736b6a](https://github.com/ReVanced/revanced-patches/commit/8736b6a80b48cb1f4562c9f9919804006ddb18bd))
|
||||||
|
|
||||||
# [5.27.0](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.27.0) (2025-06-09)
|
# [5.27.0](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.27.0) (2025-06-09)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,15 +1,23 @@
|
|||||||
package app.revanced.extension.messenger.metaai;
|
package app.revanced.extension.messenger.metaai;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class RemoveMetaAIPatch {
|
public class RemoveMetaAIPatch {
|
||||||
|
private static final Set<Long> loggedIDs = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
|
||||||
public static boolean overrideBooleanFlag(long id, boolean value) {
|
public static boolean overrideBooleanFlag(long id, boolean value) {
|
||||||
// This catches all flag IDs related to Meta AI.
|
try {
|
||||||
// The IDs change slightly with every update,
|
if (Long.toString(id).startsWith("REPLACED_BY_PATCH")) {
|
||||||
// so to work around this, IDs from different versions were compared
|
if (loggedIDs.add(id))
|
||||||
// to find what they have in common, which turned out to be those first bits.
|
Logger.printInfo(() -> "Overriding " + id + " from " + value + " to false");
|
||||||
// TODO: Find the specific flags that we care about and patch the code they control instead.
|
|
||||||
if ((id & 0x7FFFFFC000000000L) == 0x810A8000000000L) {
|
return false;
|
||||||
return false;
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "overrideBooleanFlag failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,8 @@ import app.revanced.extension.shared.settings.preference.LogBufferManager;
|
|||||||
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
|
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
|
||||||
* and additionally accessible thru {@link LogBufferManager}.
|
* and additionally accessible thru {@link LogBufferManager}.
|
||||||
*
|
*
|
||||||
* All methods are thread safe.
|
* All methods are thread safe, and are safe to call even
|
||||||
|
* if {@link Utils#getContext()} is not available.
|
||||||
*/
|
*/
|
||||||
public class Logger {
|
public class Logger {
|
||||||
|
|
||||||
@ -138,6 +139,20 @@ public class Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldLogDebug() {
|
||||||
|
// If the app is still starting up and the context is not yet set,
|
||||||
|
// then allow debug logging regardless what the debug setting actually is.
|
||||||
|
return Utils.context == null || DEBUG.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean shouldShowErrorToast() {
|
||||||
|
return Utils.context != null && DEBUG_TOAST_ON_ERROR.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean includeStackTrace() {
|
||||||
|
return Utils.context != null && DEBUG_STACKTRACE.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs debug messages under the outer class name of the code calling this method.
|
* Logs debug messages under the outer class name of the code calling this method.
|
||||||
* <p>
|
* <p>
|
||||||
@ -157,8 +172,8 @@ public class Logger {
|
|||||||
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
|
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
|
||||||
*/
|
*/
|
||||||
public static void printDebug(LogMessage message, @Nullable Exception ex) {
|
public static void printDebug(LogMessage message, @Nullable Exception ex) {
|
||||||
if (DEBUG.get()) {
|
if (shouldLogDebug()) {
|
||||||
logInternal(LogLevel.DEBUG, message, ex, DEBUG_STACKTRACE.get(), false);
|
logInternal(LogLevel.DEBUG, message, ex, includeStackTrace(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +188,7 @@ public class Logger {
|
|||||||
* Logs information messages using the outer class name of the code calling this method.
|
* Logs information messages using the outer class name of the code calling this method.
|
||||||
*/
|
*/
|
||||||
public static void printInfo(LogMessage message, @Nullable Exception ex) {
|
public static void printInfo(LogMessage message, @Nullable Exception ex) {
|
||||||
logInternal(LogLevel.INFO, message, ex, DEBUG_STACKTRACE.get(), false);
|
logInternal(LogLevel.INFO, message, ex, includeStackTrace(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -194,22 +209,6 @@ public class Logger {
|
|||||||
* @param ex exception (optional)
|
* @param ex exception (optional)
|
||||||
*/
|
*/
|
||||||
public static void printException(LogMessage message, @Nullable Throwable ex) {
|
public static void printException(LogMessage message, @Nullable Throwable ex) {
|
||||||
logInternal(LogLevel.ERROR, message, ex, DEBUG_STACKTRACE.get(), DEBUG_TOAST_ON_ERROR.get());
|
logInternal(LogLevel.ERROR, message, ex, includeStackTrace(), shouldShowErrorToast());
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
|
|
||||||
* Normally this method should not be used.
|
|
||||||
*/
|
|
||||||
public static void initializationInfo(LogMessage message) {
|
|
||||||
logInternal(LogLevel.INFO, message, null, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
|
|
||||||
* Normally this method should not be used.
|
|
||||||
*/
|
|
||||||
public static void initializationException(LogMessage message, @Nullable Exception ex) {
|
|
||||||
logInternal(LogLevel.ERROR, message, ex, false, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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,38 +92,59 @@ 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) -> {
|
() -> {
|
||||||
final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE);
|
// Action for the OK (website) button.
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE);
|
||||||
activity.startActivity(intent);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
activity.startActivity(intent);
|
||||||
|
|
||||||
// Shutdown to prevent the user from navigating back to this app,
|
// Shutdown to prevent the user from navigating back to this app,
|
||||||
// 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) -> {
|
() -> {
|
||||||
// Cleanup data if the user incorrectly imported a huge negative number.
|
// Neutral button action.
|
||||||
final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get());
|
// Cleanup data if the user incorrectly imported a huge negative number.
|
||||||
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1);
|
final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get());
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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++;
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -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;
|
||||||
@ -182,7 +184,7 @@ public class ColorPickerPreference extends EditTextPreference {
|
|||||||
* @throws IllegalArgumentException If the color string is invalid.
|
* @throws IllegalArgumentException If the color string is invalid.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final void setText(String colorString) {
|
public final void setText(String colorString) {
|
||||||
try {
|
try {
|
||||||
Logger.printDebug(() -> "setText: " + colorString);
|
Logger.printDebug(() -> "setText: " + colorString);
|
||||||
super.setText(colorString);
|
super.setText(colorString);
|
||||||
@ -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
|
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
|
||||||
Utils.setEditTextDialogTheme(builder);
|
|
||||||
LinearLayout dialogLayout = createDialogLayout(builder.getContext());
|
|
||||||
builder.setView(dialogLayout);
|
|
||||||
final int originalColor = currentColor;
|
|
||||||
|
|
||||||
builder.setNeutralButton(str("revanced_settings_reset_color"), null);
|
|
||||||
|
|
||||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
|
||||||
try {
|
|
||||||
String colorString = getEditText().getText().toString();
|
|
||||||
|
|
||||||
if (colorString.length() != COLOR_STRING_LENGTH) {
|
|
||||||
Utils.showToastShort(str("revanced_settings_color_invalid"));
|
|
||||||
setText(getColorString(originalColor));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setText(colorString);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
// Should never happen due to a bad color string,
|
|
||||||
// since the text is validated and fixed while the user types.
|
|
||||||
Logger.printException(() -> "setPositiveButton failure", ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
|
||||||
try {
|
|
||||||
// Restore the original color.
|
|
||||||
setText(getColorString(originalColor));
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "setNegativeButton failure", ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void showDialog(Bundle state) {
|
protected void showDialog(Bundle state) {
|
||||||
super.showDialog(state);
|
Context context = getContext();
|
||||||
|
|
||||||
AlertDialog dialog = (AlertDialog) getDialog();
|
// Inflate color picker view.
|
||||||
dialog.setCanceledOnTouchOutside(false);
|
View colorPicker = LayoutInflater.from(context).inflate(
|
||||||
|
getResourceIdentifier("revanced_color_picker", "layout"), null);
|
||||||
|
dialogColorPickerView = colorPicker.findViewById(
|
||||||
|
getResourceIdentifier("revanced_color_picker_view", "id"));
|
||||||
|
dialogColorPickerView.setColor(currentColor);
|
||||||
|
|
||||||
// Do not close dialog when reset is pressed.
|
// Horizontal layout for preview and EditText.
|
||||||
Button button = dialog.getButton(AlertDialog.BUTTON_NEUTRAL);
|
LinearLayout inputLayout = new LinearLayout(context);
|
||||||
button.setOnClickListener(view -> {
|
inputLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
try {
|
inputLayout.setPadding(0, 0, 0, dipToPixels(10));
|
||||||
final int defaultColor = Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF;
|
|
||||||
// Setting view color causes listener callback into this class.
|
dialogColorPreview = new TextView(context);
|
||||||
dialogColorPickerView.setColor(defaultColor);
|
LinearLayout.LayoutParams previewParams = new LinearLayout.LayoutParams(
|
||||||
} catch (Exception ex) {
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
Logger.printException(() -> "setOnClickListener failure", ex);
|
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 {
|
||||||
|
String colorString = editText.getText().toString();
|
||||||
|
if (colorString.length() != COLOR_STRING_LENGTH) {
|
||||||
|
Utils.showToastShort(str("revanced_settings_color_invalid"));
|
||||||
|
setText(getColorString(originalColor));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setText(colorString);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// Should never happen due to a bad color string,
|
||||||
|
// since the text is validated and fixed while the user types.
|
||||||
|
Logger.printException(() -> "OK button failure", ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
// Cancel button action.
|
||||||
|
try {
|
||||||
|
// Restore the original color.
|
||||||
|
setText(getColorString(originalColor));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Cancel button failure", ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
str("revanced_settings_reset_color"), // Neutral button text.
|
||||||
|
() -> {
|
||||||
|
// Neutral button action.
|
||||||
|
try {
|
||||||
|
final int defaultColor = Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF;
|
||||||
|
// Setting view color causes listener callback into this class.
|
||||||
|
dialogColorPickerView.setColor(defaultColor);
|
||||||
|
} catch (Exception 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
|
||||||
|
@ -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,12 +63,12 @@ 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;
|
||||||
/**
|
/**
|
||||||
* Hue fill radius. Use slightly smaller radius for the selector handle fill,
|
* Hue fill radius. Use slightly smaller radius for the selector handle fill,
|
||||||
* otherwise the anti-aliasing causes the fill color to bleed past the selector outline.
|
* otherwise the anti-aliasing causes the fill color to bleed past the selector outline.
|
||||||
*/
|
*/
|
||||||
private static final float SELECTOR_FILL_RADIUS = SELECTOR_RADIUS - SELECTOR_STROKE_WIDTH / 2;
|
private static final float SELECTOR_FILL_RADIUS = SELECTOR_RADIUS - SELECTOR_STROKE_WIDTH / 2;
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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 = non breaking hyphen.
|
return text.replace("-", "‑"); // #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 {
|
||||||
@ -315,7 +317,7 @@ class AboutLinksRoutes {
|
|||||||
// Do not show an exception toast if the server is down
|
// Do not show an exception toast if the server is down
|
||||||
final int responseCode = connection.getResponseCode();
|
final int responseCode = connection.getResponseCode();
|
||||||
if (responseCode != 200) {
|
if (responseCode != 200) {
|
||||||
Logger.printDebug(() -> "Failed to get social links. Response code: " + responseCode);
|
Logger.printDebug(() -> "Failed to get social links. Response code: " + responseCode);
|
||||||
return NO_CONNECTION_STATIC_LINKS;
|
return NO_CONNECTION_STATIC_LINKS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
@ -44,41 +58,61 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
|||||||
this.setting = setting;
|
this.setting = setting;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
|
||||||
super.onPrepareDialogBuilder(builder);
|
|
||||||
Utils.setEditTextDialogTheme(builder);
|
|
||||||
|
|
||||||
if (setting == null) {
|
|
||||||
String key = getKey();
|
|
||||||
if (key != null) {
|
|
||||||
setting = Setting.getSettingFromPath(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setting != null) {
|
|
||||||
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void showDialog(Bundle state) {
|
protected void showDialog(Bundle state) {
|
||||||
super.showDialog(state);
|
try {
|
||||||
|
Context context = getContext();
|
||||||
|
EditText editText = getEditText();
|
||||||
|
|
||||||
// Override the button click listener to prevent dismissing the dialog.
|
// Resolve setting if not already set.
|
||||||
Button button = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEUTRAL);
|
if (setting == null) {
|
||||||
if (button == null) {
|
String key = getKey();
|
||||||
return;
|
if (key != null) {
|
||||||
}
|
setting = Setting.getSettingFromPath(key);
|
||||||
button.setOnClickListener(v -> {
|
}
|
||||||
try {
|
|
||||||
String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString();
|
|
||||||
EditText editText = getEditText();
|
|
||||||
editText.setText(defaultStringValue);
|
|
||||||
editText.setSelection(defaultStringValue.length()); // move cursor to end of text
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "reset failure", ex);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
// 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) {
|
||||||
|
try {
|
||||||
|
String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString();
|
||||||
|
editText.setText(defaultStringValue);
|
||||||
|
editText.setSelection(defaultStringValue.length()); // Move cursor to end of text.
|
||||||
|
} catch (Exception 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,20 +37,24 @@ public final class HideCreateButtonPatch {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String stringifiedNavigationBarItem = navigationBarItem.toString();
|
try {
|
||||||
|
String stringifiedNavigationBarItem = navigationBarItem.toString();
|
||||||
|
|
||||||
for (ComponentFilter componentFilter : CREATE_BUTTON_COMPONENT_FILTERS) {
|
for (ComponentFilter componentFilter : CREATE_BUTTON_COMPONENT_FILTERS) {
|
||||||
if (componentFilter.filterUnavailable()) {
|
if (componentFilter.filterUnavailable()) {
|
||||||
Logger.printInfo(() -> "returnNullIfIsCreateButton: Filter " +
|
Logger.printInfo(() -> "returnNullIfIsCreateButton: Filter " +
|
||||||
componentFilter.getFilterRepresentation() + " not available, skipping");
|
componentFilter.getFilterRepresentation() + " not available, skipping");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stringifiedNavigationBarItem.contains(componentFilter.getFilterValue())) {
|
if (stringifiedNavigationBarItem.contains(componentFilter.getFilterValue())) {
|
||||||
Logger.printInfo(() -> "Hiding Create button because the navigation bar item " + navigationBarItem +
|
Logger.printInfo(() -> "Hiding Create button because the navigation bar item " +
|
||||||
" matched the filter " + componentFilter.getFilterRepresentation());
|
navigationBarItem + " matched the filter " + componentFilter.getFilterRepresentation());
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "returnNullIfIsCreateButton failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return navigationBarItem;
|
return navigationBarItem;
|
||||||
|
@ -143,7 +143,7 @@ public final class UnlockPremiumPatch {
|
|||||||
originalValue = ((com.spotify.remoteconfig.internal.AccountAttribute) attribute).value_;
|
originalValue = ((com.spotify.remoteconfig.internal.AccountAttribute) attribute).value_;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overrideValue == originalValue) {
|
if (overrideValue.equals(originalValue)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,37 +202,41 @@ public final class UnlockPremiumPatch {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String stringifiedContextMenuItem = contextMenuItem.toString();
|
try {
|
||||||
|
String stringifiedContextMenuItem = contextMenuItem.toString();
|
||||||
|
|
||||||
for (List<ComponentFilter> componentFilters : CONTEXT_MENU_ITEMS_COMPONENT_FILTERS) {
|
for (List<ComponentFilter> componentFilters : CONTEXT_MENU_ITEMS_COMPONENT_FILTERS) {
|
||||||
boolean allMatch = true;
|
boolean allMatch = true;
|
||||||
StringBuilder matchedFilterRepresentations = new StringBuilder();
|
StringBuilder matchedFilterRepresentations = new StringBuilder();
|
||||||
|
|
||||||
for (int i = 0, filterSize = componentFilters.size(); i < filterSize; i++) {
|
for (int i = 0, filterSize = componentFilters.size(); i < filterSize; i++) {
|
||||||
ComponentFilter componentFilter = componentFilters.get(i);
|
ComponentFilter componentFilter = componentFilters.get(i);
|
||||||
|
|
||||||
if (componentFilter.filterUnavailable()) {
|
if (componentFilter.filterUnavailable()) {
|
||||||
Logger.printInfo(() -> "isFilteredContextMenuItem: Filter " +
|
Logger.printInfo(() -> "isFilteredContextMenuItem: Filter " +
|
||||||
componentFilter.getFilterRepresentation() + " not available, skipping");
|
componentFilter.getFilterRepresentation() + " not available, skipping");
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stringifiedContextMenuItem.contains(componentFilter.getFilterValue())) {
|
||||||
|
allMatch = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchedFilterRepresentations.append(componentFilter.getFilterRepresentation());
|
||||||
|
if (i < filterSize - 1) {
|
||||||
|
matchedFilterRepresentations.append(", ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stringifiedContextMenuItem.contains(componentFilter.getFilterValue())) {
|
if (allMatch) {
|
||||||
allMatch = false;
|
Logger.printInfo(() -> "Filtering context menu item " + stringifiedContextMenuItem +
|
||||||
break;
|
" because the following filters matched: " + matchedFilterRepresentations);
|
||||||
}
|
return true;
|
||||||
|
|
||||||
matchedFilterRepresentations.append(componentFilter.getFilterRepresentation());
|
|
||||||
if (i < filterSize - 1) {
|
|
||||||
matchedFilterRepresentations.append(", ");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
if (allMatch) {
|
Logger.printException(() -> "isFilteredContextMenuItem failure", ex);
|
||||||
Logger.printInfo(() -> "Filtering context menu item " + stringifiedContextMenuItem +
|
|
||||||
" because the following filters matched: " + matchedFilterRepresentations);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package app.revanced.extension.spotify.shared;
|
package app.revanced.extension.spotify.shared;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
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;
|
||||||
|
|
||||||
public final class ComponentFilters {
|
public final class ComponentFilters {
|
||||||
|
|
||||||
public interface ComponentFilter {
|
public interface ComponentFilter {
|
||||||
|
@NonNull
|
||||||
String getFilterValue();
|
String getFilterValue();
|
||||||
String getFilterRepresentation();
|
String getFilterRepresentation();
|
||||||
default boolean filterUnavailable() {
|
default boolean filterUnavailable() {
|
||||||
@ -20,7 +23,8 @@ public final class ComponentFilters {
|
|||||||
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
|
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
|
||||||
// 0 is returned when a resource has not been found.
|
// 0 is returned when a resource has not been found.
|
||||||
private int resourceId = -1;
|
private int resourceId = -1;
|
||||||
private String stringfiedResourceId = null;
|
@Nullable
|
||||||
|
private String stringfiedResourceId;
|
||||||
|
|
||||||
public ResourceIdComponentFilter(String resourceName, String resourceType) {
|
public ResourceIdComponentFilter(String resourceName, String resourceType) {
|
||||||
this.resourceName = resourceName;
|
this.resourceName = resourceName;
|
||||||
@ -34,6 +38,7 @@ public final class ComponentFilters {
|
|||||||
return resourceId;
|
return resourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String getFilterValue() {
|
public String getFilterValue() {
|
||||||
if (stringfiedResourceId == null) {
|
if (stringfiedResourceId == null) {
|
||||||
@ -66,6 +71,7 @@ public final class ComponentFilters {
|
|||||||
this.string = string;
|
this.string = string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String getFilterValue() {
|
public String getFilterValue() {
|
||||||
return string;
|
return string;
|
||||||
|
@ -16,7 +16,7 @@ public class SpoofSimPatch {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.initializationException(() -> "Context is not yet set, cannot spoof: " + fieldSpoofed, null);
|
Logger.printException(() -> "Context is not yet set, cannot spoof: " + fieldSpoofed, null);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
||||||
|
@ -7,11 +7,17 @@ public class VersionCheckPatch {
|
|||||||
return Utils.getAppVersionName().compareTo(version) >= 0;
|
return Utils.getAppVersionName().compareTo(version) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static final boolean IS_19_17_OR_GREATER = isVersionOrGreater("19.17.00");
|
public static final boolean IS_19_17_OR_GREATER = isVersionOrGreater("19.17.00");
|
||||||
|
@Deprecated
|
||||||
public static final boolean IS_19_20_OR_GREATER = isVersionOrGreater("19.20.00");
|
public static final boolean IS_19_20_OR_GREATER = isVersionOrGreater("19.20.00");
|
||||||
|
@Deprecated
|
||||||
public static final boolean IS_19_21_OR_GREATER = isVersionOrGreater("19.21.00");
|
public static final boolean IS_19_21_OR_GREATER = isVersionOrGreater("19.21.00");
|
||||||
|
@Deprecated
|
||||||
public static final boolean IS_19_26_OR_GREATER = isVersionOrGreater("19.26.00");
|
public static final boolean IS_19_26_OR_GREATER = isVersionOrGreater("19.26.00");
|
||||||
|
@Deprecated
|
||||||
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
|
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
|
||||||
|
@Deprecated
|
||||||
public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00");
|
public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00");
|
||||||
public static final boolean IS_19_46_OR_GREATER = isVersionOrGreater("19.46.00");
|
public static final boolean IS_19_46_OR_GREATER = isVersionOrGreater("19.46.00");
|
||||||
}
|
}
|
||||||
|
@ -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";
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,69 +265,74 @@ 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,
|
||||||
|
category.title.toString(), // Title.
|
||||||
|
null, // No message (replaced by contentLayout).
|
||||||
|
null, // No EditText.
|
||||||
|
null, // OK button text.
|
||||||
|
() -> {
|
||||||
|
// OK button action.
|
||||||
|
if (selectedDialogEntryIndex >= 0 && getEntryValues() != null) {
|
||||||
|
String value = getEntryValues()[selectedDialogEntryIndex].toString();
|
||||||
|
if (callChangeListener(value)) {
|
||||||
|
setValue(value);
|
||||||
|
category.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value)));
|
||||||
|
SegmentCategory.updateEnabledCategories();
|
||||||
|
}
|
||||||
|
|
||||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
try {
|
||||||
onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
category.setColor(dialogColorEditText.getText().toString());
|
||||||
});
|
category.setOpacity(categoryOpacity);
|
||||||
builder.setNeutralButton(str("revanced_settings_reset_color"), null);
|
} catch (IllegalArgumentException ex) {
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
Utils.showToastShort(str("revanced_settings_color_invalid"));
|
||||||
|
}
|
||||||
|
|
||||||
selectedDialogEntryIndex = findIndexOfValue(getValue());
|
updateUI();
|
||||||
builder.setSingleChoiceItems(getEntries(), selectedDialogEntryIndex,
|
}
|
||||||
(dialog, which) -> selectedDialogEntryIndex = which);
|
},
|
||||||
|
() -> {}, // 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) {
|
||||||
|
Logger.printException(() -> "resetButton onClick failure", ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "onPrepareDialogBuilder failure", ex);
|
Logger.printException(() -> "showDialog 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
|
@Override
|
||||||
protected void onDialogClosed(boolean positiveResult) {
|
protected void onDialogClosed(boolean positiveResult) {
|
||||||
try {
|
// Nullify dialog references.
|
||||||
if (positiveResult && selectedDialogEntryIndex >= 0 && getEntryValues() != null) {
|
dialogColorDotView = null;
|
||||||
String value = getEntryValues()[selectedDialogEntryIndex].toString();
|
dialogColorEditText = null;
|
||||||
if (callChangeListener(value)) {
|
dialogOpacityEditText = null;
|
||||||
setValue(value);
|
dialogColorPickerView = null;
|
||||||
category.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value)));
|
|
||||||
SegmentCategory.updateEnabledCategories();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if (dialog != null) {
|
||||||
category.setColor(dialogColorEditText.getText().toString());
|
dialog.dismiss();
|
||||||
category.setOpacity(categoryOpacity);
|
dialog = null;
|
||||||
} catch (IllegalArgumentException ex) {
|
|
||||||
Utils.showToastShort(str("revanced_settings_color_invalid"));
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "onDialogClosed failure", ex);
|
|
||||||
} finally {
|
|
||||||
dialogColorDotView = null;
|
|
||||||
dialogColorEditText = null;
|
|
||||||
dialogOpacityEditText = null;
|
|
||||||
dialogColorPickerView = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
try {
|
String initialValue = getText() != null ? getText() : "";
|
||||||
Utils.setClipboard(getEditText().getText());
|
editText.setText(initialValue);
|
||||||
} catch (Exception ex) {
|
editText.setSelection(initialValue.length()); // Move cursor to end.
|
||||||
Logger.printException(() -> "Copy settings failure", ex);
|
|
||||||
}
|
// 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 {
|
||||||
|
Utils.setClipboard(getEditText().getText());
|
||||||
|
} catch (Exception 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,51 +461,90 @@ 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.
|
||||||
String serverAddress = editText.getText().toString();
|
editText, // Pass the EditText.
|
||||||
if (!SponsorBlockSettings.isValidSBServerAddress(serverAddress)) {
|
null, // OK button text.
|
||||||
Utils.showToastLong(str("revanced_sb_api_url_invalid"));
|
() -> {
|
||||||
} else if (!serverAddress.equals(Settings.SB_API_URL.get())) {
|
// OK button action.
|
||||||
Settings.SB_API_URL.save(serverAddress);
|
String serverAddress = editText.getText().toString();
|
||||||
Utils.showToastLong(str("revanced_sb_api_url_changed"));
|
if (!SponsorBlockSettings.isValidSBServerAddress(serverAddress)) {
|
||||||
}
|
Utils.showToastLong(str("revanced_sb_api_url_invalid"));
|
||||||
}
|
} else if (!serverAddress.equals(Settings.SB_API_URL.get())) {
|
||||||
};
|
Settings.SB_API_URL.save(serverAddress);
|
||||||
new AlertDialog.Builder(context)
|
Utils.showToastLong(str("revanced_sb_api_url_changed"));
|
||||||
.setTitle(apiUrl.getTitle())
|
}
|
||||||
.setView(editText)
|
},
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
() -> {}, // Cancel button action (dismiss dialog).
|
||||||
.setNeutralButton(str("revanced_settings_reset"), urlChangeListener)
|
str("revanced_settings_reset"), // Neutral (Reset) button text.
|
||||||
.setPositiveButton(android.R.string.ok, urlChangeListener)
|
() -> {
|
||||||
.show();
|
// Neutral button action.
|
||||||
|
Settings.SB_API_URL.resetToDefault();
|
||||||
|
Utils.showToastLong(str("revanced_sb_api_url_reset"));
|
||||||
|
},
|
||||||
|
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) {
|
||||||
|
try {
|
||||||
|
Context context = getContext();
|
||||||
|
EditText editText = getEditText();
|
||||||
|
|
||||||
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
|
// Create a custom dialog.
|
||||||
try {
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
Utils.setClipboard(getEditText().getText());
|
context,
|
||||||
} catch (Exception ex) {
|
str("revanced_sb_settings_ie"), // Title.
|
||||||
Logger.printException(() -> "Copy settings failure", ex);
|
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) {
|
||||||
|
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;
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 5.27.0
|
version = 5.28.0-dev.8
|
||||||
|
@ -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;
|
||||||
@ -921,6 +922,10 @@ public final class app/revanced/patches/spotify/misc/fix/login/FixFacebookLoginP
|
|||||||
public static final fun getFixFacebookLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getFixFacebookLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/spotify/misc/lyrics/ChangeLyricsProviderPatchKt {
|
||||||
|
public static final fun getChangeLyricsProviderPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt {
|
public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt {
|
||||||
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@ -953,6 +958,10 @@ public final class app/revanced/patches/swissid/integritycheck/RemoveGooglePlayI
|
|||||||
public static final fun getRemoveGooglePlayIntegrityCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getRemoveGooglePlayIntegrityCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/threads/HideAdsPatchKt {
|
||||||
|
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/ticktick/misc/themeunlock/UnlockThemePatchKt {
|
public final class app/revanced/patches/ticktick/misc/themeunlock/UnlockThemePatchKt {
|
||||||
public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@ -1674,6 +1683,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
|
||||||
@ -1683,6 +1693,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
|
||||||
|
@ -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
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
package app.revanced.patches.instagram.ads
|
package app.revanced.patches.instagram.ads
|
||||||
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.meta.ads.adInjectorFingerprint
|
||||||
|
import app.revanced.util.returnEarly
|
||||||
|
|
||||||
@Deprecated("Patch was moved to different package: app.revanced.patches.meta.ads.hideAdsPatch")
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val hideAdsPatch = bytecodePatch {
|
val hideAdsPatch = bytecodePatch(
|
||||||
dependsOn(app.revanced.patches.meta.ads.hideAdsPatch)
|
name = "Hide ads",
|
||||||
|
) {
|
||||||
|
compatibleWith("com.instagram.android")
|
||||||
|
|
||||||
|
execute {
|
||||||
|
adInjectorFingerprint.method.returnEarly(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,3 +11,15 @@ internal val getMobileConfigBoolFingerprint = fingerprint {
|
|||||||
classDef.interfaces.contains("Lcom/facebook/mobileconfig/factory/MobileConfigUnsafeContext;")
|
classDef.interfaces.contains("Lcom/facebook/mobileconfig/factory/MobileConfigUnsafeContext;")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val metaAIKillSwitchCheckFingerprint = fingerprint {
|
||||||
|
strings("SearchAiagentImplementationsKillSwitch")
|
||||||
|
opcodes(Opcode.CONST_WIDE)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val extensionMethodFingerprint = fingerprint {
|
||||||
|
strings("REPLACED_BY_PATCH")
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == EXTENSION_METHOD_NAME && classDef.type == EXTENSION_CLASS_DESCRIPTOR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,11 +2,14 @@ package app.revanced.patches.messenger.metaai
|
|||||||
|
|
||||||
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.patches.messenger.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.messenger.misc.extension.sharedExtensionPatch
|
||||||
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.WideLiteralInstruction
|
||||||
|
|
||||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/messenger/metaai/RemoveMetaAIPatch;"
|
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/messenger/metaai/RemoveMetaAIPatch;"
|
||||||
|
internal const val EXTENSION_METHOD_NAME = "overrideBooleanFlag"
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val removeMetaAIPatch = bytecodePatch(
|
val removeMetaAIPatch = bytecodePatch(
|
||||||
@ -25,10 +28,25 @@ val removeMetaAIPatch = bytecodePatch(
|
|||||||
addInstructions(
|
addInstructions(
|
||||||
returnIndex,
|
returnIndex,
|
||||||
"""
|
"""
|
||||||
invoke-static { p1, p2, v$returnRegister }, $EXTENSION_CLASS_DESCRIPTOR->overrideBooleanFlag(JZ)Z
|
invoke-static { p1, p2, v$returnRegister }, $EXTENSION_CLASS_DESCRIPTOR->$EXTENSION_METHOD_NAME(JZ)Z
|
||||||
move-result v$returnRegister
|
move-result v$returnRegister
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract the common starting digits of Meta AI flag IDs from a flag found in code.
|
||||||
|
val relevantDigits = with(metaAIKillSwitchCheckFingerprint) {
|
||||||
|
method.getInstruction<WideLiteralInstruction>(patternMatch!!.startIndex).wideLiteral
|
||||||
|
}.toString().substring(0, 7)
|
||||||
|
|
||||||
|
// Replace placeholder in the extension method.
|
||||||
|
with(extensionMethodFingerprint) {
|
||||||
|
method.replaceInstruction(
|
||||||
|
stringMatches!!.first().index,
|
||||||
|
"""
|
||||||
|
const-string v1, "$relevantDigits"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,4 @@ package app.revanced.patches.messenger.misc.extension
|
|||||||
|
|
||||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||||
|
|
||||||
val sharedExtensionPatch = sharedExtensionPatch("messenger", mainActivityOnCreateHook)
|
val sharedExtensionPatch = sharedExtensionPatch("messenger", messengerApplicationOnCreateHook)
|
||||||
|
@ -2,6 +2,8 @@ package app.revanced.patches.messenger.misc.extension
|
|||||||
|
|
||||||
import app.revanced.patches.shared.misc.extension.extensionHook
|
import app.revanced.patches.shared.misc.extension.extensionHook
|
||||||
|
|
||||||
internal val mainActivityOnCreateHook = extensionHook {
|
internal val messengerApplicationOnCreateHook = extensionHook {
|
||||||
strings("MainActivity_onCreate_begin")
|
custom { method, classDef ->
|
||||||
|
method.name == "onCreate" && classDef.endsWith("/MessengerApplication;")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,9 @@ package app.revanced.patches.meta.ads
|
|||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.util.returnEarly
|
import app.revanced.util.returnEarly
|
||||||
|
|
||||||
|
@Deprecated("Instead use the Instagram or Threads specific hide ads patch")
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val hideAdsPatch = bytecodePatch(
|
val hideAdsPatch = bytecodePatch {
|
||||||
name = "Hide ads",
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Patch is identical for both Instagram and Threads app.
|
|
||||||
*/
|
|
||||||
compatibleWith(
|
|
||||||
"com.instagram.android",
|
|
||||||
"com.instagram.barcelona",
|
|
||||||
)
|
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
adInjectorFingerprint.method.returnEarly(false)
|
adInjectorFingerprint.method.returnEarly(false)
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
package app.revanced.patches.pandora.ads
|
package app.revanced.patches.pandora.ads
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patches.pandora.shared.constructUserDataFingerprint
|
import app.revanced.util.returnEarly
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val disableAudioAdsPatch = bytecodePatch(
|
val disableAudioAdsPatch = bytecodePatch(
|
||||||
@ -15,16 +10,7 @@ val disableAudioAdsPatch = bytecodePatch(
|
|||||||
compatibleWith("com.pandora.android")
|
compatibleWith("com.pandora.android")
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
constructUserDataFingerprint.method.apply {
|
getIsAdSupportedFingerprint.method.returnEarly(false)
|
||||||
// First match is "hasAudioAds".
|
requestAudioAdFingerprint.method.returnEarly()
|
||||||
val hasAudioAdsStringIndex = constructUserDataFingerprint.stringMatches!!.first().index
|
|
||||||
val moveResultIndex = indexOfFirstInstructionOrThrow(hasAudioAdsStringIndex, Opcode.MOVE_RESULT)
|
|
||||||
val hasAudioAdsRegister = getInstruction<OneRegisterInstruction>(moveResultIndex).registerA
|
|
||||||
|
|
||||||
addInstruction(
|
|
||||||
moveResultIndex + 1,
|
|
||||||
"const/4 v$hasAudioAdsRegister, 0"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package app.revanced.patches.pandora.ads
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
internal val getIsAdSupportedFingerprint = fingerprint {
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "getIsAdSupported" && classDef.endsWith("UserData;")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val requestAudioAdFingerprint = fingerprint {
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "requestAudioAdFromAdSDK" && classDef.endsWith("ContentServiceOpsImpl;")
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,7 @@
|
|||||||
package app.revanced.patches.pandora.misc
|
package app.revanced.patches.pandora.misc
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patches.pandora.shared.constructUserDataFingerprint
|
import app.revanced.util.returnEarly
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val enableUnlimitedSkipsPatch = bytecodePatch(
|
val enableUnlimitedSkipsPatch = bytecodePatch(
|
||||||
@ -15,17 +10,6 @@ val enableUnlimitedSkipsPatch = bytecodePatch(
|
|||||||
compatibleWith("com.pandora.android")
|
compatibleWith("com.pandora.android")
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
constructUserDataFingerprint.method.apply {
|
skipLimitBehaviorFingerprint.method.returnEarly("unlimited")
|
||||||
// Last match is "skipLimitBehavior".
|
|
||||||
val skipLimitBehaviorStringIndex = constructUserDataFingerprint.stringMatches!!.last().index
|
|
||||||
val moveResultObjectIndex =
|
|
||||||
indexOfFirstInstructionOrThrow(skipLimitBehaviorStringIndex, Opcode.MOVE_RESULT_OBJECT)
|
|
||||||
val skipLimitBehaviorRegister = getInstruction<OneRegisterInstruction>(moveResultObjectIndex).registerA
|
|
||||||
|
|
||||||
addInstruction(
|
|
||||||
moveResultObjectIndex + 1,
|
|
||||||
"const-string v$skipLimitBehaviorRegister, \"unlimited\""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.patches.pandora.misc
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
internal val skipLimitBehaviorFingerprint = fingerprint {
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "getSkipLimitBehavior" && classDef.endsWith("UserData;")
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
package app.revanced.patches.pandora.shared
|
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
|
||||||
|
|
||||||
internal val constructUserDataFingerprint = fingerprint {
|
|
||||||
strings("hasAudioAds", "skipLimitBehavior")
|
|
||||||
}
|
|
@ -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
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
apiUrlIndex,
|
it.method.replaceInstruction(
|
||||||
"const-string v1, \"https://api.imgur.com/3/image\"",
|
apiUrlIndex,
|
||||||
)
|
"const-string v1, \"https://api.imgur.com/3/image\"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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")
|
||||||
|
@ -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) {
|
||||||
|
@ -72,7 +72,7 @@ val hideCreateButtonPatch = bytecodePatch(
|
|||||||
|
|
||||||
if (oldNavigationBarAddItemMethod != null) {
|
if (oldNavigationBarAddItemMethod != null) {
|
||||||
// In case an older version of the app is being patched, hook the old method which adds navigation bar items.
|
// In case an older version of the app is being patched, hook the old method which adds navigation bar items.
|
||||||
// Return null early if the navigation bar item title resource id is the old Create button title resource id.
|
// Return early if the navigation bar item title resource id is the old Create button title resource id.
|
||||||
oldNavigationBarAddItemFingerprint.methodOrNull?.apply {
|
oldNavigationBarAddItemFingerprint.methodOrNull?.apply {
|
||||||
val getNavigationBarItemTitleStringIndex = indexOfFirstInstructionOrThrow {
|
val getNavigationBarItemTitleStringIndex = indexOfFirstInstructionOrThrow {
|
||||||
val reference = getReference<MethodReference>()
|
val reference = getReference<MethodReference>()
|
||||||
@ -89,6 +89,16 @@ val hideCreateButtonPatch = bytecodePatch(
|
|||||||
val isOldCreateButtonDescriptor =
|
val isOldCreateButtonDescriptor =
|
||||||
"$EXTENSION_CLASS_DESCRIPTOR->isOldCreateButton(I)Z"
|
"$EXTENSION_CLASS_DESCRIPTOR->isOldCreateButton(I)Z"
|
||||||
|
|
||||||
|
val returnEarlyInstruction = if (returnType == "V") {
|
||||||
|
// In older implementations the method return value is void.
|
||||||
|
"return-void"
|
||||||
|
} else {
|
||||||
|
// In newer implementations
|
||||||
|
// return null because the method return value is a BottomNavigationItemView.
|
||||||
|
"const/4 v0, 0\n" +
|
||||||
|
"return-object v0"
|
||||||
|
}
|
||||||
|
|
||||||
addInstructionsWithLabels(
|
addInstructionsWithLabels(
|
||||||
0,
|
0,
|
||||||
"""
|
"""
|
||||||
@ -98,9 +108,7 @@ val hideCreateButtonPatch = bytecodePatch(
|
|||||||
# If this navigation bar item is not the Create button, jump to the normal method logic.
|
# If this navigation bar item is not the Create button, jump to the normal method logic.
|
||||||
if-eqz v0, :normal-method-logic
|
if-eqz v0, :normal-method-logic
|
||||||
|
|
||||||
# Return null early because this method return value is a BottomNavigationItemView.
|
$returnEarlyInstruction
|
||||||
const/4 v0, 0
|
|
||||||
return-object v0
|
|
||||||
""",
|
""",
|
||||||
ExternalLabel("normal-method-logic", firstInstruction)
|
ExternalLabel("normal-method-logic", firstInstruction)
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
package app.revanced.patches.spotify.misc.lyrics
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||||
|
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.stringOption
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
|
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||||
|
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
import java.net.UnknownHostException
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val changeLyricsProviderPatch = bytecodePatch(
|
||||||
|
name = "Change lyrics provider",
|
||||||
|
description = "Changes the lyrics provider to a custom one.",
|
||||||
|
use = false,
|
||||||
|
) {
|
||||||
|
compatibleWith("com.spotify.music")
|
||||||
|
|
||||||
|
val lyricsProviderHost by stringOption(
|
||||||
|
key = "lyricsProviderHost",
|
||||||
|
default = "lyrics.natanchiodi.fr",
|
||||||
|
title = "Lyrics provider host",
|
||||||
|
description = "The domain name or IP address of a custom lyrics provider.",
|
||||||
|
required = false,
|
||||||
|
) {
|
||||||
|
// Fix bad data if the user enters a URL (https://whatever.com/path).
|
||||||
|
val host = try {
|
||||||
|
URI(it!!).host ?: it
|
||||||
|
} catch (e: URISyntaxException) {
|
||||||
|
return@stringOption false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a courtesy check if the host can be resolved.
|
||||||
|
// If it does not resolve, then print a warning but use the host anyway.
|
||||||
|
// Unresolvable hosts should not be rejected, since the patching environment
|
||||||
|
// may not allow network connections or the network may be down.
|
||||||
|
try {
|
||||||
|
InetAddress.getByName(host)
|
||||||
|
} catch (e: UnknownHostException) {
|
||||||
|
Logger.getLogger(this::class.java.name).warning(
|
||||||
|
"Host \"$host\" did not resolve to any domain."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
execute {
|
||||||
|
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||||
|
Logger.getLogger(this::class.java.name).severe(
|
||||||
|
"Change lyrics provider patch is not supported for this target version."
|
||||||
|
)
|
||||||
|
return@execute
|
||||||
|
}
|
||||||
|
|
||||||
|
val httpClientBuilderMethod = httpClientBuilderFingerprint.originalMethod
|
||||||
|
|
||||||
|
// region Create a modified copy of the HTTP client builder method with the custom lyrics provider host.
|
||||||
|
val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) {
|
||||||
|
val invokeBuildUrlIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
getReference<MethodReference>()?.returnType == "Lokhttp3/HttpUrl;"
|
||||||
|
}
|
||||||
|
val setUrlBuilderHostIndex = indexOfFirstInstructionReversedOrThrow(invokeBuildUrlIndex) {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
reference?.definingClass == "Lokhttp3/HttpUrl${"$"}Builder;" &&
|
||||||
|
reference.parameterTypes.firstOrNull() == "Ljava/lang/String;"
|
||||||
|
}
|
||||||
|
val hostRegister = getInstruction<FiveRegisterInstruction>(setUrlBuilderHostIndex).registerD
|
||||||
|
|
||||||
|
MutableMethod(this).apply {
|
||||||
|
name = "rv_getCustomLyricsProviderHttpClient"
|
||||||
|
addInstruction(
|
||||||
|
setUrlBuilderHostIndex,
|
||||||
|
"const-string v$hostRegister, \"$lyricsProviderHost\""
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add the patched method to the class.
|
||||||
|
httpClientBuilderFingerprint.classDef.methods.add(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
// region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one.
|
||||||
|
getLyricsHttpClientFingerprint(httpClientBuilderMethod).method.apply {
|
||||||
|
val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
getReference<MethodReference>() == httpClientBuilderMethod
|
||||||
|
}
|
||||||
|
val getLyricsHttpClientInstruction = getInstruction<BuilderInstruction35c>(getLyricsHttpClientIndex)
|
||||||
|
|
||||||
|
// Replace the original method call with a call to our patched method.
|
||||||
|
replaceInstruction(
|
||||||
|
getLyricsHttpClientIndex,
|
||||||
|
BuilderInstruction35c(
|
||||||
|
getLyricsHttpClientInstruction.opcode,
|
||||||
|
getLyricsHttpClientInstruction.registerCount,
|
||||||
|
getLyricsHttpClientInstruction.registerC,
|
||||||
|
getLyricsHttpClientInstruction.registerD,
|
||||||
|
getLyricsHttpClientInstruction.registerE,
|
||||||
|
getLyricsHttpClientInstruction.registerF,
|
||||||
|
getLyricsHttpClientInstruction.registerG,
|
||||||
|
ImmutableMethodReference(
|
||||||
|
patchedHttpClientBuilderMethod.definingClass,
|
||||||
|
patchedHttpClientBuilderMethod.name, // Only difference from the original method.
|
||||||
|
patchedHttpClientBuilderMethod.parameters,
|
||||||
|
patchedHttpClientBuilderMethod.returnType
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package app.revanced.patches.spotify.misc.lyrics
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
|
internal val httpClientBuilderFingerprint = fingerprint {
|
||||||
|
strings("client == null", "scheduler == null")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getLyricsHttpClientFingerprint(httpClientBuilderMethodReference: MethodReference) =
|
||||||
|
fingerprint {
|
||||||
|
returns(httpClientBuilderMethodReference.returnType)
|
||||||
|
parameters()
|
||||||
|
custom { method, _ ->
|
||||||
|
method.indexOfFirstInstruction {
|
||||||
|
getReference<MethodReference>() == httpClientBuilderMethodReference
|
||||||
|
} >= 0
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package app.revanced.patches.spotify.misc.privacy
|
|||||||
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 shareCopyUrlFingerprint = fingerprint {
|
internal val shareCopyUrlFingerprint = fingerprint {
|
||||||
returns("Ljava/lang/Object;")
|
returns("Ljava/lang/Object;")
|
||||||
@ -23,9 +24,15 @@ internal val shareCopyUrlLegacyFingerprint = fingerprint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal val formatAndroidShareSheetUrlFingerprint = fingerprint {
|
internal val formatAndroidShareSheetUrlFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
|
||||||
returns("Ljava/lang/String;")
|
returns("Ljava/lang/String;")
|
||||||
parameters("L", "Ljava/lang/String;")
|
parameters("L", "Ljava/lang/String;")
|
||||||
|
opcodes(
|
||||||
|
Opcode.GOTO,
|
||||||
|
Opcode.IF_EQZ,
|
||||||
|
Opcode.INVOKE_STATIC,
|
||||||
|
Opcode.MOVE_RESULT_OBJECT,
|
||||||
|
Opcode.RETURN_OBJECT
|
||||||
|
)
|
||||||
literal {
|
literal {
|
||||||
'\n'.code.toLong()
|
'\n'.code.toLong()
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
|||||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
||||||
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.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
@ -56,7 +57,15 @@ val sanitizeSharingLinksPatch = bytecodePatch(
|
|||||||
shareUrlParameter = "p2"
|
shareUrlParameter = "p2"
|
||||||
} else {
|
} else {
|
||||||
shareSheetFingerprint = formatAndroidShareSheetUrlFingerprint
|
shareSheetFingerprint = formatAndroidShareSheetUrlFingerprint
|
||||||
shareUrlParameter = "p1"
|
val methodAccessFlags = formatAndroidShareSheetUrlFingerprint.originalMethod.accessFlags
|
||||||
|
shareUrlParameter = if (AccessFlags.STATIC.isSet(methodAccessFlags)) {
|
||||||
|
// In newer implementations the method is static, so p0 is not `this`.
|
||||||
|
"p1"
|
||||||
|
} else {
|
||||||
|
// In older implementations the method is not static, making it so p0 is `this`.
|
||||||
|
// For that reason, add one to the parameter register.
|
||||||
|
"p2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shareSheetFingerprint.method.addInstructions(
|
shareSheetFingerprint.method.addInstructions(
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.patches.threads
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.meta.ads.adInjectorFingerprint
|
||||||
|
import app.revanced.util.returnEarly
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val hideAdsPatch = bytecodePatch(
|
||||||
|
name = "Hide ads",
|
||||||
|
) {
|
||||||
|
compatibleWith("com.instagram.barcelona"("382.0.0.51.85"))
|
||||||
|
|
||||||
|
execute {
|
||||||
|
adInjectorFingerprint.method.returnEarly(false)
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,8 +77,6 @@ val hideAdsPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -25,8 +25,6 @@ val hideGetPremiumPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -23,8 +23,6 @@ val videoAdsPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -53,8 +53,6 @@ val copyVideoUrlPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -24,8 +24,6 @@ val removeViewerDiscretionDialogPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -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
|
||||||
@ -68,8 +72,6 @@ val downloadsPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
@ -83,12 +85,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(
|
||||||
|
@ -20,8 +20,6 @@ val seekbarPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -18,8 +18,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
|||||||
"Lapp/revanced/extension/youtube/patches/SeekbarThumbnailsPatch;"
|
"Lapp/revanced/extension/youtube/patches/SeekbarThumbnailsPatch;"
|
||||||
|
|
||||||
val seekbarThumbnailsPatch = bytecodePatch(
|
val seekbarThumbnailsPatch = bytecodePatch(
|
||||||
description = "Adds an option to use high quality fullscreen seekbar thumbnails. " +
|
description = "Adds an option to use high quality fullscreen seekbar thumbnails."
|
||||||
"Patching 19.16.39 adds an option to restore old seekbar thumbnails.",
|
|
||||||
) {
|
) {
|
||||||
dependsOn(
|
dependsOn(
|
||||||
sharedExtensionPatch,
|
sharedExtensionPatch,
|
||||||
|
@ -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",
|
||||||
@ -91,8 +88,6 @@ val swipeControlsPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
@ -103,7 +98,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)
|
||||||
|
@ -24,8 +24,6 @@ val autoCaptionsPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -43,8 +43,6 @@ val customBrandingPatch = resourcePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -41,8 +41,6 @@ val changeHeaderPatch = resourcePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -22,8 +22,6 @@ val hideButtonsPatch = resourcePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -40,8 +40,6 @@ val navigationButtonsPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -58,8 +58,6 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -33,8 +33,6 @@ val changeFormFactorPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
@ -47,10 +45,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)
|
||||||
|
@ -59,8 +59,6 @@ val hideEndscreenCardsPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -31,8 +31,6 @@ val hideEndScreenSuggestedVideoPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -29,8 +29,6 @@ val disableFullscreenAmbientModePatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -125,8 +125,6 @@ val hideLayoutComponentsPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -57,8 +57,6 @@ val hideInfoCardsPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -24,8 +24,6 @@ val hidePlayerFlyoutMenuPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -48,8 +48,6 @@ val hideRelatedVideoOverlayPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -29,8 +29,6 @@ val disableRollingNumberAnimationPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -170,8 +170,6 @@ val hideShortsComponentsPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -21,8 +21,6 @@ val hideTimestampPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
|
@ -153,22 +153,6 @@ val miniplayerPatch = bytecodePatch(
|
|||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
"19.16.39", // First with modern miniplayers.
|
|
||||||
// 19.17.41 // Works without issues, but no reason to recommend over 19.16.
|
|
||||||
// 19.18.41 // Works without issues, but no reason to recommend over 19.16.
|
|
||||||
// 19.19.39 // Last bug free version with smaller Modern 1 miniplayer, but no reason to recommend over 19.16.
|
|
||||||
// 19.20.35 // Cannot swipe to expand.
|
|
||||||
// 19.21.40 // Cannot swipe to expand.
|
|
||||||
// 19.22.43 // Cannot swipe to expand.
|
|
||||||
// 19.23.40 // First with Modern 1 drag and drop, Cannot swipe to expand.
|
|
||||||
// 19.24.45 // First with larger Modern 1, Cannot swipe to expand.
|
|
||||||
"19.25.37", // First with double tap, last with skip forward/back buttons, last with swipe to expand/close, and last before double tap to expand seems to be required.
|
|
||||||
// 19.26.42 // Modern 1 Pause/play button are always hidden. Unusable.
|
|
||||||
// 19.28.42 // First with custom miniplayer size, screen flickers when swiping to maximize Modern 1. Swipe to close miniplayer is broken.
|
|
||||||
// 19.29.42 // All modern players are broken and ignore tapping the miniplayer video.
|
|
||||||
// 19.30.39 // Modern 3 is less broken when double tap expand is enabled, but cannot swipe to expand when double tap is off.
|
|
||||||
// 19.31.36 // All Modern 1 buttons are missing. Unusable.
|
|
||||||
// 19.32.36 // 19.32+ and beyond all work without issues.
|
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"19.43.41",
|
"19.43.41",
|
||||||
"19.47.53",
|
"19.47.53",
|
||||||
@ -184,23 +168,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"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user