mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-06-01 14:30:25 +02:00
feat(YouTube - Settings): Add a color picker (#4981)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
parent
34932dc439
commit
1e0e398574
@ -23,6 +23,7 @@ import android.preference.Preference;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.util.Pair;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
@ -557,14 +558,14 @@ public class Utils {
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean isDarkModeEnabled(Context context) {
|
||||
Configuration config = context.getResources().getConfiguration();
|
||||
public static boolean isDarkModeEnabled() {
|
||||
Configuration config = Resources.getSystem().getConfiguration();
|
||||
final int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||
return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
|
||||
}
|
||||
|
||||
public static boolean isLandscapeOrientation() {
|
||||
final int orientation = context.getResources().getConfiguration().orientation;
|
||||
final int orientation = Resources.getSystem().getConfiguration().orientation;
|
||||
return orientation == Configuration.ORIENTATION_LANDSCAPE;
|
||||
}
|
||||
|
||||
@ -640,7 +641,11 @@ public class Utils {
|
||||
|| networkType == NetworkType.OTHER;
|
||||
}
|
||||
|
||||
@SuppressLint({"MissingPermission", "deprecation"}) // Permission already included in YouTube.
|
||||
/**
|
||||
* Calling extension code must ensure the target app has the
|
||||
* <code>ACCESS_NETWORK_STATE</code> app manifest permission.
|
||||
*/
|
||||
@SuppressWarnings({"deprecation", "MissingPermission"})
|
||||
public static NetworkType getNetworkType() {
|
||||
Context networkContext = getContext();
|
||||
if (networkContext == null) {
|
||||
@ -852,6 +857,20 @@ public class Utils {
|
||||
return getResourceColor(colorString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts dip value to actual device pixels.
|
||||
*
|
||||
* @param dip The density-independent pixels value
|
||||
* @return The device pixel value
|
||||
*/
|
||||
public static int dipToPixels(float dip) {
|
||||
return (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
dip,
|
||||
Resources.getSystem().getDisplayMetrics()
|
||||
);
|
||||
}
|
||||
|
||||
public static int clamp(int value, int lower, int upper) {
|
||||
return Math.max(lower, Math.min(value, upper));
|
||||
}
|
||||
|
@ -0,0 +1,442 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.settings.StringSetting;
|
||||
|
||||
/**
|
||||
* A custom preference for selecting a color via a hexadecimal code or a color picker dialog.
|
||||
* Extends {@link EditTextPreference} to display a colored dot in the widget area,
|
||||
* reflecting the currently selected color. The dot is dimmed when the preference is disabled.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public class ColorPickerPreference extends EditTextPreference {
|
||||
|
||||
/**
|
||||
* Character to show the color appearance.
|
||||
*/
|
||||
public static final String COLOR_DOT_STRING = "⬤";
|
||||
|
||||
/**
|
||||
* Length of a valid color string of format #RRGGBB.
|
||||
*/
|
||||
public static final int COLOR_STRING_LENGTH = 7;
|
||||
|
||||
/**
|
||||
* Matches everything that is not a hex number/letter.
|
||||
*/
|
||||
private static final Pattern PATTERN_NOT_HEX = Pattern.compile("[^0-9A-Fa-f]");
|
||||
|
||||
/**
|
||||
* Alpha for dimming when the preference is disabled.
|
||||
*/
|
||||
private static final float DISABLED_ALPHA = 0.5f; // 50%
|
||||
|
||||
/**
|
||||
* View displaying a colored dot in the widget area.
|
||||
*/
|
||||
private View widgetColorDot;
|
||||
|
||||
/**
|
||||
* Current color in RGB format (without alpha).
|
||||
*/
|
||||
@ColorInt
|
||||
private int currentColor;
|
||||
|
||||
/**
|
||||
* Associated setting for storing the color value.
|
||||
*/
|
||||
private StringSetting colorSetting;
|
||||
|
||||
/**
|
||||
* Dialog TextWatcher for the EditText to monitor color input changes.
|
||||
*/
|
||||
private TextWatcher colorTextWatcher;
|
||||
|
||||
/**
|
||||
* Dialog TextView displaying a colored dot for the selected color preview in the dialog.
|
||||
*/
|
||||
private TextView dialogColorPreview;
|
||||
|
||||
/**
|
||||
* Dialog color picker view.
|
||||
*/
|
||||
private ColorPickerView dialogColorPickerView;
|
||||
|
||||
/**
|
||||
* Removes non valid hex characters, converts to all uppercase,
|
||||
* and adds # character to the start if not present.
|
||||
*/
|
||||
public static String cleanupColorCodeString(String colorString) {
|
||||
// Remove non-hex chars, convert to uppercase, and ensure correct length
|
||||
String result = "#" + PATTERN_NOT_HEX.matcher(colorString)
|
||||
.replaceAll("").toUpperCase(Locale.ROOT);
|
||||
|
||||
if (result.length() < COLOR_STRING_LENGTH) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return result.substring(0, COLOR_STRING_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param color RGB color, without an alpha channel.
|
||||
* @return #RRGGBB hex color string
|
||||
*/
|
||||
public static String getColorString(@ColorInt int color) {
|
||||
String colorString = String.format("#%06X", color);
|
||||
if ((color & 0xFF000000) != 0) {
|
||||
// Likely a bug somewhere.
|
||||
Logger.printException(() -> "getColorString: color has alpha channel: " + colorString);
|
||||
}
|
||||
return colorString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Spanned object for a colored dot using SpannableString.
|
||||
*
|
||||
* @param color The RGB color (without alpha).
|
||||
* @return A Spanned object with the colored dot.
|
||||
*/
|
||||
public static Spanned getColorDot(@ColorInt int color) {
|
||||
SpannableString spannable = new SpannableString(COLOR_DOT_STRING);
|
||||
spannable.setSpan(new ForegroundColorSpan(color | 0xFF000000), 0, COLOR_DOT_STRING.length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
spannable.setSpan(new RelativeSizeSpan(1.5f), 0, 1,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return spannable;
|
||||
}
|
||||
|
||||
public ColorPickerPreference(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public ColorPickerPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public ColorPickerPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the preference by setting up the EditText, loading the color, and set the widget layout.
|
||||
*/
|
||||
private void init() {
|
||||
colorSetting = (StringSetting) Setting.getSettingFromPath(getKey());
|
||||
if (colorSetting == null) {
|
||||
Logger.printException(() -> "Could not find color setting for: " + getKey());
|
||||
}
|
||||
|
||||
EditText editText = getEditText();
|
||||
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
editText.setAutofillHints((String) null);
|
||||
}
|
||||
|
||||
// Set the widget layout to a custom layout containing the colored dot.
|
||||
setWidgetLayoutResource(getResourceIdentifier("revanced_color_dot_widget", "layout"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected color and updates the UI and settings.
|
||||
*
|
||||
* @param colorString The color in hexadecimal format (e.g., "#RRGGBB").
|
||||
* @throws IllegalArgumentException If the color string is invalid.
|
||||
*/
|
||||
@Override
|
||||
public final void setText(String colorString) {
|
||||
try {
|
||||
Logger.printDebug(() -> "setText: " + colorString);
|
||||
super.setText(colorString);
|
||||
|
||||
currentColor = Color.parseColor(colorString) & 0x00FFFFFF;
|
||||
if (colorSetting != null) {
|
||||
colorSetting.save(getColorString(currentColor));
|
||||
}
|
||||
updateColorPreview();
|
||||
updateWidgetColorDot();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// This code is reached if the user pastes settings json with an invalid color
|
||||
// since this preference is updated with the new setting text.
|
||||
Logger.printDebug(() -> "Parse color error: " + colorString, ex);
|
||||
Utils.showToastShort(str("revanced_settings_color_invalid"));
|
||||
setText(colorSetting.resetToDefault());
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "setText failure: " + colorString, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
|
||||
widgetColorDot = view.findViewById(getResourceIdentifier(
|
||||
"revanced_color_dot_widget", "id"));
|
||||
widgetColorDot.setBackgroundResource(getResourceIdentifier(
|
||||
"revanced_settings_circle_background", "drawable"));
|
||||
widgetColorDot.getBackground().setTint(currentColor | 0xFF000000);
|
||||
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.
|
||||
*/
|
||||
private void updateColorPreview() {
|
||||
if (dialogColorPreview != null) {
|
||||
dialogColorPreview.setText(getColorDot(currentColor));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWidgetColorDot() {
|
||||
if (widgetColorDot != null) {
|
||||
widgetColorDot.getBackground().setTint(currentColor | 0xFF000000);
|
||||
widgetColorDot.setAlpha(isEnabled() ? 1.0f : DISABLED_ALPHA);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TextWatcher to monitor changes in the EditText for color input.
|
||||
*
|
||||
* @return A TextWatcher that updates the color preview on valid input.
|
||||
*/
|
||||
private TextWatcher createColorTextWatcher(ColorPickerView colorPickerView) {
|
||||
return new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable edit) {
|
||||
try {
|
||||
String colorString = edit.toString();
|
||||
|
||||
String sanitizedColorString = cleanupColorCodeString(colorString);
|
||||
if (!sanitizedColorString.equals(colorString)) {
|
||||
edit.replace(0, colorString.length(), sanitizedColorString);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sanitizedColorString.length() != COLOR_STRING_LENGTH) {
|
||||
// User is still typing out the color.
|
||||
return;
|
||||
}
|
||||
|
||||
final int newColor = Color.parseColor(colorString);
|
||||
if (currentColor != newColor) {
|
||||
Logger.printDebug(() -> "afterTextChanged: " + sanitizedColorString);
|
||||
currentColor = newColor;
|
||||
updateColorPreview();
|
||||
updateWidgetColorDot();
|
||||
colorPickerView.setColor(newColor);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// Should never be reached since input is validated before using.
|
||||
Logger.printException(() -> "afterTextChanged failure", ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the dialog builder with a custom view and reset button.
|
||||
*
|
||||
* @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
|
||||
protected void showDialog(Bundle state) {
|
||||
super.showDialog(state);
|
||||
|
||||
AlertDialog dialog = (AlertDialog) getDialog();
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
|
||||
// Do not close dialog when reset is pressed.
|
||||
Button button = dialog.getButton(AlertDialog.BUTTON_NEUTRAL);
|
||||
button.setOnClickListener(view -> {
|
||||
try {
|
||||
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(() -> "setOnClickListener failure", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
super.onDialogClosed(positiveResult);
|
||||
|
||||
if (colorTextWatcher != null) {
|
||||
getEditText().removeTextChangedListener(colorTextWatcher);
|
||||
colorTextWatcher = null;
|
||||
}
|
||||
|
||||
dialogColorPreview = null;
|
||||
dialogColorPickerView = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
updateWidgetColorDot();
|
||||
}
|
||||
}
|
@ -0,0 +1,500 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ComposeShader;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
|
||||
/**
|
||||
* A custom color picker view that allows the user to select a color using a hue slider and a saturation-value selector.
|
||||
* This implementation is density-independent and responsive across different screen sizes and DPIs.
|
||||
*
|
||||
* <p>
|
||||
* This view displays two main components for color selection:
|
||||
* <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>Saturation-Value Selector:</b> A rectangular area that allows the user to select the saturation and value (brightness)
|
||||
* components of the color based on the selected hue.
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* The view uses {@link LinearGradient} and {@link ComposeShader} to create the color gradients for the hue bar and the
|
||||
* saturation-value selector. It also uses {@link Paint} to draw the selectors (draggable handles).
|
||||
*
|
||||
* <p>
|
||||
* The selected color can be retrieved using {@link #getColor()} and can be set using {@link #setColor(int)}.
|
||||
* An {@link OnColorChangedListener} can be registered to receive notifications when the selected color changes.
|
||||
*/
|
||||
public class ColorPickerView extends View {
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when the selected color changes.
|
||||
*/
|
||||
public interface OnColorChangedListener {
|
||||
/**
|
||||
* Called when the selected color has changed.
|
||||
*
|
||||
* Important: Callback color uses RGB format with zero alpha channel.
|
||||
*
|
||||
* @param color The new selected color.
|
||||
*/
|
||||
void onColorChanged(@ColorInt int color);
|
||||
}
|
||||
|
||||
/** Expanded touch area for the hue bar to increase the touch-sensitive area. */
|
||||
public static final float TOUCH_EXPANSION = dipToPixels(20f);
|
||||
|
||||
private static final float MARGIN_BETWEEN_AREAS = dipToPixels(24);
|
||||
private static final float VIEW_PADDING = dipToPixels(16);
|
||||
private static final float HUE_BAR_WIDTH = dipToPixels(12);
|
||||
private static final float HUE_CORNER_RADIUS = dipToPixels(6);
|
||||
private static final float SELECTOR_RADIUS = dipToPixels(12);
|
||||
private static final float SELECTOR_STROKE_WIDTH = 8;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private static final float SELECTOR_FILL_RADIUS = SELECTOR_RADIUS - SELECTOR_STROKE_WIDTH / 2;
|
||||
/** Thin dark outline stroke width for the selector rings. */
|
||||
private static final float SELECTOR_EDGE_STROKE_WIDTH = 1;
|
||||
public static final float SELECTOR_EDGE_RADIUS =
|
||||
SELECTOR_RADIUS + SELECTOR_STROKE_WIDTH / 2 + SELECTOR_EDGE_STROKE_WIDTH / 2;
|
||||
|
||||
/** Selector outline inner color. */
|
||||
@ColorInt
|
||||
private static final int SELECTOR_OUTLINE_COLOR = Color.WHITE;
|
||||
|
||||
/** Dark edge color for the selector rings. */
|
||||
@ColorInt
|
||||
private static final int SELECTOR_EDGE_COLOR = Color.parseColor("#CFCFCF");
|
||||
|
||||
private static final int[] HUE_COLORS = new int[361];
|
||||
static {
|
||||
for (int i = 0; i < 361; i++) {
|
||||
HUE_COLORS[i] = Color.HSVToColor(new float[]{i, 1, 1});
|
||||
}
|
||||
}
|
||||
|
||||
/** Hue bar. */
|
||||
private final Paint huePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
/** Saturation-value selector. */
|
||||
private final Paint saturationValuePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
/** Draggable selector. */
|
||||
private final Paint selectorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
{
|
||||
selectorPaint.setStrokeWidth(SELECTOR_STROKE_WIDTH);
|
||||
}
|
||||
|
||||
/** Bounds of the hue bar. */
|
||||
private final RectF hueRect = new RectF();
|
||||
/** Bounds of the saturation-value selector. */
|
||||
private final RectF saturationValueRect = new RectF();
|
||||
|
||||
/** HSV color calculations to avoid allocations during drawing. */
|
||||
private final float[] hsvArray = {1, 1, 1};
|
||||
|
||||
/** Current hue value (0-360). */
|
||||
private float hue = 0f;
|
||||
/** Current saturation value (0-1). */
|
||||
private float saturation = 1f;
|
||||
/** Current value (brightness) value (0-1). */
|
||||
private float value = 1f;
|
||||
|
||||
/** The currently selected color in RGB format with no alpha channel. */
|
||||
@ColorInt
|
||||
private int selectedColor;
|
||||
|
||||
private OnColorChangedListener colorChangedListener;
|
||||
|
||||
/** Track if we're currently dragging the hue or saturation handle. */
|
||||
private boolean isDraggingHue;
|
||||
private boolean isDraggingSaturation;
|
||||
|
||||
public ColorPickerView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ColorPickerView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ColorPickerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final float DESIRED_ASPECT_RATIO = 0.8f; // height = width * 0.8
|
||||
|
||||
final int minWidth = Utils.dipToPixels(250);
|
||||
final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO);
|
||||
|
||||
int width = resolveSize(minWidth, widthMeasureSpec);
|
||||
int height = resolveSize(minHeight, heightMeasureSpec);
|
||||
|
||||
// Ensure minimum dimensions for usability
|
||||
width = Math.max(width, minWidth);
|
||||
height = Math.max(height, minHeight);
|
||||
|
||||
// Adjust height to maintain desired aspect ratio if possible
|
||||
final int desiredHeight = (int) (width * DESIRED_ASPECT_RATIO);
|
||||
if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
|
||||
height = desiredHeight;
|
||||
}
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the size of the view changes.
|
||||
* This method calculates and sets the bounds of the hue bar and saturation-value selector.
|
||||
* It also creates the necessary shaders for the gradients.
|
||||
*/
|
||||
@Override
|
||||
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
|
||||
super.onSizeChanged(width, height, oldWidth, oldHeight);
|
||||
|
||||
// Calculate bounds with hue bar on the right
|
||||
final float effectiveWidth = width - (2 * VIEW_PADDING);
|
||||
final float selectorWidth = effectiveWidth - HUE_BAR_WIDTH - MARGIN_BETWEEN_AREAS;
|
||||
|
||||
// Adjust rectangles to account for padding and density-independent dimensions
|
||||
saturationValueRect.set(
|
||||
VIEW_PADDING,
|
||||
VIEW_PADDING,
|
||||
VIEW_PADDING + selectorWidth,
|
||||
height - VIEW_PADDING
|
||||
);
|
||||
|
||||
hueRect.set(
|
||||
width - VIEW_PADDING - HUE_BAR_WIDTH,
|
||||
VIEW_PADDING,
|
||||
width - VIEW_PADDING,
|
||||
height - VIEW_PADDING
|
||||
);
|
||||
|
||||
// Update the shaders.
|
||||
updateHueShader();
|
||||
updateSaturationValueShader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the hue full spectrum (0-360 degrees).
|
||||
*/
|
||||
private void updateHueShader() {
|
||||
LinearGradient hueShader = new LinearGradient(
|
||||
hueRect.left, hueRect.top,
|
||||
hueRect.left, hueRect.bottom,
|
||||
HUE_COLORS,
|
||||
null,
|
||||
Shader.TileMode.CLAMP
|
||||
);
|
||||
|
||||
huePaint.setShader(hueShader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the shader for the saturation-value selector based on the currently selected hue.
|
||||
* This method creates a combined shader that blends a saturation gradient with a value gradient.
|
||||
*/
|
||||
private void updateSaturationValueShader() {
|
||||
// Create a saturation-value gradient based on the current hue.
|
||||
// Calculate the start color (white with the selected hue) for the saturation gradient.
|
||||
final int startColor = Color.HSVToColor(new float[]{hue, 0f, 1f});
|
||||
|
||||
// Calculate the middle color (fully saturated color with the selected hue) for the saturation gradient.
|
||||
final int midColor = Color.HSVToColor(new float[]{hue, 1f, 1f});
|
||||
|
||||
// Create a linear gradient for the saturation from startColor to midColor (horizontal).
|
||||
LinearGradient satShader = new LinearGradient(
|
||||
saturationValueRect.left, saturationValueRect.top,
|
||||
saturationValueRect.right, saturationValueRect.top,
|
||||
startColor,
|
||||
midColor,
|
||||
Shader.TileMode.CLAMP
|
||||
);
|
||||
|
||||
// Create a linear gradient for the value (brightness) from white to black (vertical).
|
||||
//noinspection ExtractMethodRecommender
|
||||
LinearGradient valShader = new LinearGradient(
|
||||
saturationValueRect.left, saturationValueRect.top,
|
||||
saturationValueRect.left, saturationValueRect.bottom,
|
||||
Color.WHITE,
|
||||
Color.BLACK,
|
||||
Shader.TileMode.CLAMP
|
||||
);
|
||||
|
||||
// Combine the saturation and value shaders using PorterDuff.Mode.MULTIPLY to create the final color.
|
||||
ComposeShader combinedShader = new ComposeShader(satShader, valShader, PorterDuff.Mode.MULTIPLY);
|
||||
|
||||
// Set the combined shader for the saturation-value paint.
|
||||
saturationValuePaint.setShader(combinedShader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the color picker view on the canvas.
|
||||
* This method draws the saturation-value selector, the hue bar with rounded corners,
|
||||
* and the draggable handles.
|
||||
*
|
||||
* @param canvas The canvas on which to draw.
|
||||
*/
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
// Draw the saturation-value selector rectangle.
|
||||
canvas.drawRect(saturationValueRect, saturationValuePaint);
|
||||
|
||||
// Draw the hue bar.
|
||||
canvas.drawRoundRect(hueRect, HUE_CORNER_RADIUS, HUE_CORNER_RADIUS, huePaint);
|
||||
|
||||
final float hueSelectorX = hueRect.centerX();
|
||||
final float hueSelectorY = hueRect.top + (hue / 360f) * hueRect.height();
|
||||
|
||||
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
|
||||
final float satSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
|
||||
|
||||
// Draw the saturation and hue selector handle filled with the selected color.
|
||||
hsvArray[0] = hue;
|
||||
final int hueHandleColor = Color.HSVToColor(0xFF, hsvArray);
|
||||
selectorPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
|
||||
selectorPaint.setColor(hueHandleColor);
|
||||
canvas.drawCircle(hueSelectorX, hueSelectorY, SELECTOR_FILL_RADIUS, selectorPaint);
|
||||
|
||||
selectorPaint.setColor(selectedColor | 0xFF000000);
|
||||
canvas.drawCircle(satSelectorX, satSelectorY, SELECTOR_FILL_RADIUS, selectorPaint);
|
||||
|
||||
// Draw white outlines for the handles.
|
||||
selectorPaint.setColor(SELECTOR_OUTLINE_COLOR);
|
||||
selectorPaint.setStyle(Paint.Style.STROKE);
|
||||
selectorPaint.setStrokeWidth(SELECTOR_STROKE_WIDTH);
|
||||
canvas.drawCircle(hueSelectorX, hueSelectorY, SELECTOR_RADIUS, selectorPaint);
|
||||
canvas.drawCircle(satSelectorX, satSelectorY, SELECTOR_RADIUS, selectorPaint);
|
||||
|
||||
// Draw thin dark outlines for the handles at the outer edge of the white outline.
|
||||
selectorPaint.setColor(SELECTOR_EDGE_COLOR);
|
||||
selectorPaint.setStrokeWidth(SELECTOR_EDGE_STROKE_WIDTH);
|
||||
canvas.drawCircle(hueSelectorX, hueSelectorY, SELECTOR_EDGE_RADIUS, selectorPaint);
|
||||
canvas.drawCircle(satSelectorX, satSelectorY, SELECTOR_EDGE_RADIUS, selectorPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles touch events on the view.
|
||||
* This method determines whether the touch event occurred within the hue bar or the saturation-value selector,
|
||||
* updates the corresponding values (hue, saturation, value), and invalidates the view to trigger a redraw.
|
||||
* <p>
|
||||
* In addition to testing if the touch is within the strict rectangles, an expanded hit area (by selectorRadius)
|
||||
* is used so that the draggable handles remain active even when half of the handle is outside the drawn bounds.
|
||||
*
|
||||
* @param event The motion event.
|
||||
* @return True if the event was handled, false otherwise.
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility") // performClick is not overridden, but not needed in this case.
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
try {
|
||||
final float x = event.getX();
|
||||
final float y = event.getY();
|
||||
final int action = event.getAction();
|
||||
Logger.printDebug(() -> "onTouchEvent action: " + action + " x: " + x + " y: " + y);
|
||||
|
||||
// Define touch expansion for the hue bar.
|
||||
RectF expandedHueRect = new RectF(
|
||||
hueRect.left - TOUCH_EXPANSION,
|
||||
hueRect.top,
|
||||
hueRect.right + TOUCH_EXPANSION,
|
||||
hueRect.bottom
|
||||
);
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// Calculate current handle positions.
|
||||
final float hueSelectorX = hueRect.centerX();
|
||||
final float hueSelectorY = hueRect.top + (hue / 360f) * hueRect.height();
|
||||
|
||||
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
|
||||
final float valSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
|
||||
|
||||
// Create hit areas for both handles.
|
||||
RectF hueHitRect = new RectF(
|
||||
hueSelectorX - SELECTOR_RADIUS,
|
||||
hueSelectorY - SELECTOR_RADIUS,
|
||||
hueSelectorX + SELECTOR_RADIUS,
|
||||
hueSelectorY + SELECTOR_RADIUS
|
||||
);
|
||||
RectF satValHitRect = new RectF(
|
||||
satSelectorX - SELECTOR_RADIUS,
|
||||
valSelectorY - SELECTOR_RADIUS,
|
||||
satSelectorX + SELECTOR_RADIUS,
|
||||
valSelectorY + SELECTOR_RADIUS
|
||||
);
|
||||
|
||||
// Check if the touch started on a handle or within the expanded hue bar area.
|
||||
if (hueHitRect.contains(x, y)) {
|
||||
isDraggingHue = true;
|
||||
updateHueFromTouch(y);
|
||||
} else if (satValHitRect.contains(x, y)) {
|
||||
isDraggingSaturation = true;
|
||||
updateSaturationValueFromTouch(x, y);
|
||||
} else if (expandedHueRect.contains(x, y)) {
|
||||
// Handle touch within the expanded hue bar area.
|
||||
isDraggingHue = true;
|
||||
updateHueFromTouch(y);
|
||||
} else if (saturationValueRect.contains(x, y)) {
|
||||
isDraggingSaturation = true;
|
||||
updateSaturationValueFromTouch(x, y);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
// Continue updating values even if touch moves outside the view.
|
||||
if (isDraggingHue) {
|
||||
updateHueFromTouch(y);
|
||||
} else if (isDraggingSaturation) {
|
||||
updateSaturationValueFromTouch(x, y);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
isDraggingHue = false;
|
||||
isDraggingSaturation = false;
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onTouchEvent failure", ex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the hue value based on touch position, clamping to valid range.
|
||||
*
|
||||
* @param y The y-coordinate of the touch position.
|
||||
*/
|
||||
private void updateHueFromTouch(float y) {
|
||||
// Clamp y to the hue rectangle bounds.
|
||||
final float clampedY = Utils.clamp(y, hueRect.top, hueRect.bottom);
|
||||
final float updatedHue = ((clampedY - hueRect.top) / hueRect.height()) * 360f;
|
||||
if (hue == updatedHue) {
|
||||
return;
|
||||
}
|
||||
|
||||
hue = updatedHue;
|
||||
updateSaturationValueShader();
|
||||
updateSelectedColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates saturation and value based on touch position, clamping to valid range.
|
||||
*
|
||||
* @param x The x-coordinate of the touch position.
|
||||
* @param y The y-coordinate of the touch position.
|
||||
*/
|
||||
private void updateSaturationValueFromTouch(float x, float y) {
|
||||
// Clamp x and y to the saturation-value rectangle bounds.
|
||||
final float clampedX = Utils.clamp(x, saturationValueRect.left, saturationValueRect.right);
|
||||
final float clampedY = Utils.clamp(y, saturationValueRect.top, saturationValueRect.bottom);
|
||||
|
||||
final float updatedSaturation = (clampedX - saturationValueRect.left) / saturationValueRect.width();
|
||||
final float updatedValue = 1 - ((clampedY - saturationValueRect.top) / saturationValueRect.height());
|
||||
|
||||
if (saturation == updatedSaturation && value == updatedValue) {
|
||||
return;
|
||||
}
|
||||
saturation = updatedSaturation;
|
||||
value = updatedValue;
|
||||
updateSelectedColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the selected color and notifies listeners.
|
||||
*/
|
||||
private void updateSelectedColor() {
|
||||
final int updatedColor = Color.HSVToColor(0, new float[]{hue, saturation, value});
|
||||
|
||||
if (selectedColor != updatedColor) {
|
||||
selectedColor = updatedColor;
|
||||
|
||||
if (colorChangedListener != null) {
|
||||
colorChangedListener.onColorChanged(updatedColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Must always redraw, otherwise if saturation is pure grey or black
|
||||
// then the hue slider cannot be changed.
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the currently selected color.
|
||||
*
|
||||
* @param color The color to set in either ARGB or RGB format.
|
||||
*/
|
||||
public void setColor(@ColorInt int color) {
|
||||
color &= 0x00FFFFFF;
|
||||
if (selectedColor == color) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the selected color.
|
||||
selectedColor = color;
|
||||
Logger.printDebug(() -> "setColor: " + getColorString(selectedColor));
|
||||
|
||||
// Convert the ARGB color to HSV values.
|
||||
float[] hsv = new float[3];
|
||||
Color.colorToHSV(color, hsv);
|
||||
|
||||
// Update the hue, saturation, and value.
|
||||
hue = hsv[0];
|
||||
saturation = hsv[1];
|
||||
value = hsv[2];
|
||||
|
||||
// Update the saturation-value shader based on the new hue.
|
||||
updateSaturationValueShader();
|
||||
|
||||
// Notify the listener if it's set.
|
||||
if (colorChangedListener != null) {
|
||||
colorChangedListener.onColorChanged(selectedColor);
|
||||
}
|
||||
|
||||
// Invalidate the view to trigger a redraw.
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently selected color.
|
||||
*
|
||||
* @return The selected color in RGB format with no alpha channel.
|
||||
*/
|
||||
@ColorInt
|
||||
public int getColor() {
|
||||
return selectedColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to be notified when the selected color changes.
|
||||
*
|
||||
* @param listener The listener to set.
|
||||
*/
|
||||
public void setOnColorChangedListener(OnColorChangedListener listener) {
|
||||
colorChangedListener = listener;
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@ -54,7 +53,7 @@ public class ReVancedAboutPreference extends Preference {
|
||||
}
|
||||
|
||||
protected boolean isDarkModeEnabled() {
|
||||
return Utils.isDarkModeEnabled(getContext());
|
||||
return Utils.isDarkModeEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,6 @@ package app.revanced.extension.tiktok;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.isDarkModeEnabled;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
@ -43,8 +42,8 @@ public class Utils {
|
||||
private static final @ColorInt int TEXT_LIGHT_MODE_SUMMARY
|
||||
= Color.argb(255, 80, 80, 80);
|
||||
|
||||
public static void setTitleAndSummaryColor(Context context, View view) {
|
||||
final boolean darkModeEnabled = isDarkModeEnabled(context);
|
||||
public static void setTitleAndSummaryColor(View view) {
|
||||
final boolean darkModeEnabled = isDarkModeEnabled();
|
||||
|
||||
TextView title = view.findViewById(android.R.id.title);
|
||||
title.setTextColor(darkModeEnabled
|
||||
|
@ -101,7 +101,7 @@ public class DownloadPathPreference extends DialogPreference {
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
|
||||
Utils.setTitleAndSummaryColor(getContext(), view);
|
||||
Utils.setTitleAndSummaryColor(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -22,6 +22,6 @@ public class InputTextPreference extends EditTextPreference {
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
|
||||
Utils.setTitleAndSummaryColor(getContext(), view);
|
||||
Utils.setTitleAndSummaryColor(view);
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ public class RangeValuePreference extends DialogPreference {
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
|
||||
Utils.setTitleAndSummaryColor(getContext(), view);
|
||||
Utils.setTitleAndSummaryColor(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -48,6 +48,6 @@ public class ReVancedTikTokAboutPreference extends ReVancedAboutPreference {
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
|
||||
Utils.setTitleAndSummaryColor(getContext(), view);
|
||||
Utils.setTitleAndSummaryColor(view);
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,6 @@ public class TogglePreference extends SwitchPreference {
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
|
||||
Utils.setTitleAndSummaryColor(getContext(), view);
|
||||
Utils.setTitleAndSummaryColor(view);
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ public final class NavigationButtonsPatch {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Utils.isDarkModeEnabled(Utils.getContext())
|
||||
return Utils.isDarkModeEnabled()
|
||||
? !DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
|
||||
: !DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT;
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
@ -33,8 +31,7 @@ public final class WideSearchbarPatch {
|
||||
final int paddingRight = searchBarView.getPaddingRight();
|
||||
final int paddingTop = searchBarView.getPaddingTop();
|
||||
final int paddingBottom = searchBarView.getPaddingBottom();
|
||||
final int paddingStart = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
|
||||
8, Resources.getSystem().getDisplayMetrics());
|
||||
final int paddingStart = Utils.dipToPixels(8);
|
||||
|
||||
if (Utils.isRightToLeftLocale()) {
|
||||
searchBarView.setPadding(paddingLeft, paddingTop, paddingStart, paddingBottom);
|
||||
|
@ -20,13 +20,16 @@ import app.revanced.extension.youtube.settings.Settings;
|
||||
public class ProgressBarDrawable extends Drawable {
|
||||
|
||||
private final Paint paint = new Paint();
|
||||
{
|
||||
paint.setColor(SeekbarColorPatch.getSeekbarColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
if (Settings.HIDE_SEEKBAR_THUMBNAIL.get()) {
|
||||
return;
|
||||
}
|
||||
paint.setColor(SeekbarColorPatch.getSeekbarColor());
|
||||
|
||||
canvas.drawRect(getBounds(), paint);
|
||||
}
|
||||
|
||||
|
@ -179,7 +179,7 @@ public final class SeekbarColorPatch {
|
||||
//noinspection ConstantConditions
|
||||
if (false) { // Set true to force slow animation for development.
|
||||
final int longAnimation = Utils.getResourceIdentifier(
|
||||
Utils.isDarkModeEnabled(Utils.getContext())
|
||||
Utils.isDarkModeEnabled()
|
||||
? "startup_animation_5s_30fps_dark"
|
||||
: "startup_animation_5s_30fps_light",
|
||||
"raw");
|
||||
|
@ -21,8 +21,6 @@ import android.text.Spanned;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
@ -120,16 +118,13 @@ public class ReturnYouTubeDislike {
|
||||
private static final ShapeDrawable leftSeparatorShape;
|
||||
|
||||
static {
|
||||
DisplayMetrics dp = Objects.requireNonNull(Utils.getContext()).getResources().getDisplayMetrics();
|
||||
|
||||
leftSeparatorBounds = new Rect(0, 0,
|
||||
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1.2f, dp),
|
||||
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, dp));
|
||||
final int middleSeparatorSize =
|
||||
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
|
||||
Utils.dipToPixels(1.2f),
|
||||
Utils.dipToPixels(14f));
|
||||
final int middleSeparatorSize = Utils.dipToPixels(3.7f);
|
||||
middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize);
|
||||
|
||||
leftSeparatorShapePaddingPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8.4f, dp);
|
||||
leftSeparatorShapePaddingPixels = Utils.dipToPixels(8.4f);
|
||||
|
||||
leftSeparatorShape = new ShapeDrawable(new RectShape());
|
||||
leftSeparatorShape.setBounds(leftSeparatorBounds);
|
||||
|
@ -6,7 +6,6 @@ import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
@ -119,8 +118,7 @@ public class LicenseActivityHook {
|
||||
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
|
||||
toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string"));
|
||||
|
||||
final int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16,
|
||||
Utils.getContext().getResources().getDisplayMetrics());
|
||||
final int margin = Utils.dipToPixels(16);
|
||||
toolbar.setTitleMarginStart(margin);
|
||||
toolbar.setTitleMarginEnd(margin);
|
||||
TextView toolbarTextView = Utils.getChildView(toolbar, false,
|
||||
|
@ -17,7 +17,6 @@ import android.preference.SwitchPreference;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.TextView;
|
||||
@ -245,9 +244,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
toolbar.setNavigationIcon(getBackButtonDrawable());
|
||||
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
|
||||
|
||||
final int margin = (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()
|
||||
);
|
||||
final int margin = Utils.dipToPixels(16);
|
||||
toolbar.setTitleMargin(margin, 0, margin, 0);
|
||||
|
||||
TextView toolbarTextView = Utils.getChildView(toolbar,
|
||||
|
@ -5,7 +5,6 @@ import static app.revanced.extension.shared.StringRef.str;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -727,15 +726,11 @@ public class SegmentPlaybackController {
|
||||
}
|
||||
}
|
||||
|
||||
private static int highlightSegmentTimeBarScreenWidth = -1; // actual pixel width to use
|
||||
private static int getHighlightSegmentTimeBarScreenWidth() {
|
||||
if (highlightSegmentTimeBarScreenWidth == -1) {
|
||||
highlightSegmentTimeBarScreenWidth = (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH,
|
||||
Objects.requireNonNull(Utils.getContext()).getResources().getDisplayMetrics());
|
||||
}
|
||||
return highlightSegmentTimeBarScreenWidth;
|
||||
}
|
||||
/**
|
||||
* Actual screen pixel width to use for the highlight segment time bar.
|
||||
*/
|
||||
private static final int highlightSegmentTimeBarScreenWidth
|
||||
= Utils.dipToPixels(HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH);
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
@ -757,7 +752,7 @@ public class SegmentPlaybackController {
|
||||
final float left = leftPadding + segment.start * videoMillisecondsToPixels;
|
||||
final float right;
|
||||
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
||||
right = left + getHighlightSegmentTimeBarScreenWidth();
|
||||
right = left + highlightSegmentTimeBarScreenWidth;
|
||||
} else {
|
||||
right = leftPadding + segment.end * videoMillisecondsToPixels;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package app.revanced.extension.youtube.sponsorblock.objects;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.sf;
|
||||
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.COLOR_DOT_STRING;
|
||||
import static app.revanced.extension.youtube.settings.Settings.*;
|
||||
|
||||
import android.graphics.Color;
|
||||
@ -9,7 +10,9 @@ import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@ -134,7 +137,8 @@ public enum SegmentCategory {
|
||||
updateEnabledCategories();
|
||||
}
|
||||
|
||||
public static int applyOpacityToColor(int color, float opacity) {
|
||||
@ColorInt
|
||||
public static int applyOpacityToColor(@ColorInt int color, float opacity) {
|
||||
if (opacity < 0 || opacity > 1.0f) {
|
||||
throw new IllegalArgumentException("Invalid opacity: " + opacity);
|
||||
}
|
||||
@ -165,29 +169,28 @@ public enum SegmentCategory {
|
||||
/**
|
||||
* Skipped segment toast, if the skip occurred in the first quarter of the video
|
||||
*/
|
||||
@NonNull
|
||||
public final StringRef skippedToastBeginning;
|
||||
/**
|
||||
* Skipped segment toast, if the skip occurred in the middle half of the video
|
||||
*/
|
||||
@NonNull
|
||||
public final StringRef skippedToastMiddle;
|
||||
/**
|
||||
* Skipped segment toast, if the skip occurred in the last quarter of the video
|
||||
*/
|
||||
@NonNull
|
||||
public final StringRef skippedToastEnd;
|
||||
|
||||
@NonNull
|
||||
public final Paint paint;
|
||||
|
||||
/**
|
||||
* Category color with opacity applied.
|
||||
*/
|
||||
@ColorInt
|
||||
private int color;
|
||||
|
||||
/**
|
||||
* Value must be changed using {@link #setBehaviour(CategoryBehaviour)}.
|
||||
* Caller must also {@link #updateEnabledCategories()}.
|
||||
*/
|
||||
@NonNull
|
||||
public CategoryBehaviour behaviour = CategoryBehaviour.IGNORE;
|
||||
|
||||
SegmentCategory(String keyValue, StringRef title, StringRef description,
|
||||
@ -247,7 +250,7 @@ public enum SegmentCategory {
|
||||
}
|
||||
}
|
||||
|
||||
public void setBehaviour(@NonNull CategoryBehaviour behaviour) {
|
||||
public void setBehaviour(CategoryBehaviour behaviour) {
|
||||
this.behaviour = Objects.requireNonNull(behaviour);
|
||||
this.behaviorSetting.save(behaviour.reVancedKeyValue);
|
||||
}
|
||||
@ -273,6 +276,10 @@ public enum SegmentCategory {
|
||||
return opacitySetting.get();
|
||||
}
|
||||
|
||||
public float getOpacityDefault() {
|
||||
return opacitySetting.defaultValue;
|
||||
}
|
||||
|
||||
public void resetColorAndOpacity() {
|
||||
setColor(colorSetting.defaultValue);
|
||||
setOpacity(opacitySetting.defaultValue);
|
||||
@ -291,10 +298,19 @@ public enum SegmentCategory {
|
||||
/**
|
||||
* @return Integer color of #RRGGBB format.
|
||||
*/
|
||||
@ColorInt
|
||||
public int getColorNoOpacity() {
|
||||
return color & 0x00FFFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Integer color of #RRGGBB format.
|
||||
*/
|
||||
@ColorInt
|
||||
public int getColorNoOpacityDefault() {
|
||||
return Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Hex color string of #RRGGBB format with no opacity level.
|
||||
*/
|
||||
@ -302,22 +318,27 @@ public enum SegmentCategory {
|
||||
return String.format(Locale.US, "#%06X", getColorNoOpacity());
|
||||
}
|
||||
|
||||
private static SpannableString getCategoryColorDotSpan(String text, int color) {
|
||||
SpannableString dotSpan = new SpannableString('⬤' + text);
|
||||
private static SpannableString getCategoryColorDotSpan(String text, @ColorInt int color) {
|
||||
SpannableString dotSpan = new SpannableString(COLOR_DOT_STRING + text);
|
||||
dotSpan.setSpan(new ForegroundColorSpan(color), 0, 1,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return dotSpan;
|
||||
}
|
||||
|
||||
public static SpannableString getCategoryColorDot(int color) {
|
||||
return getCategoryColorDotSpan("", color);
|
||||
public static SpannableString getCategoryColorDot(@ColorInt int color) {
|
||||
SpannableString dotSpan = new SpannableString(COLOR_DOT_STRING);
|
||||
dotSpan.setSpan(new ForegroundColorSpan(color), 0, 1,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
dotSpan.setSpan(new RelativeSizeSpan(1.5f), 0, 1,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return dotSpan;
|
||||
}
|
||||
|
||||
public SpannableString getCategoryColorDot() {
|
||||
return getCategoryColorDot(color);
|
||||
}
|
||||
|
||||
public SpannableString getTitleWithColorDot(int categoryColor) {
|
||||
public SpannableString getTitleWithColorDot(@ColorInt int categoryColor) {
|
||||
return getCategoryColorDotSpan(" " + title, categoryColor);
|
||||
}
|
||||
|
||||
|
@ -1,35 +1,46 @@
|
||||
package app.revanced.extension.youtube.sponsorblock.objects;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.preference.ListPreference;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.GridLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
||||
import app.revanced.extension.shared.settings.preference.ColorPickerView;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class SegmentCategoryListPreference extends ListPreference {
|
||||
private final SegmentCategory category;
|
||||
private TextView colorDotView;
|
||||
private EditText colorEditText;
|
||||
private EditText opacityEditText;
|
||||
|
||||
/**
|
||||
* #RRGGBB
|
||||
* RGB format (no alpha).
|
||||
*/
|
||||
@ColorInt
|
||||
private int categoryColor;
|
||||
/**
|
||||
* [0, 1]
|
||||
@ -37,6 +48,11 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
private float categoryOpacity;
|
||||
private int selectedDialogEntryIndex;
|
||||
|
||||
private TextView dialogColorDotView;
|
||||
private EditText dialogColorEditText;
|
||||
private EditText dialogOpacityEditText;
|
||||
private ColorPickerView dialogColorPickerView;
|
||||
|
||||
public SegmentCategoryListPreference(Context context, SegmentCategory category) {
|
||||
super(context);
|
||||
this.category = Objects.requireNonNull(category);
|
||||
@ -67,8 +83,20 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
categoryOpacity = category.getOpacity();
|
||||
|
||||
Context context = builder.getContext();
|
||||
LinearLayout mainLayout = new LinearLayout(context);
|
||||
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
mainLayout.setPadding(70, 0, 70, 0);
|
||||
|
||||
// Inflate the color picker view.
|
||||
View colorPickerContainer = LayoutInflater.from(context)
|
||||
.inflate(getResourceIdentifier("revanced_color_picker", "layout"), null);
|
||||
dialogColorPickerView = colorPickerContainer.findViewById(
|
||||
getResourceIdentifier("color_picker_view", "id"));
|
||||
dialogColorPickerView.setColor(categoryColor);
|
||||
mainLayout.addView(colorPickerContainer);
|
||||
|
||||
// Grid layout for color and opacity inputs.
|
||||
GridLayout gridLayout = new GridLayout(context);
|
||||
gridLayout.setPadding(70, 0, 150, 0); // Padding for the entire layout.
|
||||
gridLayout.setColumnCount(3);
|
||||
gridLayout.setRowCount(2);
|
||||
|
||||
@ -84,19 +112,22 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
gridParams.rowSpec = GridLayout.spec(0); // First row.
|
||||
gridParams.columnSpec = GridLayout.spec(1); // Second column.
|
||||
gridParams.setMargins(0, 0, 10, 0);
|
||||
colorDotView = new TextView(context);
|
||||
colorDotView.setLayoutParams(gridParams);
|
||||
gridLayout.addView(colorDotView);
|
||||
dialogColorDotView = new TextView(context);
|
||||
dialogColorDotView.setLayoutParams(gridParams);
|
||||
gridLayout.addView(dialogColorDotView);
|
||||
updateCategoryColorDot();
|
||||
|
||||
gridParams = new GridLayout.LayoutParams();
|
||||
gridParams.rowSpec = GridLayout.spec(0); // First row.
|
||||
gridParams.columnSpec = GridLayout.spec(2); // Third column.
|
||||
colorEditText = new EditText(context);
|
||||
colorEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
|
||||
colorEditText.setTextLocale(Locale.US);
|
||||
colorEditText.setText(category.getColorString());
|
||||
colorEditText.addTextChangedListener(new TextWatcher() {
|
||||
dialogColorEditText = new EditText(context);
|
||||
dialogColorEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
dialogColorEditText.setAutofillHints((String) null);
|
||||
dialogColorEditText.setTypeface(Typeface.MONOSPACE);
|
||||
dialogColorEditText.setTextLocale(Locale.US);
|
||||
dialogColorEditText.setText(getColorString(categoryColor));
|
||||
dialogColorEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
@ -109,28 +140,30 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
public void afterTextChanged(Editable edit) {
|
||||
try {
|
||||
String colorString = edit.toString();
|
||||
final int colorStringLength = colorString.length();
|
||||
String normalizedColorString = ColorPickerPreference.cleanupColorCodeString(colorString);
|
||||
|
||||
if (!colorString.startsWith("#")) {
|
||||
edit.insert(0, "#"); // Recursively calls back into this method.
|
||||
if (!normalizedColorString.equals(colorString)) {
|
||||
edit.replace(0, colorString.length(), normalizedColorString);
|
||||
return;
|
||||
}
|
||||
|
||||
final int maxColorStringLength = 7; // #RRGGBB
|
||||
if (colorStringLength > maxColorStringLength) {
|
||||
edit.delete(maxColorStringLength, colorStringLength);
|
||||
if (normalizedColorString.length() != ColorPickerPreference.COLOR_STRING_LENGTH) {
|
||||
// User is still typing out the color.
|
||||
return;
|
||||
}
|
||||
|
||||
categoryColor = Color.parseColor(colorString);
|
||||
updateCategoryColorDot();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// Ignore.
|
||||
// Remove the alpha channel.
|
||||
final int newColor = Color.parseColor(colorString) & 0x00FFFFFF;
|
||||
// Changing view color causes callback into this class.
|
||||
dialogColorPickerView.setColor(newColor);
|
||||
} catch (Exception ex) {
|
||||
// Should never be reached since input is validated before using.
|
||||
Logger.printException(() -> "colorEditText afterTextChanged failure", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
colorEditText.setLayoutParams(gridParams);
|
||||
gridLayout.addView(colorEditText);
|
||||
dialogColorEditText.setLayoutParams(gridParams);
|
||||
gridLayout.addView(dialogColorEditText);
|
||||
|
||||
gridParams = new GridLayout.LayoutParams();
|
||||
gridParams.rowSpec = GridLayout.spec(1); // Second row.
|
||||
@ -143,9 +176,13 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
gridParams = new GridLayout.LayoutParams();
|
||||
gridParams.rowSpec = GridLayout.spec(1); // Second row.
|
||||
gridParams.columnSpec = GridLayout.spec(2); // Third column.
|
||||
opacityEditText = new EditText(context);
|
||||
opacityEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
opacityEditText.addTextChangedListener(new TextWatcher() {
|
||||
dialogOpacityEditText = new EditText(context);
|
||||
dialogOpacityEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL
|
||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
dialogOpacityEditText.setAutofillHints((String) null);
|
||||
dialogOpacityEditText.setTypeface(Typeface.MONOSPACE);
|
||||
dialogOpacityEditText.setTextLocale(Locale.US);
|
||||
dialogOpacityEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
@ -183,31 +220,40 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
}
|
||||
|
||||
updateCategoryColorDot();
|
||||
} catch (NumberFormatException ex) {
|
||||
} catch (Exception ex) {
|
||||
// Should never happen.
|
||||
Logger.printException(() -> "Could not parse opacity string", ex);
|
||||
Logger.printException(() -> "opacityEditText afterTextChanged failure", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
opacityEditText.setLayoutParams(gridParams);
|
||||
gridLayout.addView(opacityEditText);
|
||||
dialogOpacityEditText.setLayoutParams(gridParams);
|
||||
gridLayout.addView(dialogOpacityEditText);
|
||||
updateOpacityText();
|
||||
|
||||
builder.setView(gridLayout);
|
||||
mainLayout.addView(gridLayout);
|
||||
|
||||
// Set up color picker listener.
|
||||
// Do last to prevent listener callbacks while setting up view.
|
||||
dialogColorPickerView.setOnColorChangedListener(color -> {
|
||||
if (categoryColor == color) {
|
||||
return;
|
||||
}
|
||||
categoryColor = color;
|
||||
String hexColor = getColorString(color);
|
||||
Logger.printDebug(() -> "onColorChanged: " + hexColor);
|
||||
|
||||
updateCategoryColorDot();
|
||||
dialogColorEditText.setText(hexColor);
|
||||
dialogColorEditText.setSelection(hexColor.length());
|
||||
});
|
||||
|
||||
builder.setView(mainLayout);
|
||||
builder.setTitle(category.title.toString());
|
||||
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||
});
|
||||
builder.setNeutralButton(str("revanced_sb_reset_color"), (dialog, which) -> {
|
||||
try {
|
||||
category.resetColorAndOpacity();
|
||||
updateUI();
|
||||
Utils.showToastShort(str("revanced_sb_color_reset"));
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "setNeutralButton failure", ex);
|
||||
}
|
||||
});
|
||||
builder.setNeutralButton(str("revanced_settings_reset_color"), null);
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
|
||||
selectedDialogEntryIndex = findIndexOfValue(getValue());
|
||||
@ -218,6 +264,25 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showDialog(Bundle state) {
|
||||
super.showDialog(state);
|
||||
|
||||
// Do not close dialog when reset is pressed.
|
||||
Button button = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEUTRAL);
|
||||
button.setOnClickListener(view -> {
|
||||
try {
|
||||
// Setting view color causes callback to update the UI.
|
||||
dialogColorPickerView.setColor(category.getColorNoOpacityDefault());
|
||||
|
||||
categoryOpacity = category.getOpacityDefault();
|
||||
updateOpacityText();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "setOnClickListener failure", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
try {
|
||||
@ -230,43 +295,42 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
}
|
||||
|
||||
try {
|
||||
String colorString = colorEditText.getText().toString();
|
||||
if (!colorString.equals(category.getColorString()) || categoryOpacity != category.getOpacity()) {
|
||||
category.setColor(colorString);
|
||||
category.setOpacity(categoryOpacity);
|
||||
Utils.showToastShort(str("revanced_sb_color_changed"));
|
||||
}
|
||||
category.setColor(dialogColorEditText.getText().toString());
|
||||
category.setOpacity(categoryOpacity);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Utils.showToastShort(str("revanced_sb_color_invalid"));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void applyOpacityToCategoryColor() {
|
||||
categoryColor = applyOpacityToColor(categoryColor, categoryOpacity);
|
||||
@ColorInt
|
||||
private int applyOpacityToCategoryColor() {
|
||||
return applyOpacityToColor(categoryColor, categoryOpacity);
|
||||
}
|
||||
|
||||
public void updateUI() {
|
||||
categoryColor = category.getColorNoOpacity();
|
||||
categoryOpacity = category.getOpacity();
|
||||
applyOpacityToCategoryColor();
|
||||
|
||||
setTitle(category.getTitleWithColorDot(categoryColor));
|
||||
setTitle(category.getTitleWithColorDot(applyOpacityToCategoryColor()));
|
||||
}
|
||||
|
||||
private void updateCategoryColorDot() {
|
||||
applyOpacityToCategoryColor();
|
||||
|
||||
colorDotView.setText(SegmentCategory.getCategoryColorDot(categoryColor));
|
||||
dialogColorDotView.setText(SegmentCategory.getCategoryColorDot(applyOpacityToCategoryColor()));
|
||||
}
|
||||
|
||||
private void updateOpacityText() {
|
||||
opacityEditText.setText(String.format(Locale.US, "%.2f", categoryOpacity));
|
||||
dialogOpacityEditText.setText(String.format(Locale.US, "%.2f", categoryOpacity));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -277,4 +341,4 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
// This is required otherwise the ReVanced preference fragment
|
||||
// sets all ListPreference summaries to show the current selection.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -421,7 +421,7 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
.setTitle(apiUrl.getTitle())
|
||||
.setView(editText)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setNeutralButton(str("revanced_sb_reset"), urlChangeListener)
|
||||
.setNeutralButton(str("revanced_settings_reset"), urlChangeListener)
|
||||
.setPositiveButton(android.R.string.ok, urlChangeListener)
|
||||
.show();
|
||||
return true;
|
||||
|
@ -48,7 +48,9 @@ private val swipeControlsResourcePatch = resourcePatch {
|
||||
summaryKey = null,
|
||||
),
|
||||
TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER),
|
||||
TextPreference("revanced_swipe_overlay_progress_color", inputType = InputType.TEXT_CAP_CHARACTERS),
|
||||
TextPreference("revanced_swipe_overlay_progress_color",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
|
||||
inputType = InputType.TEXT_CAP_CHARACTERS),
|
||||
TextPreference("revanced_swipe_text_overlay_size", inputType = InputType.NUMBER),
|
||||
TextPreference("revanced_swipe_overlay_timeout", inputType = InputType.NUMBER),
|
||||
TextPreference("revanced_swipe_threshold", inputType = InputType.NUMBER),
|
||||
|
@ -89,14 +89,15 @@ val themePatch = bytecodePatch(
|
||||
execute {
|
||||
val preferences = mutableSetOf<BasePreference>(
|
||||
SwitchPreference("revanced_seekbar_custom_color"),
|
||||
TextPreference("revanced_seekbar_custom_color_primary", inputType = InputType.TEXT_CAP_CHARACTERS),
|
||||
TextPreference("revanced_seekbar_custom_color_primary",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
|
||||
inputType = InputType.TEXT_CAP_CHARACTERS),
|
||||
)
|
||||
|
||||
if (is_19_25_or_greater) {
|
||||
preferences += TextPreference(
|
||||
"revanced_seekbar_custom_color_accent",
|
||||
inputType = InputType.TEXT_CAP_CHARACTERS
|
||||
)
|
||||
preferences += TextPreference("revanced_seekbar_custom_color_accent",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
|
||||
inputType = InputType.TEXT_CAP_CHARACTERS)
|
||||
}
|
||||
|
||||
PreferenceScreen.SEEKBAR.addPreferences(
|
||||
|
@ -74,6 +74,7 @@ private val settingsResourcePatch = resourcePatch {
|
||||
|
||||
arrayOf(
|
||||
ResourceGroup("drawable",
|
||||
"revanced_settings_circle_background.xml",
|
||||
"revanced_settings_cursor.xml",
|
||||
"revanced_settings_icon.xml",
|
||||
"revanced_settings_screen_00_about.xml",
|
||||
@ -91,6 +92,8 @@ private val settingsResourcePatch = resourcePatch {
|
||||
"revanced_settings_screen_12_video.xml",
|
||||
),
|
||||
ResourceGroup("layout",
|
||||
"revanced_color_dot_widget.xml",
|
||||
"revanced_color_picker.xml",
|
||||
"revanced_preference_with_icon_no_search_result.xml",
|
||||
"revanced_search_suggestion_item.xml",
|
||||
"revanced_settings_with_toolbar.xml"),
|
||||
|
@ -35,6 +35,8 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_settings_title" translatable="false">ReVanced</string>
|
||||
<string name="revanced_settings_confirm_user_dialog_title">Do you wish to proceed?</string>
|
||||
<string name="revanced_settings_reset">Reset</string>
|
||||
<string name="revanced_settings_reset_color">Reset color</string>
|
||||
<string name="revanced_settings_color_invalid">Invalid color</string>
|
||||
<string name="revanced_settings_restart_title">Refresh and restart</string>
|
||||
<string name="revanced_settings_restart">Restart</string>
|
||||
<string name="revanced_settings_import">Import</string>
|
||||
@ -1161,11 +1163,6 @@ Ready to submit?"</string>
|
||||
<string name="revanced_sb_stats_saved_second_format">%s seconds</string>
|
||||
<string name="revanced_sb_color_opacity_label">Opacity:</string>
|
||||
<string name="revanced_sb_color_dot_label">Color:</string>
|
||||
<string name="revanced_sb_color_changed">Color changed</string>
|
||||
<string name="revanced_sb_color_reset">Color reset</string>
|
||||
<string name="revanced_sb_color_invalid">Invalid color code</string>
|
||||
<string name="revanced_sb_reset_color">Reset color</string>
|
||||
<string name="revanced_sb_reset">Reset</string>
|
||||
<string name="revanced_sb_about_title">About</string>
|
||||
<string name="revanced_sb_about_api_title" translatable="false">sponsor.ajay.app</string>
|
||||
<string name="revanced_sb_about_api_summary">Data is provided by the SponsorBlock API. Tap here to learn more and see downloads for other platforms</string>
|
||||
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="#FF0000" />
|
||||
<size
|
||||
android:width="20dp"
|
||||
android:height="20dp" />
|
||||
</shape>
|
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
<View
|
||||
android:id="@+id/revanced_color_dot_widget"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/revanced_settings_circle_background"
|
||||
android:elevation="2dp"
|
||||
android:translationZ="2dp"
|
||||
android:outlineProvider="background" />
|
||||
</FrameLayout>
|
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<app.revanced.extension.shared.settings.preference.ColorPickerView
|
||||
android:id="@+id/color_picker_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp" />
|
||||
</LinearLayout>
|
Loading…
x
Reference in New Issue
Block a user