mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-31 14:41:03 +00:00
fix(YouTube - Settings): Use an overlay to show search results (#5806)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@@ -65,6 +66,17 @@ public final class AlternativeThumbnailsPatch {
|
||||
public boolean isAvailable() {
|
||||
return usingDeArrowAnywhere();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Setting<?>> getParentSettings() {
|
||||
return List.of(
|
||||
ALT_THUMBNAIL_HOME,
|
||||
ALT_THUMBNAIL_SUBSCRIPTIONS,
|
||||
ALT_THUMBNAIL_LIBRARY,
|
||||
ALT_THUMBNAIL_PLAYER,
|
||||
ALT_THUMBNAIL_SEARCH
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class StillImagesAvailability implements Setting.Availability {
|
||||
@@ -80,6 +92,17 @@ public final class AlternativeThumbnailsPatch {
|
||||
public boolean isAvailable() {
|
||||
return usingStillImagesAnywhere();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Setting<?>> getParentSettings() {
|
||||
return List.of(
|
||||
ALT_THUMBNAIL_HOME,
|
||||
ALT_THUMBNAIL_SUBSCRIPTIONS,
|
||||
ALT_THUMBNAIL_LIBRARY,
|
||||
ALT_THUMBNAIL_PLAYER,
|
||||
ALT_THUMBNAIL_SEARCH
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ThumbnailOption {
|
||||
@@ -451,29 +474,29 @@ public final class AlternativeThumbnailsPatch {
|
||||
}
|
||||
|
||||
final boolean useFastQuality = Settings.ALT_THUMBNAIL_STILLS_FAST.get();
|
||||
switch (quality) {
|
||||
case SDDEFAULT:
|
||||
// SD alt images have somewhat worse quality with washed out color and poor contrast.
|
||||
// But the 720 images look much better and don't suffer from these issues.
|
||||
// For unknown reasons, the 720 thumbnails are used only for the home feed,
|
||||
// while SD is used for the search and subscription feed
|
||||
// (even though search and subscriptions use the exact same layout as the home feed).
|
||||
// Of note, this image quality issue only appears with the alt thumbnail images,
|
||||
// and the regular thumbnails have identical color/contrast quality for all sizes.
|
||||
// Fix this by falling thru and upgrading SD to 720.
|
||||
case HQ720:
|
||||
return switch (quality) {
|
||||
// SD alt images have somewhat worse quality with washed out color and poor contrast.
|
||||
// But the 720 images look much better and don't suffer from these issues.
|
||||
// For unknown reasons, the 720 thumbnails are used only for the home feed,
|
||||
// while SD is used for the search and subscription feed
|
||||
// (even though search and subscriptions use the exact same layout as the home feed).
|
||||
// Of note, this image quality issue only appears with the alt thumbnail images,
|
||||
// and the regular thumbnails have identical color/contrast quality for all sizes.
|
||||
// Fix this by falling thru and upgrading SD to 720.
|
||||
case SDDEFAULT, HQ720 -> { // SD is max resolution for fast alt images.
|
||||
if (useFastQuality) {
|
||||
return SDDEFAULT; // SD is max resolution for fast alt images.
|
||||
yield SDDEFAULT;
|
||||
}
|
||||
return HQ720;
|
||||
case MAXRESDEFAULT:
|
||||
yield HQ720;
|
||||
}
|
||||
case MAXRESDEFAULT -> {
|
||||
if (useFastQuality) {
|
||||
return SDDEFAULT;
|
||||
yield SDDEFAULT;
|
||||
}
|
||||
return MAXRESDEFAULT;
|
||||
default:
|
||||
return quality;
|
||||
}
|
||||
yield MAXRESDEFAULT;
|
||||
}
|
||||
default -> quality;
|
||||
};
|
||||
}
|
||||
|
||||
final String originalName;
|
||||
|
||||
@@ -12,6 +12,8 @@ import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class ChangeStartPagePatch {
|
||||
|
||||
@@ -87,6 +89,11 @@ public final class ChangeStartPagePatch {
|
||||
public boolean isAvailable() {
|
||||
return Settings.CHANGE_START_PAGE.get() != StartPage.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Setting<?>> getParentSettings() {
|
||||
return List.of(Settings.CHANGE_START_PAGE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.net.UnknownHostException;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.ui.CustomDialog;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -68,7 +69,7 @@ public class CheckWatchHistoryDomainNameResolutionPatch {
|
||||
Utils.runOnMainThread(() -> {
|
||||
try {
|
||||
// Create the custom dialog.
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||
context,
|
||||
str("revanced_check_watch_history_domain_name_dialog_title"), // Title.
|
||||
Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message")), // Message (HTML).
|
||||
|
||||
@@ -27,11 +27,11 @@ public final class DownloadsPatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* <p>
|
||||
* Called from the in app download hook,
|
||||
* for both the player action button (below the video)
|
||||
* and the 'Download video' flyout option for feed videos.
|
||||
*
|
||||
* <p>
|
||||
* Appears to always be called from the main thread.
|
||||
*/
|
||||
public static boolean inAppDownloadButtonOnClick(String videoId) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
@@ -37,11 +39,11 @@ public final class HidePlayerOverlayButtonsPatch {
|
||||
private static final boolean HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS_ENABLED
|
||||
= Settings.HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS.get();
|
||||
|
||||
private static final int PLAYER_CONTROL_PREVIOUS_BUTTON_TOUCH_AREA_ID =
|
||||
Utils.getResourceIdentifier("player_control_previous_button_touch_area", "id");
|
||||
private static final int PLAYER_CONTROL_PREVIOUS_BUTTON_TOUCH_AREA_ID = getResourceIdentifierOrThrow(
|
||||
"player_control_previous_button_touch_area", "id");
|
||||
|
||||
private static final int PLAYER_CONTROL_NEXT_BUTTON_TOUCH_AREA_ID =
|
||||
Utils.getResourceIdentifier("player_control_next_button_touch_area", "id");
|
||||
private static final int PLAYER_CONTROL_NEXT_BUTTON_TOUCH_AREA_ID = getResourceIdentifierOrThrow(
|
||||
"player_control_next_button_touch_area", "id");
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
|
||||
@@ -12,12 +12,14 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings({"unused", "SpellCheckingInspection"})
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
public final class MiniplayerPatch {
|
||||
|
||||
/**
|
||||
@@ -173,6 +175,14 @@ public final class MiniplayerPatch {
|
||||
public boolean isAvailable() {
|
||||
return Settings.MINIPLAYER_TYPE.get().isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Setting<?>> getParentSettings() {
|
||||
return List.of(
|
||||
Settings.MINIPLAYER_TYPE,
|
||||
Settings.MINIPLAYER_DRAG_AND_DROP
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class MiniplayerHideOverlayButtonsAvailability implements Setting.Availability {
|
||||
@@ -185,11 +195,59 @@ public final class MiniplayerPatch {
|
||||
&& !Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get() && !Settings.MINIPLAYER_DRAG_AND_DROP.get())
|
||||
|| (IS_19_29_OR_GREATER && type == MODERN_3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Setting<?>> getParentSettings() {
|
||||
return List.of(
|
||||
Settings.MINIPLAYER_TYPE,
|
||||
Settings.MINIPLAYER_DOUBLE_TAP_ACTION,
|
||||
Settings.MINIPLAYER_DRAG_AND_DROP
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class MiniplayerAnyModernAvailability implements Setting.Availability {
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
MiniplayerType type = Settings.MINIPLAYER_TYPE.get();
|
||||
return type == MODERN_1 || type == MODERN_2 || type == MODERN_3 || type == MODERN_4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Setting<?>> getParentSettings() {
|
||||
return List.of(Settings.MINIPLAYER_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class MiniplayerHideSubtextsAvailability implements Setting.Availability {
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
MiniplayerType type = Settings.MINIPLAYER_TYPE.get();
|
||||
return type == MODERN_3 || type == MODERN_4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Setting<?>> getParentSettings() {
|
||||
return List.of(Settings.MINIPLAYER_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class MiniplayerHideRewindOrOverlayOpacityAvailability implements Setting.Availability {
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
MiniplayerType type = Settings.MINIPLAYER_TYPE.get();
|
||||
return type == MODERN_1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Setting<?>> getParentSettings() {
|
||||
return List.of(Settings.MINIPLAYER_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* <p>
|
||||
* Enables a handler that immediately closes the miniplayer when the video is minimized,
|
||||
* effectively disabling the miniplayer.
|
||||
*/
|
||||
@@ -378,4 +436,4 @@ public final class MiniplayerPatch {
|
||||
Logger.printException(() -> "playerOverlayGroupCreated failure", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package app.revanced.extension.youtube.patches;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SeekbarThumbnailsPatch {
|
||||
|
||||
@@ -11,6 +13,11 @@ public class SeekbarThumbnailsPatch {
|
||||
public boolean isAvailable() {
|
||||
return VersionCheckPatch.IS_19_17_OR_GREATER || !Settings.RESTORE_OLD_SEEKBAR_THUMBNAILS.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Setting<?>> getParentSettings() {
|
||||
return List.of(Settings.RESTORE_OLD_SEEKBAR_THUMBNAILS);
|
||||
}
|
||||
}
|
||||
|
||||
private static final boolean SEEKBAR_THUMBNAILS_HIGH_QUALITY_ENABLED
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import app.revanced.extension.shared.ui.CustomDialog;
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -125,7 +126,7 @@ public final class AnnouncementsPatch {
|
||||
|
||||
Utils.runOnMainThread(() -> {
|
||||
// Create the custom dialog and show the announcement.
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||
context,
|
||||
finalTitle, // Title.
|
||||
finalMessage, // Message.
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||
|
||||
@@ -17,6 +20,11 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||
// without a restart the setting will show as available when it's not.
|
||||
return AVAILABLE_ON_LAUNCH && !SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Setting<?>> getParentSettings() {
|
||||
return List.of(BaseSettings.SPOOF_VIDEO_STREAMS);
|
||||
}
|
||||
}
|
||||
|
||||
private final ByteArrayFilterGroupList flyoutFilterGroupList = new ByteArrayFilterGroupList();
|
||||
|
||||
@@ -2,11 +2,11 @@ package app.revanced.extension.youtube.patches.playback.speed;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.youtube.videoplayer.PlayerControlButton.fadeInDuration;
|
||||
import static app.revanced.extension.youtube.videoplayer.PlayerControlButton.getDialogBackgroundColor;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
@@ -20,19 +20,9 @@ import android.graphics.drawable.shapes.RoundRectShape;
|
||||
import android.icu.text.NumberFormat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.GridLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.*;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
@@ -40,6 +30,7 @@ import java.util.function.Function;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.ui.SheetBottomDialog;
|
||||
import app.revanced.extension.youtube.patches.VideoInformation;
|
||||
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilter;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
@@ -97,7 +88,7 @@ public class CustomPlaybackSpeedPatch {
|
||||
/**
|
||||
* Weak reference to the currently open dialog.
|
||||
*/
|
||||
private static WeakReference<Dialog> currentDialog = new WeakReference<>(null);
|
||||
private static WeakReference<SheetBottomDialog.SlideDialog> currentDialog;
|
||||
|
||||
static {
|
||||
// Use same 2 digit format as built in speed picker,
|
||||
@@ -268,368 +259,239 @@ public class CustomPlaybackSpeedPatch {
|
||||
*/
|
||||
@SuppressLint("SetTextI18n")
|
||||
public static void showModernCustomPlaybackSpeedDialog(Context context) {
|
||||
// Create a dialog without a theme for custom appearance.
|
||||
Dialog dialog = new Dialog(context);
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar.
|
||||
try {
|
||||
// Create main layout.
|
||||
SheetBottomDialog.DraggableLinearLayout mainLayout =
|
||||
SheetBottomDialog.createMainLayout(context, getDialogBackgroundColor());
|
||||
|
||||
// Store the dialog reference.
|
||||
currentDialog = new WeakReference<>(dialog);
|
||||
// Preset size constants.
|
||||
final int dip4 = dipToPixels(4);
|
||||
final int dip8 = dipToPixels(8);
|
||||
final int dip12 = dipToPixels(12);
|
||||
final int dip20 = dipToPixels(20);
|
||||
final int dip32 = dipToPixels(32);
|
||||
final int dip60 = dipToPixels(60);
|
||||
|
||||
// Enable dismissing the dialog when tapping outside.
|
||||
dialog.setCanceledOnTouchOutside(true);
|
||||
// Display current playback speed.
|
||||
TextView currentSpeedText = new TextView(context);
|
||||
float currentSpeed = VideoInformation.getPlaybackSpeed();
|
||||
// Initially show with only 0 minimum digits, so 1.0 shows as 1x.
|
||||
currentSpeedText.setText(formatSpeedStringX(currentSpeed));
|
||||
currentSpeedText.setTextColor(Utils.getAppForegroundColor());
|
||||
currentSpeedText.setTextSize(16);
|
||||
currentSpeedText.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
currentSpeedText.setGravity(Gravity.CENTER);
|
||||
LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
textParams.setMargins(0, dip20, 0, 0);
|
||||
currentSpeedText.setLayoutParams(textParams);
|
||||
// Add current speed text view to main layout.
|
||||
mainLayout.addView(currentSpeedText);
|
||||
|
||||
// Create main vertical LinearLayout for dialog content.
|
||||
LinearLayout mainLayout = new LinearLayout(context);
|
||||
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
// Create horizontal layout for slider and +/- buttons.
|
||||
LinearLayout sliderLayout = new LinearLayout(context);
|
||||
sliderLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
sliderLayout.setGravity(Gravity.CENTER_VERTICAL);
|
||||
|
||||
// Preset size constants.
|
||||
final int dip4 = dipToPixels(4); // Height for handle bar.
|
||||
final int dip5 = dipToPixels(5);
|
||||
final int dip6 = dipToPixels(6); // Padding for mainLayout from bottom.
|
||||
final int dip8 = dipToPixels(8); // Padding for mainLayout from left and right.
|
||||
final int dip20 = dipToPixels(20);
|
||||
final int dip32 = dipToPixels(32); // Height for in-rows speed buttons.
|
||||
final int dip36 = dipToPixels(36); // Height for minus and plus buttons.
|
||||
final int dip40 = dipToPixels(40); // Width for handle bar.
|
||||
final int dip60 = dipToPixels(60); // Height for speed button container.
|
||||
// Create +/- buttons.
|
||||
Button minusButton = createStyledButton(context, false, dip8, dip8);
|
||||
Button plusButton = createStyledButton(context, true, dip8, dip8);
|
||||
|
||||
mainLayout.setPadding(dip5, dip8, dip5, dip8);
|
||||
// Create slider for speed adjustment.
|
||||
SeekBar speedSlider = new SeekBar(context);
|
||||
speedSlider.setFocusable(true);
|
||||
speedSlider.setFocusableInTouchMode(true);
|
||||
speedSlider.setMax(speedToProgressValue(customPlaybackSpeedsMax));
|
||||
speedSlider.setProgress(speedToProgressValue(currentSpeed));
|
||||
speedSlider.getProgressDrawable().setColorFilter(
|
||||
Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme progress bar.
|
||||
speedSlider.getThumb().setColorFilter(
|
||||
Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme slider thumb.
|
||||
LinearLayout.LayoutParams sliderParams = new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f);
|
||||
speedSlider.setLayoutParams(sliderParams);
|
||||
|
||||
// Set rounded rectangle background for the main layout.
|
||||
RoundRectShape roundRectShape = new RoundRectShape(
|
||||
Utils.createCornerRadii(12), null, null);
|
||||
ShapeDrawable background = new ShapeDrawable(roundRectShape);
|
||||
background.getPaint().setColor(Utils.getDialogBackgroundColor());
|
||||
mainLayout.setBackground(background);
|
||||
// Add -/+ and slider views to slider layout.
|
||||
sliderLayout.addView(minusButton);
|
||||
sliderLayout.addView(speedSlider);
|
||||
sliderLayout.addView(plusButton);
|
||||
|
||||
// Add handle bar at the top.
|
||||
View handleBar = new View(context);
|
||||
ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(4), null, null));
|
||||
handleBackground.getPaint().setColor(getAdjustedBackgroundColor(true));
|
||||
handleBar.setBackground(handleBackground);
|
||||
LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(
|
||||
dip40, // handle bar width.
|
||||
dip4 // handle bar height.
|
||||
);
|
||||
handleParams.gravity = Gravity.CENTER_HORIZONTAL; // Center horizontally.
|
||||
handleParams.setMargins(0, 0, 0, dip20); // 20dp bottom margins.
|
||||
handleBar.setLayoutParams(handleParams);
|
||||
// Add handle bar view to main layout.
|
||||
mainLayout.addView(handleBar);
|
||||
// Add slider layout to main layout.
|
||||
mainLayout.addView(sliderLayout);
|
||||
|
||||
// Display current playback speed.
|
||||
TextView currentSpeedText = new TextView(context);
|
||||
float currentSpeed = VideoInformation.getPlaybackSpeed();
|
||||
// Initially show with only 0 minimum digits, so 1.0 shows as 1x
|
||||
currentSpeedText.setText(formatSpeedStringX(currentSpeed));
|
||||
currentSpeedText.setTextColor(Utils.getAppForegroundColor());
|
||||
currentSpeedText.setTextSize(16);
|
||||
currentSpeedText.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
currentSpeedText.setGravity(Gravity.CENTER);
|
||||
LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
textParams.setMargins(0, 0, 0, 0);
|
||||
currentSpeedText.setLayoutParams(textParams);
|
||||
// Add current speed text view to main layout.
|
||||
mainLayout.addView(currentSpeedText);
|
||||
Function<Float, Void> userSelectedSpeed = newSpeed -> {
|
||||
final float roundedSpeed = roundSpeedToNearestIncrement(newSpeed);
|
||||
if (VideoInformation.getPlaybackSpeed() == roundedSpeed) {
|
||||
// Nothing has changed. New speed rounds to the current speed.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create horizontal layout for slider and +/- buttons.
|
||||
LinearLayout sliderLayout = new LinearLayout(context);
|
||||
sliderLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
sliderLayout.setGravity(Gravity.CENTER_VERTICAL);
|
||||
sliderLayout.setPadding(dip5, dip5, dip5, dip5); // 5dp padding.
|
||||
currentSpeedText.setText(formatSpeedStringX(roundedSpeed)); // Update display.
|
||||
speedSlider.setProgress(speedToProgressValue(roundedSpeed)); // Update slider.
|
||||
|
||||
// Create minus button.
|
||||
Button minusButton = new Button(context, null, 0); // Disable default theme style.
|
||||
minusButton.setText(""); // No text on button.
|
||||
ShapeDrawable minusBackground = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(20), null, null));
|
||||
minusBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
|
||||
minusButton.setBackground(minusBackground);
|
||||
OutlineSymbolDrawable minusDrawable = new OutlineSymbolDrawable(false); // Minus symbol.
|
||||
minusButton.setForeground(minusDrawable);
|
||||
LinearLayout.LayoutParams minusParams = new LinearLayout.LayoutParams(dip36, dip36);
|
||||
minusParams.setMargins(0, 0, dip5, 0); // 5dp to slider.
|
||||
minusButton.setLayoutParams(minusParams);
|
||||
|
||||
// Create slider for speed adjustment.
|
||||
SeekBar speedSlider = new SeekBar(context);
|
||||
speedSlider.setMax(speedToProgressValue(customPlaybackSpeedsMax));
|
||||
speedSlider.setProgress(speedToProgressValue(currentSpeed));
|
||||
speedSlider.getProgressDrawable().setColorFilter(
|
||||
Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme progress bar.
|
||||
speedSlider.getThumb().setColorFilter(
|
||||
Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme slider thumb.
|
||||
LinearLayout.LayoutParams sliderParams = new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f);
|
||||
sliderParams.setMargins(dip5, 0, dip5, 0); // 5dp to -/+ buttons.
|
||||
speedSlider.setLayoutParams(sliderParams);
|
||||
|
||||
// Create plus button.
|
||||
Button plusButton = new Button(context, null, 0); // Disable default theme style.
|
||||
plusButton.setText(""); // No text on button.
|
||||
ShapeDrawable plusBackground = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(20), null, null));
|
||||
plusBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
|
||||
plusButton.setBackground(plusBackground);
|
||||
OutlineSymbolDrawable plusDrawable = new OutlineSymbolDrawable(true); // Plus symbol.
|
||||
plusButton.setForeground(plusDrawable);
|
||||
LinearLayout.LayoutParams plusParams = new LinearLayout.LayoutParams(dip36, dip36);
|
||||
plusParams.setMargins(dip5, 0, 0, 0); // 5dp to slider.
|
||||
plusButton.setLayoutParams(plusParams);
|
||||
|
||||
// Add -/+ and slider views to slider layout.
|
||||
sliderLayout.addView(minusButton);
|
||||
sliderLayout.addView(speedSlider);
|
||||
sliderLayout.addView(plusButton);
|
||||
|
||||
LinearLayout.LayoutParams sliderLayoutParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
sliderLayoutParams.setMargins(0, 0, 0, dip5); // 5dp bottom margin.
|
||||
sliderLayout.setLayoutParams(sliderLayoutParams);
|
||||
|
||||
// Add slider layout to main layout.
|
||||
mainLayout.addView(sliderLayout);
|
||||
|
||||
Function<Float, Void> userSelectedSpeed = newSpeed -> {
|
||||
final float roundedSpeed = roundSpeedToNearestIncrement(newSpeed);
|
||||
if (VideoInformation.getPlaybackSpeed() == roundedSpeed) {
|
||||
// Nothing has changed. New speed rounds to the current speed.
|
||||
RememberPlaybackSpeedPatch.userSelectedPlaybackSpeed(roundedSpeed);
|
||||
VideoInformation.overridePlaybackSpeed(roundedSpeed);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
currentSpeedText.setText(formatSpeedStringX(roundedSpeed)); // Update display.
|
||||
speedSlider.setProgress(speedToProgressValue(roundedSpeed)); // Update slider.
|
||||
|
||||
RememberPlaybackSpeedPatch.userSelectedPlaybackSpeed(roundedSpeed);
|
||||
VideoInformation.overridePlaybackSpeed(roundedSpeed);
|
||||
return null;
|
||||
};
|
||||
|
||||
// Set listener for slider to update playback speed.
|
||||
speedSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (fromUser) {
|
||||
// Convert from progress value to video playback speed.
|
||||
userSelectedSpeed.apply(customPlaybackSpeedsMin + (progress / PROGRESS_BAR_VALUE_SCALE));
|
||||
// Set listener for slider to update playback speed.
|
||||
speedSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (fromUser) {
|
||||
// Convert from progress value to video playback speed.
|
||||
userSelectedSpeed.apply(customPlaybackSpeedsMin + (progress / PROGRESS_BAR_VALUE_SCALE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {}
|
||||
});
|
||||
|
||||
minusButton.setOnClickListener(v -> userSelectedSpeed.apply(
|
||||
(float) (VideoInformation.getPlaybackSpeed() - SPEED_ADJUSTMENT_CHANGE)));
|
||||
plusButton.setOnClickListener(v -> userSelectedSpeed.apply(
|
||||
(float) (VideoInformation.getPlaybackSpeed() + SPEED_ADJUSTMENT_CHANGE)));
|
||||
|
||||
// Create GridLayout for preset speed buttons.
|
||||
GridLayout gridLayout = new GridLayout(context);
|
||||
gridLayout.setColumnCount(5); // 5 columns for speed buttons.
|
||||
gridLayout.setAlignmentMode(GridLayout.ALIGN_BOUNDS);
|
||||
gridLayout.setRowCount((int) Math.ceil(customPlaybackSpeeds.length / 5.0));
|
||||
LinearLayout.LayoutParams gridParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
gridParams.setMargins(dip4, dip12, dip4, dip12); // Speed buttons container.
|
||||
gridLayout.setLayoutParams(gridParams);
|
||||
|
||||
// For button use 1 digit minimum.
|
||||
speedFormatter.setMinimumFractionDigits(1);
|
||||
|
||||
// Add buttons for each preset playback speed.
|
||||
for (float speed : customPlaybackSpeeds) {
|
||||
// Container for button and optional label.
|
||||
FrameLayout buttonContainer = new FrameLayout(context);
|
||||
|
||||
// Set layout parameters for each grid cell.
|
||||
GridLayout.LayoutParams containerParams = new GridLayout.LayoutParams();
|
||||
containerParams.width = 0; // Equal width for columns.
|
||||
containerParams.columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1, 1f);
|
||||
containerParams.setMargins(dip4, 0, dip4, 0); // Button margins.
|
||||
containerParams.height = dip60; // Fixed height for button and label.
|
||||
buttonContainer.setLayoutParams(containerParams);
|
||||
|
||||
// Create speed button.
|
||||
Button speedButton = new Button(context, null, 0);
|
||||
speedButton.setText(speedFormatter.format(speed));
|
||||
speedButton.setTextColor(Utils.getAppForegroundColor());
|
||||
speedButton.setTextSize(12);
|
||||
speedButton.setAllCaps(false);
|
||||
speedButton.setGravity(Gravity.CENTER);
|
||||
|
||||
ShapeDrawable buttonBackground = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(20), null, null));
|
||||
buttonBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
|
||||
speedButton.setBackground(buttonBackground);
|
||||
speedButton.setPadding(dip4, dip4, dip4, dip4);
|
||||
|
||||
// Center button vertically and stretch horizontally in container.
|
||||
FrameLayout.LayoutParams buttonParams = new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT, dip32, Gravity.CENTER);
|
||||
speedButton.setLayoutParams(buttonParams);
|
||||
|
||||
// Add speed buttons view to buttons container layout.
|
||||
buttonContainer.addView(speedButton);
|
||||
|
||||
// Add "Normal" label for 1.0x speed.
|
||||
if (speed == 1.0f) {
|
||||
TextView normalLabel = new TextView(context);
|
||||
// Use same 'Normal' string as stock YouTube.
|
||||
normalLabel.setText(str("normal_playback_rate_label"));
|
||||
normalLabel.setTextColor(Utils.getAppForegroundColor());
|
||||
normalLabel.setTextSize(10);
|
||||
normalLabel.setGravity(Gravity.CENTER);
|
||||
|
||||
FrameLayout.LayoutParams labelParams = new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
|
||||
labelParams.bottomMargin = 0; // Position label below button.
|
||||
normalLabel.setLayoutParams(labelParams);
|
||||
|
||||
buttonContainer.addView(normalLabel);
|
||||
}
|
||||
|
||||
speedButton.setOnClickListener(v -> userSelectedSpeed.apply(speed));
|
||||
|
||||
gridLayout.addView(buttonContainer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {}
|
||||
// Restore 2 digit minimum.
|
||||
speedFormatter.setMinimumFractionDigits(2);
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {}
|
||||
});
|
||||
// Add in-rows speed buttons layout to main layout.
|
||||
mainLayout.addView(gridLayout);
|
||||
|
||||
minusButton.setOnClickListener(v -> userSelectedSpeed.apply(
|
||||
(float) (VideoInformation.getPlaybackSpeed() - SPEED_ADJUSTMENT_CHANGE)));
|
||||
plusButton.setOnClickListener(v -> userSelectedSpeed.apply(
|
||||
(float) (VideoInformation.getPlaybackSpeed() + SPEED_ADJUSTMENT_CHANGE)));
|
||||
// Create dialog.
|
||||
SheetBottomDialog.SlideDialog dialog = SheetBottomDialog.createSlideDialog(context, mainLayout, fadeInDuration);
|
||||
currentDialog = new WeakReference<>(dialog);
|
||||
|
||||
// Create GridLayout for preset speed buttons.
|
||||
GridLayout gridLayout = new GridLayout(context);
|
||||
gridLayout.setColumnCount(5); // 5 columns for speed buttons.
|
||||
gridLayout.setAlignmentMode(GridLayout.ALIGN_BOUNDS);
|
||||
gridLayout.setRowCount((int) Math.ceil(customPlaybackSpeeds.length / 5.0));
|
||||
LinearLayout.LayoutParams gridParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
gridParams.setMargins(0, 0, 0, 0); // No margins around GridLayout.
|
||||
gridLayout.setLayoutParams(gridParams);
|
||||
// Create observer for PlayerType changes.
|
||||
Function1<PlayerType, Unit> playerTypeObserver = new Function1<>() {
|
||||
@Override
|
||||
public Unit invoke(PlayerType type) {
|
||||
SheetBottomDialog.SlideDialog current = currentDialog.get();
|
||||
if (current == null || !current.isShowing()) {
|
||||
// Should never happen.
|
||||
PlayerType.getOnChange().removeObserver(this);
|
||||
Logger.printException(() -> "Removing player type listener as dialog is null or closed");
|
||||
} else if (type == PlayerType.WATCH_WHILE_PICTURE_IN_PICTURE) {
|
||||
current.dismiss();
|
||||
Logger.printDebug(() -> "Playback speed dialog dismissed due to PiP mode");
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
};
|
||||
|
||||
// For button use 1 digit minimum.
|
||||
speedFormatter.setMinimumFractionDigits(1);
|
||||
// Add observer to dismiss dialog when entering PiP mode.
|
||||
PlayerType.getOnChange().addObserver(playerTypeObserver);
|
||||
|
||||
// Add buttons for each preset playback speed.
|
||||
for (float speed : customPlaybackSpeeds) {
|
||||
// Container for button and optional label.
|
||||
FrameLayout buttonContainer = new FrameLayout(context);
|
||||
// Remove observer when dialog is dismissed.
|
||||
dialog.setOnDismissListener(d -> {
|
||||
PlayerType.getOnChange().removeObserver(playerTypeObserver);
|
||||
Logger.printDebug(() -> "PlayerType observer removed on dialog dismiss");
|
||||
});
|
||||
|
||||
// Set layout parameters for each grid cell.
|
||||
GridLayout.LayoutParams containerParams = new GridLayout.LayoutParams();
|
||||
containerParams.width = 0; // Equal width for columns.
|
||||
containerParams.columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1, 1f);
|
||||
containerParams.setMargins(dip5, 0, dip5, 0); // Button margins.
|
||||
containerParams.height = dip60; // Fixed height for button and label.
|
||||
buttonContainer.setLayoutParams(containerParams);
|
||||
dialog.show(); // Show the dialog.
|
||||
|
||||
// Create speed button.
|
||||
Button speedButton = new Button(context, null, 0);
|
||||
speedButton.setText(speedFormatter.format(speed));
|
||||
speedButton.setTextColor(Utils.getAppForegroundColor());
|
||||
speedButton.setTextSize(12);
|
||||
speedButton.setAllCaps(false);
|
||||
speedButton.setGravity(Gravity.CENTER);
|
||||
|
||||
ShapeDrawable buttonBackground = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(20), null, null));
|
||||
buttonBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
|
||||
speedButton.setBackground(buttonBackground);
|
||||
speedButton.setPadding(dip5, dip5, dip5, dip5);
|
||||
|
||||
// Center button vertically and stretch horizontally in container.
|
||||
FrameLayout.LayoutParams buttonParams = new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT, dip32, Gravity.CENTER);
|
||||
speedButton.setLayoutParams(buttonParams);
|
||||
|
||||
// Add speed buttons view to buttons container layout.
|
||||
buttonContainer.addView(speedButton);
|
||||
|
||||
// Add "Normal" label for 1.0x speed.
|
||||
if (speed == 1.0f) {
|
||||
TextView normalLabel = new TextView(context);
|
||||
// Use same 'Normal' string as stock YouTube.
|
||||
normalLabel.setText(str("normal_playback_rate_label"));
|
||||
normalLabel.setTextColor(Utils.getAppForegroundColor());
|
||||
normalLabel.setTextSize(10);
|
||||
normalLabel.setGravity(Gravity.CENTER);
|
||||
|
||||
FrameLayout.LayoutParams labelParams = new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
|
||||
labelParams.bottomMargin = 0; // Position label below button.
|
||||
normalLabel.setLayoutParams(labelParams);
|
||||
|
||||
buttonContainer.addView(normalLabel);
|
||||
}
|
||||
|
||||
speedButton.setOnClickListener(v -> userSelectedSpeed.apply(speed));
|
||||
|
||||
gridLayout.addView(buttonContainer);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "showModernCustomPlaybackSpeedDialog failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore 2 digit minimum.
|
||||
speedFormatter.setMinimumFractionDigits(2);
|
||||
|
||||
// Add in-rows speed buttons layout to main layout.
|
||||
mainLayout.addView(gridLayout);
|
||||
|
||||
// Wrap mainLayout in another LinearLayout for side margins.
|
||||
LinearLayout wrapperLayout = new LinearLayout(context);
|
||||
wrapperLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
wrapperLayout.setPadding(dip8, 0, dip8, 0); // 8dp side margins.
|
||||
wrapperLayout.addView(mainLayout);
|
||||
dialog.setContentView(wrapperLayout);
|
||||
|
||||
// Configure dialog window to appear at the bottom.
|
||||
Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
WindowManager.LayoutParams params = window.getAttributes();
|
||||
params.gravity = Gravity.BOTTOM; // Position at bottom of screen.
|
||||
params.y = dip6; // 6dp margin from bottom.
|
||||
// In landscape, use the smaller dimension (height) as portrait width.
|
||||
int portraitWidth = context.getResources().getDisplayMetrics().widthPixels;
|
||||
if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
portraitWidth = Math.min(
|
||||
portraitWidth,
|
||||
context.getResources().getDisplayMetrics().heightPixels);
|
||||
}
|
||||
params.width = portraitWidth; // Use portrait width.
|
||||
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
window.setAttributes(params);
|
||||
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.
|
||||
Function1<PlayerType, Unit> playerTypeObserver = new Function1<>() {
|
||||
@Override
|
||||
public Unit invoke(PlayerType type) {
|
||||
Dialog current = currentDialog.get();
|
||||
if (current == null || !current.isShowing()) {
|
||||
// Should never happen.
|
||||
PlayerType.getOnChange().removeObserver(this);
|
||||
Logger.printException(() -> "Removing player type listener as dialog is null or closed");
|
||||
} else if (type == PlayerType.WATCH_WHILE_PICTURE_IN_PICTURE) {
|
||||
current.dismiss();
|
||||
Logger.printDebug(() -> "Playback speed dialog dismissed due to PiP mode");
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
};
|
||||
|
||||
// Add observer to dismiss dialog when entering PiP mode.
|
||||
PlayerType.getOnChange().addObserver(playerTypeObserver);
|
||||
|
||||
// Remove observer when dialog is dismissed.
|
||||
dialog.setOnDismissListener(d -> {
|
||||
PlayerType.getOnChange().removeObserver(playerTypeObserver);
|
||||
Logger.printDebug(() -> "PlayerType observer removed on dialog dismiss");
|
||||
});
|
||||
|
||||
dialog.show(); // Display the dialog.
|
||||
/**
|
||||
* Creates a styled button with a plus or minus symbol.
|
||||
*
|
||||
* @param context The Android context used to create the button.
|
||||
* @param isPlus True to display a plus symbol, false to display a minus symbol.
|
||||
* @param marginStart The start margin in pixels (left for LTR, right for RTL).
|
||||
* @param marginEnd The end margin in pixels (right for LTR, left for RTL).
|
||||
* @return A configured {@link Button} with the specified styling and layout parameters.
|
||||
*/
|
||||
private static Button createStyledButton(Context context, boolean isPlus, int marginStart, int marginEnd) {
|
||||
Button button = new Button(context, null, 0); // Disable default theme style.
|
||||
button.setText(""); // No text on button.
|
||||
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(20), null, null));
|
||||
background.getPaint().setColor(getAdjustedBackgroundColor(false));
|
||||
button.setBackground(background);
|
||||
button.setForeground(new OutlineSymbolDrawable(isPlus)); // Plus or minus symbol.
|
||||
final int dip36 = dipToPixels(36);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(dip36, dip36);
|
||||
params.setMargins(marginStart, 0, marginEnd, 0); // Set margins.
|
||||
button.setLayoutParams(params);
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -674,10 +536,9 @@ public class CustomPlaybackSpeedPatch {
|
||||
* for light themes to ensure visual contrast.
|
||||
*/
|
||||
public static int getAdjustedBackgroundColor(boolean isHandleBar) {
|
||||
final int baseColor = Utils.getDialogBackgroundColor();
|
||||
final float darkThemeFactor = isHandleBar ? 1.25f : 1.115f; // 1.25f for handleBar, 1.115f for others in dark theme.
|
||||
final float lightThemeFactor = isHandleBar ? 0.9f : 0.95f; // 0.9f for handleBar, 0.95f for others in light theme.
|
||||
return Utils.adjustColorBrightness(baseColor, lightThemeFactor, darkThemeFactor);
|
||||
return Utils.adjustColorBrightness(getDialogBackgroundColor(), lightThemeFactor, darkThemeFactor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -151,13 +151,10 @@ public final class SeekbarColorPatch {
|
||||
String seekbarStyle = get9BitStyleIdentifier(customSeekbarColor);
|
||||
Logger.printDebug(() -> "Using splash seekbar style: " + seekbarStyle);
|
||||
|
||||
final int styleIdentifierDefault = Utils.getResourceIdentifier(
|
||||
final int styleIdentifierDefault = Utils.getResourceIdentifierOrThrow(
|
||||
seekbarStyle,
|
||||
"style"
|
||||
);
|
||||
if (styleIdentifierDefault == 0) {
|
||||
throw new RuntimeException("Seekbar style not found: " + seekbarStyle);
|
||||
}
|
||||
|
||||
Resources.Theme theme = Utils.getContext().getResources().newTheme();
|
||||
theme.applyStyle(styleIdentifierDefault, true);
|
||||
|
||||
@@ -3,7 +3,7 @@ package app.revanced.extension.youtube.returnyoutubedislike.ui;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import app.revanced.extension.youtube.settings.preference.UrlLinkPreference;
|
||||
import app.revanced.extension.shared.settings.preference.UrlLinkPreference;
|
||||
|
||||
/**
|
||||
* Allows tapping the RYD about preference to open the website.
|
||||
|
||||
@@ -1,414 +0,0 @@
|
||||
package app.revanced.extension.youtube.settings;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.util.Pair;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.AppLanguage;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.StringSetting;
|
||||
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
|
||||
|
||||
/**
|
||||
* Controller for managing the search view in ReVanced settings.
|
||||
*/
|
||||
@SuppressWarnings({"deprecated", "DiscouragedApi"})
|
||||
public class SearchViewController {
|
||||
private static final int MAX_HISTORY_SIZE = 5;
|
||||
|
||||
private final SearchView searchView;
|
||||
private final FrameLayout searchContainer;
|
||||
private final Toolbar toolbar;
|
||||
private final Activity activity;
|
||||
private boolean isSearchActive;
|
||||
private final CharSequence originalTitle;
|
||||
private final Deque<String> searchHistory;
|
||||
private final AutoCompleteTextView autoCompleteTextView;
|
||||
private final boolean showSettingsSearchHistory;
|
||||
private int currentOrientation;
|
||||
|
||||
/**
|
||||
* Creates a background drawable for the SearchView with rounded corners.
|
||||
*/
|
||||
private static GradientDrawable createBackgroundDrawable(Context context) {
|
||||
GradientDrawable background = new GradientDrawable();
|
||||
background.setShape(GradientDrawable.RECTANGLE);
|
||||
background.setCornerRadius(28 * context.getResources().getDisplayMetrics().density); // 28dp corner radius.
|
||||
background.setColor(getSearchViewBackground());
|
||||
return background;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a background drawable for suggestion items with rounded corners.
|
||||
*/
|
||||
private static GradientDrawable createSuggestionBackgroundDrawable(Context context) {
|
||||
GradientDrawable background = new GradientDrawable();
|
||||
background.setShape(GradientDrawable.RECTANGLE);
|
||||
background.setColor(getSearchViewBackground());
|
||||
return background;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int getSearchViewBackground() {
|
||||
return Utils.isDarkModeEnabled()
|
||||
? Utils.adjustColorBrightness(Utils.getDialogBackgroundColor(), 1.11f)
|
||||
: Utils.adjustColorBrightness(Utils.getThemeLightColor(), 0.95f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds search view components to the activity.
|
||||
*/
|
||||
public static SearchViewController addSearchViewComponents(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
|
||||
return new SearchViewController(activity, toolbar, fragment);
|
||||
}
|
||||
|
||||
private SearchViewController(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
|
||||
this.activity = activity;
|
||||
this.toolbar = toolbar;
|
||||
this.originalTitle = toolbar.getTitle();
|
||||
this.showSettingsSearchHistory = Settings.SETTINGS_SEARCH_HISTORY.get();
|
||||
this.searchHistory = new LinkedList<>();
|
||||
this.currentOrientation = activity.getResources().getConfiguration().orientation;
|
||||
StringSetting searchEntries = Settings.SETTINGS_SEARCH_ENTRIES;
|
||||
if (showSettingsSearchHistory) {
|
||||
String entries = searchEntries.get();
|
||||
if (!entries.isBlank()) {
|
||||
searchHistory.addAll(Arrays.asList(entries.split("\n")));
|
||||
}
|
||||
} else {
|
||||
// Clear old saved history if the user turns off the feature.
|
||||
searchEntries.resetToDefault();
|
||||
}
|
||||
|
||||
// Retrieve SearchView and container from XML.
|
||||
searchView = activity.findViewById(getResourceIdentifier(
|
||||
"revanced_search_view", "id"));
|
||||
searchContainer = activity.findViewById(getResourceIdentifier(
|
||||
"revanced_search_view_container", "id"));
|
||||
|
||||
// Initialize AutoCompleteTextView.
|
||||
autoCompleteTextView = searchView.findViewById(
|
||||
searchView.getContext().getResources().getIdentifier(
|
||||
"android:id/search_src_text", null, null));
|
||||
|
||||
// Disable fullscreen keyboard mode.
|
||||
autoCompleteTextView.setImeOptions(autoCompleteTextView.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
|
||||
// Set background and query hint.
|
||||
searchView.setBackground(createBackgroundDrawable(toolbar.getContext()));
|
||||
searchView.setQueryHint(str("revanced_settings_search_hint"));
|
||||
|
||||
// Configure RTL support based on app language.
|
||||
AppLanguage appLanguage = BaseSettings.REVANCED_LANGUAGE.get();
|
||||
if (Utils.isRightToLeftLocale(appLanguage.getLocale())) {
|
||||
searchView.setTextDirection(View.TEXT_DIRECTION_RTL);
|
||||
searchView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
|
||||
}
|
||||
|
||||
// Set up search history suggestions.
|
||||
if (showSettingsSearchHistory) {
|
||||
setupSearchHistory();
|
||||
}
|
||||
|
||||
// Set up query text listener.
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
try {
|
||||
String queryTrimmed = query.trim();
|
||||
if (!queryTrimmed.isEmpty()) {
|
||||
saveSearchQuery(queryTrimmed);
|
||||
}
|
||||
// Hide suggestions on submit.
|
||||
if (showSettingsSearchHistory && autoCompleteTextView != null) {
|
||||
autoCompleteTextView.dismissDropDown();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onQueryTextSubmit failure", ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
try {
|
||||
Logger.printDebug(() -> "Search query: " + newText);
|
||||
fragment.filterPreferences(newText);
|
||||
// Prevent suggestions from showing during text input.
|
||||
if (showSettingsSearchHistory && autoCompleteTextView != null) {
|
||||
if (!newText.isEmpty()) {
|
||||
autoCompleteTextView.dismissDropDown();
|
||||
autoCompleteTextView.setThreshold(Integer.MAX_VALUE); // Disable autocomplete suggestions.
|
||||
} else {
|
||||
autoCompleteTextView.setThreshold(1); // Re-enable for empty input.
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onQueryTextChange failure", ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Set menu and search icon.
|
||||
final int actionSearchId = getResourceIdentifier("action_search", "id");
|
||||
toolbar.inflateMenu(getResourceIdentifier("revanced_search_menu", "menu"));
|
||||
MenuItem searchItem = toolbar.getMenu().findItem(actionSearchId);
|
||||
|
||||
// Set menu item click listener.
|
||||
toolbar.setOnMenuItemClickListener(item -> {
|
||||
try {
|
||||
if (item.getItemId() == actionSearchId) {
|
||||
if (!isSearchActive) {
|
||||
openSearch();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "menu click failure", ex);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Set navigation click listener.
|
||||
toolbar.setNavigationOnClickListener(view -> {
|
||||
try {
|
||||
if (isSearchActive) {
|
||||
closeSearch();
|
||||
} else {
|
||||
activity.finish();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "navigation click failure", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the search history suggestions for the SearchView with custom adapter.
|
||||
*/
|
||||
private void setupSearchHistory() {
|
||||
if (autoCompleteTextView != null) {
|
||||
SearchHistoryAdapter adapter = new SearchHistoryAdapter(activity, new ArrayList<>(searchHistory));
|
||||
autoCompleteTextView.setAdapter(adapter);
|
||||
autoCompleteTextView.setThreshold(1); // Initial threshold for empty input.
|
||||
autoCompleteTextView.setLongClickable(true);
|
||||
|
||||
// Show suggestions only when search bar is active and query is empty.
|
||||
autoCompleteTextView.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (hasFocus && isSearchActive && autoCompleteTextView.getText().length() == 0) {
|
||||
autoCompleteTextView.showDropDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a search query to the search history.
|
||||
* @param query The search query to save.
|
||||
*/
|
||||
private void saveSearchQuery(String query) {
|
||||
if (!showSettingsSearchHistory) {
|
||||
return;
|
||||
}
|
||||
searchHistory.remove(query); // Remove if already exists to update position.
|
||||
searchHistory.addFirst(query); // Add to the most recent.
|
||||
|
||||
// Remove extra old entries.
|
||||
while (searchHistory.size() > MAX_HISTORY_SIZE) {
|
||||
String last = searchHistory.removeLast();
|
||||
Logger.printDebug(() -> "Removing search history query: " + last);
|
||||
}
|
||||
|
||||
saveSearchHistory();
|
||||
|
||||
updateSearchHistoryAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a search query from the search history.
|
||||
* @param query The search query to remove.
|
||||
*/
|
||||
private void removeSearchQuery(String query) {
|
||||
searchHistory.remove(query);
|
||||
|
||||
saveSearchHistory();
|
||||
|
||||
updateSearchHistoryAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the search history to the shared preferences.
|
||||
*/
|
||||
private void saveSearchHistory() {
|
||||
Logger.printDebug(() -> "Saving search history: " + searchHistory);
|
||||
|
||||
Settings.SETTINGS_SEARCH_ENTRIES.save(
|
||||
String.join("\n", searchHistory)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the search history adapter with the latest history.
|
||||
*/
|
||||
private void updateSearchHistoryAdapter() {
|
||||
if (autoCompleteTextView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SearchHistoryAdapter adapter = (SearchHistoryAdapter) autoCompleteTextView.getAdapter();
|
||||
if (adapter != null) {
|
||||
adapter.clear();
|
||||
adapter.addAll(searchHistory);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void handleOrientationChange(int newOrientation) {
|
||||
if (newOrientation != currentOrientation) {
|
||||
currentOrientation = newOrientation;
|
||||
if (autoCompleteTextView != null) {
|
||||
autoCompleteTextView.dismissDropDown();
|
||||
Logger.printDebug(() -> "Orientation changed, search history dismissed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the search view and shows the keyboard.
|
||||
*/
|
||||
private void openSearch() {
|
||||
isSearchActive = true;
|
||||
toolbar.getMenu().findItem(getResourceIdentifier(
|
||||
"action_search", "id")).setVisible(false);
|
||||
toolbar.setTitle("");
|
||||
searchContainer.setVisibility(View.VISIBLE);
|
||||
searchView.requestFocus();
|
||||
|
||||
// Show keyboard.
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT);
|
||||
|
||||
// Show suggestions with a slight delay.
|
||||
if (showSettingsSearchHistory && autoCompleteTextView != null && autoCompleteTextView.getText().length() == 0) {
|
||||
searchView.postDelayed(() -> {
|
||||
if (isSearchActive && autoCompleteTextView.getText().length() == 0) {
|
||||
autoCompleteTextView.showDropDown();
|
||||
}
|
||||
}, 100); // 100ms delay to ensure focus is stable.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the search view and hides the keyboard.
|
||||
*/
|
||||
public void closeSearch() {
|
||||
isSearchActive = false;
|
||||
toolbar.getMenu().findItem(getResourceIdentifier(
|
||||
"action_search", "id")).setVisible(true);
|
||||
toolbar.setTitle(originalTitle);
|
||||
searchContainer.setVisibility(View.GONE);
|
||||
searchView.setQuery("", false);
|
||||
|
||||
// Hide keyboard.
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(searchView.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
public static boolean handleBackPress() {
|
||||
if (LicenseActivityHook.searchViewController != null
|
||||
&& LicenseActivityHook.searchViewController.isSearchActive()) {
|
||||
LicenseActivityHook.searchViewController.closeSearch();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSearchActive() {
|
||||
return isSearchActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom ArrayAdapter for search history.
|
||||
*/
|
||||
private class SearchHistoryAdapter extends ArrayAdapter<String> {
|
||||
public SearchHistoryAdapter(Context context, List<String> history) {
|
||||
super(context, 0, history);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView = LinearLayout.inflate(getContext(), getResourceIdentifier(
|
||||
"revanced_search_suggestion_item", "layout"), null);
|
||||
}
|
||||
|
||||
// Apply rounded corners programmatically.
|
||||
convertView.setBackground(createSuggestionBackgroundDrawable(getContext()));
|
||||
String query = getItem(position);
|
||||
|
||||
// Set query text.
|
||||
TextView textView = convertView.findViewById(getResourceIdentifier(
|
||||
"suggestion_text", "id"));
|
||||
if (textView != null) {
|
||||
textView.setText(query);
|
||||
}
|
||||
|
||||
// Set click listener for inserting query into SearchView.
|
||||
convertView.setOnClickListener(v -> {
|
||||
searchView.setQuery(query, true); // Insert selected query and submit.
|
||||
});
|
||||
|
||||
// Set long click listener for deletion confirmation.
|
||||
convertView.setOnLongClickListener(v -> {
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
activity,
|
||||
query, // Title.
|
||||
str("revanced_settings_search_remove_message"), // Message.
|
||||
null, // No EditText.
|
||||
null, // OK button text.
|
||||
() -> 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 convertView;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package app.revanced.extension.youtube.settings;
|
||||
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.shared.settings.Setting.Availability;
|
||||
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
|
||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
import static app.revanced.extension.shared.settings.Setting.parentsAll;
|
||||
@@ -15,10 +14,6 @@ import static app.revanced.extension.youtube.patches.ExitFullscreenPatch.Fullscr
|
||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability;
|
||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;
|
||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MINIMAL;
|
||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_1;
|
||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_2;
|
||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_3;
|
||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_4;
|
||||
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
|
||||
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
|
||||
import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
|
||||
@@ -179,16 +174,15 @@ public class Settings extends BaseSettings {
|
||||
|
||||
// Miniplayer
|
||||
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.DEFAULT, true);
|
||||
private static final Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4);
|
||||
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, MINIPLAYER_ANY_MODERN);
|
||||
public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_drag_and_drop", TRUE, true, MINIPLAYER_ANY_MODERN);
|
||||
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, new MiniplayerPatch.MiniplayerAnyModernAvailability());
|
||||
public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_drag_and_drop", TRUE, true, new MiniplayerPatch.MiniplayerAnyModernAvailability());
|
||||
public static final BooleanSetting MINIPLAYER_HORIZONTAL_DRAG = new BooleanSetting("revanced_miniplayer_horizontal_drag", FALSE, true, new MiniplayerHorizontalDragAvailability());
|
||||
public static final BooleanSetting MINIPLAYER_HIDE_OVERLAY_BUTTONS = new BooleanSetting("revanced_miniplayer_hide_overlay_buttons", FALSE, true, new MiniplayerPatch.MiniplayerHideOverlayButtonsAvailability());
|
||||
public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1, MODERN_3));
|
||||
public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", TRUE, true, MINIPLAYER_TYPE.availability(MODERN_1));
|
||||
public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, MINIPLAYER_ANY_MODERN);
|
||||
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, MINIPLAYER_ANY_MODERN);
|
||||
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, MINIPLAYER_TYPE.availability(MODERN_1));
|
||||
public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, new MiniplayerPatch.MiniplayerHideSubtextsAvailability());
|
||||
public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", TRUE, true, new MiniplayerPatch.MiniplayerHideRewindOrOverlayOpacityAvailability());
|
||||
public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, new MiniplayerPatch.MiniplayerAnyModernAvailability());
|
||||
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, new MiniplayerPatch.MiniplayerAnyModernAvailability());
|
||||
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, new MiniplayerPatch.MiniplayerHideRewindOrOverlayOpacityAvailability());
|
||||
|
||||
// External downloader
|
||||
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
|
||||
@@ -254,8 +248,6 @@ public class Settings extends BaseSettings {
|
||||
|
||||
// General layout
|
||||
public static final BooleanSetting RESTORE_OLD_SETTINGS_MENUS = new BooleanSetting("revanced_restore_old_settings_menus", FALSE, true);
|
||||
public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true);
|
||||
public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", "", true);
|
||||
public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
|
||||
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
|
||||
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
|
||||
@@ -381,9 +373,9 @@ public class Settings extends BaseSettings {
|
||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||
public static final IntegerSetting SWIPE_OVERLAY_OPACITY = new IntegerSetting("revanced_swipe_overlay_background_opacity", 60, true,
|
||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||
public static final StringSetting SWIPE_OVERLAY_BRIGHTNESS_COLOR = new StringSetting("revanced_swipe_overlay_progress_brightness_color", "#FFFFFF", true,
|
||||
public static final StringSetting SWIPE_OVERLAY_BRIGHTNESS_COLOR = new StringSetting("revanced_swipe_overlay_progress_brightness_color", "#BFFFFFFF", true,
|
||||
parent(SWIPE_BRIGHTNESS));
|
||||
public static final StringSetting SWIPE_OVERLAY_VOLUME_COLOR = new StringSetting("revanced_swipe_overlay_progress_volume_color", "#FFFFFF", true,
|
||||
public static final StringSetting SWIPE_OVERLAY_VOLUME_COLOR = new StringSetting("revanced_swipe_overlay_progress_volume_color", "#BFFFFFFF", true,
|
||||
parent(SWIPE_VOLUME));
|
||||
public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true,
|
||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||
@@ -405,7 +397,7 @@ public class Settings extends BaseSettings {
|
||||
// SponsorBlock
|
||||
public static final BooleanSetting SB_ENABLED = new BooleanSetting("sb_enabled", TRUE);
|
||||
/** Do not use id setting directly. Instead use {@link SponsorBlockSettings}. */
|
||||
public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id_Do_Not_Share", "");
|
||||
public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id_Do_Not_Share", "", parent(SB_ENABLED));
|
||||
public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED));
|
||||
public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED));
|
||||
public static final BooleanSetting SB_CREATE_NEW_SEGMENT = new BooleanSetting("sb_create_new_segment", FALSE, parent(SB_ENABLED));
|
||||
@@ -421,46 +413,36 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting SB_TRACK_SKIP_COUNT = new BooleanSetting("sb_track_skip_count", TRUE, parent(SB_ENABLED));
|
||||
public static final FloatSetting SB_SEGMENT_MIN_DURATION = new FloatSetting("sb_min_segment_duration", 0F, parent(SB_ENABLED));
|
||||
public static final BooleanSetting SB_VIDEO_LENGTH_WITHOUT_SEGMENTS = new BooleanSetting("sb_video_length_without_segments", FALSE, parent(SB_ENABLED));
|
||||
public static final StringSetting SB_API_URL = new StringSetting("sb_api_url", "https://sponsor.ajay.app");
|
||||
public static final StringSetting SB_API_URL = new StringSetting("sb_api_url", "https://sponsor.ajay.app", parent(SB_ENABLED));
|
||||
public static final BooleanSetting SB_USER_IS_VIP = new BooleanSetting("sb_user_is_vip", FALSE);
|
||||
public static final IntegerSetting SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS = new IntegerSetting("sb_local_time_saved_number_segments", 0);
|
||||
public static final LongSetting SB_LOCAL_TIME_SAVED_MILLISECONDS = new LongSetting("sb_local_time_saved_milliseconds", 0L);
|
||||
public static final IntegerSetting SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS = new IntegerSetting("sb_local_time_saved_number_segments", 0, parent(SB_ENABLED));
|
||||
public static final LongSetting SB_LOCAL_TIME_SAVED_MILLISECONDS = new LongSetting("sb_local_time_saved_milliseconds", 0L, parent(SB_ENABLED));
|
||||
public static final LongSetting SB_LAST_VIP_CHECK = new LongSetting("sb_last_vip_check", 0L, false, false);
|
||||
public static final BooleanSetting SB_HIDE_EXPORT_WARNING = new BooleanSetting("sb_hide_export_warning", FALSE, false, false);
|
||||
public static final BooleanSetting SB_SEEN_GUIDELINES = new BooleanSetting("sb_seen_guidelines", FALSE, false, false);
|
||||
public static final StringSetting SB_CATEGORY_SPONSOR = new StringSetting("sb_sponsor", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_SPONSOR_COLOR = new StringSetting("sb_sponsor_color", "#00D400");
|
||||
public static final FloatSetting SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_SELF_PROMO = new StringSetting("sb_selfpromo", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_SELF_PROMO_COLOR = new StringSetting("sb_selfpromo_color", "#FFFF00");
|
||||
public static final FloatSetting SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_INTERACTION = new StringSetting("sb_interaction", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_INTERACTION_COLOR = new StringSetting("sb_interaction_color", "#CC00FF");
|
||||
public static final FloatSetting SB_CATEGORY_INTERACTION_OPACITY = new FloatSetting("sb_interaction_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_HIGHLIGHT = new StringSetting("sb_highlight", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_HIGHLIGHT_COLOR = new StringSetting("sb_highlight_color", "#FF1684");
|
||||
public static final FloatSetting SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_HOOK = new StringSetting("sb_hook", IGNORE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_HOOK_COLOR = new StringSetting("sb_hook_color", "#395699");
|
||||
public static final FloatSetting SB_CATEGORY_HOOK_OPACITY = new FloatSetting("sb_hook_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#00FFFF");
|
||||
public static final FloatSetting SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_OUTRO = new StringSetting("sb_outro", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_OUTRO_COLOR = new StringSetting("sb_outro_color", "#0202ED");
|
||||
public static final FloatSetting SB_CATEGORY_OUTRO_OPACITY = new FloatSetting("sb_outro_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_PREVIEW = new StringSetting("sb_preview", IGNORE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_PREVIEW_COLOR = new StringSetting("sb_preview_color", "#008FD6");
|
||||
public static final FloatSetting SB_CATEGORY_PREVIEW_OPACITY = new FloatSetting("sb_preview_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_FILLER = new StringSetting("sb_filler", IGNORE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_FILLER_COLOR = new StringSetting("sb_filler_color", "#7300FF");
|
||||
public static final FloatSetting SB_CATEGORY_FILLER_OPACITY = new FloatSetting("sb_filler_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC = new StringSetting("sb_music_offtopic", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC_COLOR = new StringSetting("sb_music_offtopic_color", "#FF9900");
|
||||
public static final FloatSetting SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY = new FloatSetting("sb_music_offtopic_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_UNSUBMITTED = new StringSetting("sb_unsubmitted", SKIP_AUTOMATICALLY.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFF");
|
||||
public static final FloatSetting SB_CATEGORY_UNSUBMITTED_OPACITY = new FloatSetting("sb_unsubmitted_opacity", 1.0f);
|
||||
public static final StringSetting SB_CATEGORY_SPONSOR = new StringSetting("sb_sponsor", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue, parent(SB_ENABLED));
|
||||
public static final StringSetting SB_CATEGORY_SPONSOR_COLOR = new StringSetting("sb_sponsor_color", "#CC00D400");
|
||||
public static final StringSetting SB_CATEGORY_SELF_PROMO = new StringSetting("sb_selfpromo", MANUAL_SKIP.reVancedKeyValue, parent(SB_ENABLED));
|
||||
public static final StringSetting SB_CATEGORY_SELF_PROMO_COLOR = new StringSetting("sb_selfpromo_color", "#CCFFFF00");
|
||||
public static final StringSetting SB_CATEGORY_INTERACTION = new StringSetting("sb_interaction", MANUAL_SKIP.reVancedKeyValue, parent(SB_ENABLED));
|
||||
public static final StringSetting SB_CATEGORY_INTERACTION_COLOR = new StringSetting("sb_interaction_color", "#CCCC00FF");
|
||||
public static final StringSetting SB_CATEGORY_HIGHLIGHT = new StringSetting("sb_highlight", MANUAL_SKIP.reVancedKeyValue, parent(SB_ENABLED));
|
||||
public static final StringSetting SB_CATEGORY_HIGHLIGHT_COLOR = new StringSetting("sb_highlight_color", "#CCFF1684");
|
||||
public static final StringSetting SB_CATEGORY_HOOK = new StringSetting("sb_hook", IGNORE.reVancedKeyValue, parent(SB_ENABLED));
|
||||
public static final StringSetting SB_CATEGORY_HOOK_COLOR = new StringSetting("sb_hook_color", "#CC395699");
|
||||
public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", MANUAL_SKIP.reVancedKeyValue, parent(SB_ENABLED));
|
||||
public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#CC00FFFF");
|
||||
public static final StringSetting SB_CATEGORY_OUTRO = new StringSetting("sb_outro", MANUAL_SKIP.reVancedKeyValue, parent(SB_ENABLED));
|
||||
public static final StringSetting SB_CATEGORY_OUTRO_COLOR = new StringSetting("sb_outro_color", "#CC0202ED");
|
||||
public static final StringSetting SB_CATEGORY_PREVIEW = new StringSetting("sb_preview", IGNORE.reVancedKeyValue, parent(SB_ENABLED));
|
||||
public static final StringSetting SB_CATEGORY_PREVIEW_COLOR = new StringSetting("sb_preview_color", "#CC008FD6");
|
||||
public static final StringSetting SB_CATEGORY_FILLER = new StringSetting("sb_filler", IGNORE.reVancedKeyValue, parent(SB_ENABLED));
|
||||
public static final StringSetting SB_CATEGORY_FILLER_COLOR = new StringSetting("sb_filler_color", "#CC7300FF");
|
||||
public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC = new StringSetting("sb_music_offtopic", MANUAL_SKIP.reVancedKeyValue, parent(SB_ENABLED));
|
||||
public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC_COLOR = new StringSetting("sb_music_offtopic_color", "#CCFF9900");
|
||||
// Dummy setting. Category is not exposed in the UI nor does it ever change.
|
||||
public static final StringSetting SB_CATEGORY_UNSUBMITTED = new StringSetting("sb_unsubmitted", SKIP_AUTOMATICALLY.reVancedKeyValue, false, false);
|
||||
public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFFFF", false, false);
|
||||
|
||||
// Deprecated migrations
|
||||
private static final BooleanSetting DEPRECATED_HIDE_PLAYER_BUTTONS = new BooleanSetting("revanced_hide_player_buttons", FALSE, true);
|
||||
@@ -471,6 +453,17 @@ public class Settings extends BaseSettings {
|
||||
private static final BooleanSetting DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE);
|
||||
private static final BooleanSetting DEPRECATED_AUTO_CAPTIONS = new BooleanSetting("revanced_auto_captions", FALSE);
|
||||
|
||||
public static final FloatSetting DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f, false, false);
|
||||
public static final FloatSetting DEPRECATED_SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f, false, false);
|
||||
public static final FloatSetting DEPRECATED_SB_CATEGORY_INTERACTION_OPACITY = new FloatSetting("sb_interaction_opacity", 0.8f, false, false);
|
||||
public static final FloatSetting DEPRECATED_SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f, false, false);
|
||||
public static final FloatSetting DEPRECATED_SB_CATEGORY_HOOK_OPACITY = new FloatSetting("sb_hook_opacity", 0.8f, false, false);
|
||||
public static final FloatSetting DEPRECATED_SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f, false, false);
|
||||
public static final FloatSetting DEPRECATED_SB_CATEGORY_OUTRO_OPACITY = new FloatSetting("sb_outro_opacity", 0.8f, false, false);
|
||||
public static final FloatSetting DEPRECATED_SB_CATEGORY_PREVIEW_OPACITY = new FloatSetting("sb_preview_opacity", 0.8f, false, false);
|
||||
public static final FloatSetting DEPRECATED_SB_CATEGORY_FILLER_OPACITY = new FloatSetting("sb_filler_opacity", 0.8f, false, false);
|
||||
public static final FloatSetting DEPRECATED_SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY = new FloatSetting("sb_music_offtopic_opacity", 0.8f, false, false);
|
||||
|
||||
static {
|
||||
// region Migration
|
||||
|
||||
@@ -535,6 +528,18 @@ public class Settings extends BaseSettings {
|
||||
Setting.migrateFromOldPreferences(revancedPrefs, RYD_ESTIMATED_LIKE, "ryd_estimated_like");
|
||||
Setting.migrateFromOldPreferences(revancedPrefs, RYD_TOAST_ON_CONNECTION_ERROR, "ryd_toast_on_connection_error");
|
||||
|
||||
// Migrate old saved data. Must be done here before the settings can be used by any other code.
|
||||
applyOldSbOpacityToColor(SB_CATEGORY_SPONSOR_COLOR, DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY);
|
||||
applyOldSbOpacityToColor(SB_CATEGORY_SELF_PROMO_COLOR, DEPRECATED_SB_CATEGORY_SELF_PROMO_OPACITY);
|
||||
applyOldSbOpacityToColor(SB_CATEGORY_INTERACTION_COLOR, DEPRECATED_SB_CATEGORY_INTERACTION_OPACITY);
|
||||
applyOldSbOpacityToColor(SB_CATEGORY_HIGHLIGHT_COLOR, DEPRECATED_SB_CATEGORY_HIGHLIGHT_OPACITY);
|
||||
applyOldSbOpacityToColor(SB_CATEGORY_HOOK_COLOR, DEPRECATED_SB_CATEGORY_HOOK_OPACITY);
|
||||
applyOldSbOpacityToColor(SB_CATEGORY_INTRO_COLOR, DEPRECATED_SB_CATEGORY_INTRO_OPACITY);
|
||||
applyOldSbOpacityToColor(SB_CATEGORY_OUTRO_COLOR, DEPRECATED_SB_CATEGORY_OUTRO_OPACITY);
|
||||
applyOldSbOpacityToColor(SB_CATEGORY_PREVIEW_COLOR, DEPRECATED_SB_CATEGORY_PREVIEW_OPACITY);
|
||||
applyOldSbOpacityToColor(SB_CATEGORY_FILLER_COLOR, DEPRECATED_SB_CATEGORY_FILLER_OPACITY);
|
||||
applyOldSbOpacityToColor(SB_CATEGORY_MUSIC_OFFTOPIC_COLOR, DEPRECATED_SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY);
|
||||
|
||||
// endregion
|
||||
|
||||
// region SB import/export callbacks
|
||||
@@ -543,4 +548,13 @@ public class Settings extends BaseSettings {
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
private static void applyOldSbOpacityToColor(StringSetting colorSetting, FloatSetting opacitySetting) {
|
||||
String colorString = colorSetting.get();
|
||||
if (colorString.length() >= 8) {
|
||||
return; // Color is already #ARGB
|
||||
}
|
||||
|
||||
colorSetting.save(SponsorBlockSettings.migrateOldColorString(colorString, opacitySetting.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,26 +2,23 @@ package app.revanced.extension.youtube.settings;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.view.View;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.AppLanguage;
|
||||
import app.revanced.extension.shared.settings.BaseActivityHook;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.youtube.patches.VersionCheckPatch;
|
||||
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
|
||||
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
|
||||
import app.revanced.extension.youtube.settings.preference.YouTubePreferenceFragment;
|
||||
import app.revanced.extension.youtube.settings.search.YouTubeSearchViewController;
|
||||
|
||||
/**
|
||||
* Hooks LicenseActivity to inject a custom ReVancedPreferenceFragment with a toolbar and search functionality.
|
||||
* Hooks LicenseActivity to inject a custom {@link YouTubePreferenceFragment} with a toolbar and search functionality.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class LicenseActivityHook extends BaseActivityHook {
|
||||
public class YouTubeActivityHook extends BaseActivityHook {
|
||||
|
||||
private static int currentThemeValueOrdinal = -1; // Must initially be a non-valid enum ordinal value.
|
||||
|
||||
@@ -29,16 +26,14 @@ public class LicenseActivityHook extends BaseActivityHook {
|
||||
* Controller for managing search view components in the toolbar.
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public static SearchViewController searchViewController;
|
||||
public static YouTubeSearchViewController searchViewController;
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
* <p>
|
||||
* Creates an instance of LicenseActivityHook for use in static initialization.
|
||||
* Injection point.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static LicenseActivityHook createInstance() {
|
||||
return new LicenseActivityHook();
|
||||
public static void initialize(Activity parentActivity) {
|
||||
BaseActivityHook.initialize(new YouTubeActivityHook(), parentActivity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +44,7 @@ public class LicenseActivityHook extends BaseActivityHook {
|
||||
final var theme = Utils.isDarkModeEnabled()
|
||||
? "Theme.YouTube.Settings.Dark"
|
||||
: "Theme.YouTube.Settings";
|
||||
activity.setTheme(Utils.getResourceIdentifier(theme, "style"));
|
||||
activity.setTheme(Utils.getResourceIdentifierOrThrow(theme, "style"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +52,7 @@ public class LicenseActivityHook extends BaseActivityHook {
|
||||
*/
|
||||
@Override
|
||||
protected int getContentViewResourceId() {
|
||||
return Utils.getResourceIdentifier("revanced_settings_with_toolbar", "layout");
|
||||
return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +71,7 @@ public class LicenseActivityHook extends BaseActivityHook {
|
||||
*/
|
||||
@Override
|
||||
protected Drawable getNavigationIcon() {
|
||||
return ReVancedPreferenceFragment.getBackButtonDrawable();
|
||||
return YouTubePreferenceFragment.getBackButtonDrawable();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,7 +83,7 @@ public class LicenseActivityHook extends BaseActivityHook {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds search view components to the toolbar for ReVancedPreferenceFragment.
|
||||
* Adds search view components to the toolbar for {@link YouTubePreferenceFragment}.
|
||||
*
|
||||
* @param activity The activity hosting the toolbar.
|
||||
* @param toolbar The configured toolbar.
|
||||
@@ -96,32 +91,18 @@ public class LicenseActivityHook extends BaseActivityHook {
|
||||
*/
|
||||
@Override
|
||||
protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) {
|
||||
if (fragment instanceof ReVancedPreferenceFragment) {
|
||||
searchViewController = SearchViewController.addSearchViewComponents(
|
||||
activity, toolbar, (ReVancedPreferenceFragment) fragment);
|
||||
if (fragment instanceof YouTubePreferenceFragment) {
|
||||
searchViewController = YouTubeSearchViewController.addSearchViewComponents(
|
||||
activity, toolbar, (YouTubePreferenceFragment) fragment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ReVancedPreferenceFragment for the activity.
|
||||
* Creates a new {@link YouTubePreferenceFragment} for the activity.
|
||||
*/
|
||||
@Override
|
||||
protected PreferenceFragment createPreferenceFragment() {
|
||||
return new ReVancedPreferenceFragment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Overrides the ReVanced settings language.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static Context getAttachBaseContext(Context original) {
|
||||
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
|
||||
if (language == AppLanguage.DEFAULT) {
|
||||
return original;
|
||||
}
|
||||
|
||||
return Utils.getContext();
|
||||
return new YouTubePreferenceFragment();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,12 +145,14 @@ public class LicenseActivityHook extends BaseActivityHook {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles configuration changes, such as orientation, to update the search view.
|
||||
* Injection point.
|
||||
* <p>
|
||||
* Overrides {@link Activity#finish()} of the injection Activity.
|
||||
*
|
||||
* @return if the original activity finish method should be allowed to run.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void handleConfigurationChanged(Activity activity, Configuration newConfig) {
|
||||
if (searchViewController != null) {
|
||||
searchViewController.handleOrientationChange(newConfig.orientation);
|
||||
}
|
||||
public static boolean handleFinish() {
|
||||
return YouTubeSearchViewController.handleFinish(searchViewController);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package app.revanced.extension.youtube.settings.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import app.revanced.extension.shared.settings.preference.UrlLinkPreference;
|
||||
|
||||
/**
|
||||
* Allows tapping the DeArrow about preference to open the DeArrow website.
|
||||
|
||||
@@ -36,6 +36,7 @@ import java.util.function.Function;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||
import app.revanced.extension.shared.ui.CustomDialog;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
/**
|
||||
@@ -210,7 +211,7 @@ public class ExternalDownloaderPreference extends CustomDialogListPreference {
|
||||
final boolean usingCustomDownloader = Downloader.findByPackageName(packageName) == null;
|
||||
adapter = new CustomDialogListPreference.ListPreferenceArrayAdapter(
|
||||
context,
|
||||
Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"),
|
||||
LAYOUT_REVANCED_CUSTOM_LIST_ITEM_CHECKED,
|
||||
getEntries(),
|
||||
getEntryValues(),
|
||||
usingCustomDownloader
|
||||
@@ -302,7 +303,7 @@ public class ExternalDownloaderPreference extends CustomDialogListPreference {
|
||||
contentLayout.addView(editText);
|
||||
|
||||
// Create the custom dialog.
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||
context,
|
||||
getTitle() != null ? getTitle().toString() : "",
|
||||
null,
|
||||
@@ -312,7 +313,7 @@ public class ExternalDownloaderPreference extends CustomDialogListPreference {
|
||||
String newValue = editText.getText().toString().trim();
|
||||
if (newValue.isEmpty()) {
|
||||
// Show dialog if EditText is empty.
|
||||
Utils.createCustomDialog(
|
||||
CustomDialog.create(
|
||||
context,
|
||||
str("revanced_external_downloader_name_title"),
|
||||
str("revanced_external_downloader_empty_warning"),
|
||||
@@ -415,7 +416,7 @@ public class ExternalDownloaderPreference extends CustomDialogListPreference {
|
||||
? str("revanced_external_downloader_not_installed_warning", downloader.name)
|
||||
: str("revanced_external_downloader_package_not_found_warning", packageName);
|
||||
|
||||
Utils.createCustomDialog(
|
||||
CustomDialog.create(
|
||||
context,
|
||||
str("revanced_external_downloader_not_found_title"),
|
||||
message,
|
||||
|
||||
@@ -1,483 +0,0 @@
|
||||
package app.revanced.extension.youtube.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory;
|
||||
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
|
||||
import app.revanced.extension.youtube.settings.LicenseActivityHook;
|
||||
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
|
||||
|
||||
/**
|
||||
* Preference fragment for ReVanced settings.
|
||||
*/
|
||||
@SuppressWarnings({"deprecation", "NewApi"})
|
||||
public class ReVancedPreferenceFragment extends ToolbarPreferenceFragment {
|
||||
|
||||
/**
|
||||
* The main PreferenceScreen used to display the current set of preferences.
|
||||
* This screen is manipulated during initialization and filtering to show or hide preferences.
|
||||
*/
|
||||
private PreferenceScreen preferenceScreen;
|
||||
|
||||
/**
|
||||
* A copy of the original PreferenceScreen created during initialization.
|
||||
* Used to restore the preference structure to its initial state after filtering or other modifications.
|
||||
*/
|
||||
private PreferenceScreen originalPreferenceScreen;
|
||||
|
||||
/**
|
||||
* Used for searching preferences. A Collection of all preferences including nested preferences.
|
||||
* Root preferences are excluded (no need to search what's on the root screen),
|
||||
* but their sub preferences are included.
|
||||
*/
|
||||
private final List<AbstractPreferenceSearchData<?>> allPreferences = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Initializes the preference fragment, copying the original screen to allow full restoration.
|
||||
*/
|
||||
@Override
|
||||
protected void initialize() {
|
||||
super.initialize();
|
||||
|
||||
try {
|
||||
preferenceScreen = getPreferenceScreen();
|
||||
Utils.sortPreferenceGroups(preferenceScreen);
|
||||
|
||||
// Store the original structure for restoration after filtering.
|
||||
originalPreferenceScreen = getPreferenceManager().createPreferenceScreen(getContext());
|
||||
for (int i = 0, count = preferenceScreen.getPreferenceCount(); i < count; i++) {
|
||||
originalPreferenceScreen.addPreference(preferenceScreen.getPreference(i));
|
||||
}
|
||||
|
||||
setPreferenceScreenToolbar(preferenceScreen);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "initialize failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the fragment starts, ensuring all preferences are collected after initialization.
|
||||
*/
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
try {
|
||||
if (allPreferences.isEmpty()) {
|
||||
// Must collect preferences on start and not in initialize since
|
||||
// legacy SB settings are not loaded yet.
|
||||
Logger.printDebug(() -> "Collecting preferences to search");
|
||||
|
||||
// Do not show root menu preferences in search results.
|
||||
// Instead search for everything that's not shown when search is not active.
|
||||
collectPreferences(preferenceScreen, 1, 0);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onStart failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets toolbar for all nested preference screens.
|
||||
*/
|
||||
@Override
|
||||
protected void customizeToolbar(Toolbar toolbar) {
|
||||
LicenseActivityHook.setToolbarLayoutParams(toolbar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform actions after toolbar setup.
|
||||
*/
|
||||
@Override
|
||||
protected void onPostToolbarSetup(Toolbar toolbar, Dialog preferenceScreenDialog) {
|
||||
if (LicenseActivityHook.searchViewController != null
|
||||
&& LicenseActivityHook.searchViewController.isSearchActive()) {
|
||||
toolbar.post(() -> LicenseActivityHook.searchViewController.closeSearch());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively collects all preferences from the screen or group.
|
||||
*
|
||||
* @param includeDepth Menu depth to start including preferences.
|
||||
* A value of 0 adds all preferences.
|
||||
*/
|
||||
private void collectPreferences(PreferenceGroup group, int includeDepth, int currentDepth) {
|
||||
for (int i = 0, count = group.getPreferenceCount(); i < count; i++) {
|
||||
Preference preference = group.getPreference(i);
|
||||
if (includeDepth <= currentDepth && !(preference instanceof PreferenceCategory)
|
||||
&& !(preference instanceof SponsorBlockPreferenceGroup)) {
|
||||
|
||||
AbstractPreferenceSearchData<?> data;
|
||||
if (preference instanceof SwitchPreference switchPref) {
|
||||
data = new SwitchPreferenceSearchData(switchPref);
|
||||
} else if (preference instanceof ListPreference listPref) {
|
||||
data = new ListPreferenceSearchData(listPref);
|
||||
} else {
|
||||
data = new PreferenceSearchData(preference);
|
||||
}
|
||||
|
||||
allPreferences.add(data);
|
||||
}
|
||||
|
||||
if (preference instanceof PreferenceGroup subGroup) {
|
||||
collectPreferences(subGroup, includeDepth, currentDepth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the preferences using the given query string and applies highlighting.
|
||||
*/
|
||||
public void filterPreferences(String query) {
|
||||
preferenceScreen.removeAll();
|
||||
|
||||
if (TextUtils.isEmpty(query)) {
|
||||
// Restore original preferences and their titles/summaries/entries.
|
||||
for (int i = 0, count = originalPreferenceScreen.getPreferenceCount(); i < count; i++) {
|
||||
preferenceScreen.addPreference(originalPreferenceScreen.getPreference(i));
|
||||
}
|
||||
|
||||
for (AbstractPreferenceSearchData<?> data : allPreferences) {
|
||||
data.clearHighlighting();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigation path -> Category
|
||||
Map<String, PreferenceCategory> categoryMap = new HashMap<>();
|
||||
String queryLower = Utils.removePunctuationToLowercase(query);
|
||||
|
||||
Pattern queryPattern = Pattern.compile(Pattern.quote(Utils.removePunctuationToLowercase(query)),
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
for (AbstractPreferenceSearchData<?> data : allPreferences) {
|
||||
if (data.matchesSearchQuery(queryLower)) {
|
||||
data.applyHighlighting(queryLower, queryPattern);
|
||||
|
||||
String navigationPath = data.navigationPath;
|
||||
PreferenceCategory group = categoryMap.computeIfAbsent(navigationPath, key -> {
|
||||
PreferenceCategory newGroup = new PreferenceCategory(preferenceScreen.getContext());
|
||||
newGroup.setTitle(navigationPath);
|
||||
preferenceScreen.addPreference(newGroup);
|
||||
return newGroup;
|
||||
});
|
||||
group.addPreference(data.preference);
|
||||
}
|
||||
}
|
||||
|
||||
// Show 'No results found' if search results are empty.
|
||||
if (categoryMap.isEmpty()) {
|
||||
Preference noResultsPreference = new Preference(preferenceScreen.getContext());
|
||||
noResultsPreference.setTitle(str("revanced_settings_search_no_results_title", query));
|
||||
noResultsPreference.setSummary(str("revanced_settings_search_no_results_summary"));
|
||||
noResultsPreference.setSelectable(false);
|
||||
// Set icon for the placeholder preference.
|
||||
noResultsPreference.setLayoutResource(getResourceIdentifier(
|
||||
"revanced_preference_with_icon_no_search_result", "layout"));
|
||||
noResultsPreference.setIcon(getResourceIdentifier("revanced_settings_search_icon", "drawable"));
|
||||
preferenceScreen.addPreference(noResultsPreference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
class AbstractPreferenceSearchData<T extends Preference> {
|
||||
/**
|
||||
* @return The navigation path for the given preference, such as "Player > Action buttons".
|
||||
*/
|
||||
private static String getPreferenceNavigationString(Preference preference) {
|
||||
Deque<CharSequence> pathElements = new ArrayDeque<>();
|
||||
|
||||
while (true) {
|
||||
preference = preference.getParent();
|
||||
|
||||
if (preference == null) {
|
||||
if (pathElements.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
Locale locale = BaseSettings.REVANCED_LANGUAGE.get().getLocale();
|
||||
return Utils.getTextDirectionString(locale) + String.join(" > ", pathElements);
|
||||
}
|
||||
|
||||
if (!(preference instanceof NoTitlePreferenceCategory)
|
||||
&& !(preference instanceof SponsorBlockPreferenceGroup)) {
|
||||
CharSequence title = preference.getTitle();
|
||||
if (title != null && title.length() > 0) {
|
||||
pathElements.addFirst(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights the search query in the given text by applying color span.
|
||||
* @param text The original text to process.
|
||||
* @param queryPattern The search query to highlight.
|
||||
* @return The text with highlighted query matches as a SpannableStringBuilder.
|
||||
*/
|
||||
static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern) {
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return text;
|
||||
}
|
||||
|
||||
final int adjustedColor = Utils.adjustColorBrightness(Utils.getAppBackgroundColor(),
|
||||
0.95f, 1.20f);
|
||||
BackgroundColorSpan highlightSpan = new BackgroundColorSpan(adjustedColor);
|
||||
|
||||
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
|
||||
Matcher matcher = queryPattern.matcher(text);
|
||||
|
||||
while (matcher.find()) {
|
||||
spannable.setSpan(
|
||||
highlightSpan,
|
||||
matcher.start(),
|
||||
matcher.end(),
|
||||
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
);
|
||||
}
|
||||
|
||||
return spannable;
|
||||
}
|
||||
|
||||
final T preference;
|
||||
final String key;
|
||||
final String navigationPath;
|
||||
boolean highlightingApplied;
|
||||
|
||||
@Nullable
|
||||
CharSequence originalTitle;
|
||||
@Nullable
|
||||
String searchTitle;
|
||||
|
||||
AbstractPreferenceSearchData(T pref) {
|
||||
preference = pref;
|
||||
key = Utils.removePunctuationToLowercase(pref.getKey());
|
||||
navigationPath = getPreferenceNavigationString(pref);
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
void updateSearchDataIfNeeded() {
|
||||
if (highlightingApplied) {
|
||||
// Must clear, otherwise old highlighting is still applied.
|
||||
clearHighlighting();
|
||||
}
|
||||
|
||||
CharSequence title = preference.getTitle();
|
||||
if (originalTitle != title) { // Check using reference equality.
|
||||
originalTitle = title;
|
||||
searchTitle = Utils.removePunctuationToLowercase(title);
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
boolean matchesSearchQuery(String query) {
|
||||
updateSearchDataIfNeeded();
|
||||
|
||||
return key.contains(query)
|
||||
|| searchTitle != null && searchTitle.contains(query);
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
void applyHighlighting(String query, Pattern queryPattern) {
|
||||
preference.setTitle(highlightSearchQuery(originalTitle, queryPattern));
|
||||
highlightingApplied = true;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
void clearHighlighting() {
|
||||
if (highlightingApplied) {
|
||||
preference.setTitle(originalTitle);
|
||||
highlightingApplied = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular preference type that only uses the base preference summary.
|
||||
* Should only be used if a more specific data class does not exist.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
class PreferenceSearchData extends AbstractPreferenceSearchData<Preference> {
|
||||
@Nullable
|
||||
CharSequence originalSummary;
|
||||
@Nullable
|
||||
String searchSummary;
|
||||
|
||||
PreferenceSearchData(Preference pref) {
|
||||
super(pref);
|
||||
}
|
||||
|
||||
void updateSearchDataIfNeeded() {
|
||||
super.updateSearchDataIfNeeded();
|
||||
|
||||
CharSequence summary = preference.getSummary();
|
||||
if (originalSummary != summary) {
|
||||
originalSummary = summary;
|
||||
searchSummary = Utils.removePunctuationToLowercase(summary);
|
||||
}
|
||||
}
|
||||
|
||||
boolean matchesSearchQuery(String query) {
|
||||
return super.matchesSearchQuery(query)
|
||||
|| searchSummary != null && searchSummary.contains(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
void applyHighlighting(String query, Pattern queryPattern) {
|
||||
super.applyHighlighting(query, queryPattern);
|
||||
|
||||
preference.setSummary(highlightSearchQuery(originalSummary, queryPattern));
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
void clearHighlighting() {
|
||||
if (highlightingApplied) {
|
||||
preference.setSummary(originalSummary);
|
||||
}
|
||||
|
||||
super.clearHighlighting();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch preference type that uses summaryOn and summaryOff.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
class SwitchPreferenceSearchData extends AbstractPreferenceSearchData<SwitchPreference> {
|
||||
@Nullable
|
||||
CharSequence originalSummaryOn, originalSummaryOff;
|
||||
@Nullable
|
||||
String searchSummaryOn, searchSummaryOff;
|
||||
|
||||
SwitchPreferenceSearchData(SwitchPreference pref) {
|
||||
super(pref);
|
||||
}
|
||||
|
||||
void updateSearchDataIfNeeded() {
|
||||
super.updateSearchDataIfNeeded();
|
||||
|
||||
CharSequence summaryOn = preference.getSummaryOn();
|
||||
if (originalSummaryOn != summaryOn) {
|
||||
originalSummaryOn = summaryOn;
|
||||
searchSummaryOn = Utils.removePunctuationToLowercase(summaryOn);
|
||||
}
|
||||
|
||||
CharSequence summaryOff = preference.getSummaryOff();
|
||||
if (originalSummaryOff != summaryOff) {
|
||||
originalSummaryOff = summaryOff;
|
||||
searchSummaryOff = Utils.removePunctuationToLowercase(summaryOff);
|
||||
}
|
||||
}
|
||||
|
||||
boolean matchesSearchQuery(String query) {
|
||||
return super.matchesSearchQuery(query)
|
||||
|| searchSummaryOn != null && searchSummaryOn.contains(query)
|
||||
|| searchSummaryOff != null && searchSummaryOff.contains(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
void applyHighlighting(String query, Pattern queryPattern) {
|
||||
super.applyHighlighting(query, queryPattern);
|
||||
|
||||
preference.setSummaryOn(highlightSearchQuery(originalSummaryOn, queryPattern));
|
||||
preference.setSummaryOff(highlightSearchQuery(originalSummaryOff, queryPattern));
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
void clearHighlighting() {
|
||||
if (highlightingApplied) {
|
||||
preference.setSummaryOn(originalSummaryOn);
|
||||
preference.setSummaryOff(originalSummaryOff);
|
||||
}
|
||||
|
||||
super.clearHighlighting();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List preference type that uses entries.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
class ListPreferenceSearchData extends AbstractPreferenceSearchData<ListPreference> {
|
||||
@Nullable
|
||||
CharSequence[] originalEntries;
|
||||
@Nullable
|
||||
String searchEntries;
|
||||
|
||||
ListPreferenceSearchData(ListPreference pref) {
|
||||
super(pref);
|
||||
}
|
||||
|
||||
void updateSearchDataIfNeeded() {
|
||||
super.updateSearchDataIfNeeded();
|
||||
|
||||
CharSequence[] entries = preference.getEntries();
|
||||
if (originalEntries != entries) {
|
||||
originalEntries = entries;
|
||||
searchEntries = Utils.removePunctuationToLowercase(String.join(" ", entries));
|
||||
}
|
||||
}
|
||||
|
||||
boolean matchesSearchQuery(String query) {
|
||||
return super.matchesSearchQuery(query)
|
||||
|| searchEntries != null && searchEntries.contains(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
void applyHighlighting(String query, Pattern queryPattern) {
|
||||
super.applyHighlighting(query, queryPattern);
|
||||
|
||||
if (originalEntries != null) {
|
||||
final int length = originalEntries.length;
|
||||
CharSequence[] highlightedEntries = new CharSequence[length];
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
highlightedEntries[i] = highlightSearchQuery(originalEntries[i], queryPattern);
|
||||
|
||||
// Cannot highlight the summary text, because ListPreference uses
|
||||
// the toString() of the summary CharSequence which strips away all formatting.
|
||||
}
|
||||
|
||||
preference.setEntries(highlightedEntries);
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
void clearHighlighting() {
|
||||
if (highlightingApplied) {
|
||||
preference.setEntries(originalEntries);
|
||||
}
|
||||
|
||||
super.clearHighlighting();
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.settings.preference.BulletPointPreference;
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@@ -99,6 +100,7 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_kids_videos");
|
||||
}
|
||||
|
||||
setSummary(summary);
|
||||
// Use better formatting for bullet points.
|
||||
setSummary(BulletPointPreference.formatIntoBulletPoints(summary));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package app.revanced.extension.youtube.settings.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
|
||||
/**
|
||||
* Simple preference that opens a url when clicked.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class UrlLinkPreference extends Preference {
|
||||
|
||||
protected String externalUrl;
|
||||
|
||||
{
|
||||
setOnPreferenceClickListener(pref -> {
|
||||
if (externalUrl == null) {
|
||||
Logger.printException(() -> "URL not set " + getClass().getSimpleName());
|
||||
return false;
|
||||
}
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(externalUrl));
|
||||
pref.getContext().startActivity(i);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public UrlLinkPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
public UrlLinkPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
public UrlLinkPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public UrlLinkPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package app.revanced.extension.youtube.settings.preference;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
|
||||
import app.revanced.extension.youtube.settings.YouTubeActivityHook;
|
||||
|
||||
/**
|
||||
* Preference fragment for ReVanced settings.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class YouTubePreferenceFragment extends ToolbarPreferenceFragment {
|
||||
/**
|
||||
* The main PreferenceScreen used to display the current set of preferences.
|
||||
*/
|
||||
private PreferenceScreen preferenceScreen;
|
||||
|
||||
/**
|
||||
* Initializes the preference fragment.
|
||||
*/
|
||||
@Override
|
||||
protected void initialize() {
|
||||
super.initialize();
|
||||
|
||||
try {
|
||||
preferenceScreen = getPreferenceScreen();
|
||||
Utils.sortPreferenceGroups(preferenceScreen);
|
||||
setPreferenceScreenToolbar(preferenceScreen);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "initialize failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the fragment starts.
|
||||
*/
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
try {
|
||||
// Initialize search controller if needed.
|
||||
if (YouTubeActivityHook.searchViewController != null) {
|
||||
// Trigger search data collection after fragment is ready.
|
||||
YouTubeActivityHook.searchViewController.initializeSearchData();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onStart failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets toolbar for all nested preference screens.
|
||||
*/
|
||||
@Override
|
||||
protected void customizeToolbar(Toolbar toolbar) {
|
||||
YouTubeActivityHook.setToolbarLayoutParams(toolbar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform actions after toolbar setup.
|
||||
*/
|
||||
@Override
|
||||
protected void onPostToolbarSetup(Toolbar toolbar, Dialog preferenceScreenDialog) {
|
||||
if (YouTubeActivityHook.searchViewController != null
|
||||
&& YouTubeActivityHook.searchViewController.isSearchActive()) {
|
||||
toolbar.post(() -> YouTubeActivityHook.searchViewController.closeSearch());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the preference screen for external access by SearchViewController.
|
||||
*/
|
||||
public PreferenceScreen getPreferenceScreenForSearch() {
|
||||
return preferenceScreen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package app.revanced.extension.youtube.settings.search;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.PreferenceScreen;
|
||||
|
||||
import app.revanced.extension.shared.settings.search.BaseSearchResultsAdapter;
|
||||
import app.revanced.extension.shared.settings.search.BaseSearchViewController;
|
||||
import app.revanced.extension.shared.settings.search.BaseSearchResultItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* YouTube-specific search results adapter.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class YouTubeSearchResultsAdapter extends BaseSearchResultsAdapter {
|
||||
|
||||
public YouTubeSearchResultsAdapter(Context context, List<BaseSearchResultItem> items,
|
||||
BaseSearchViewController.BasePreferenceFragment fragment,
|
||||
BaseSearchViewController searchViewController) {
|
||||
super(context, items, fragment, searchViewController);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PreferenceScreen getMainPreferenceScreen() {
|
||||
return fragment.getPreferenceScreenForSearch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package app.revanced.extension.youtube.settings.search;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.view.View;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import app.revanced.extension.shared.settings.search.BaseSearchResultItem;
|
||||
import app.revanced.extension.shared.settings.search.BaseSearchResultsAdapter;
|
||||
import app.revanced.extension.shared.settings.search.BaseSearchViewController;
|
||||
import app.revanced.extension.youtube.settings.preference.YouTubePreferenceFragment;
|
||||
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
|
||||
|
||||
/**
|
||||
* YouTube-specific search view controller implementation.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class YouTubeSearchViewController extends BaseSearchViewController {
|
||||
|
||||
public static YouTubeSearchViewController addSearchViewComponents(Activity activity, Toolbar toolbar,
|
||||
YouTubePreferenceFragment fragment) {
|
||||
return new YouTubeSearchViewController(activity, toolbar, fragment);
|
||||
}
|
||||
|
||||
private YouTubeSearchViewController(Activity activity, Toolbar toolbar, YouTubePreferenceFragment fragment) {
|
||||
super(activity, toolbar, new PreferenceFragmentAdapter(fragment));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseSearchResultsAdapter createSearchResultsAdapter() {
|
||||
return new YouTubeSearchResultsAdapter(activity, filteredSearchItems, fragment, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSpecialPreferenceGroup(Preference preference) {
|
||||
return preference instanceof SponsorBlockPreferenceGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupSpecialPreferenceListeners(BaseSearchResultItem item) {
|
||||
}
|
||||
|
||||
// Static method for Activity finish.
|
||||
public static boolean handleFinish(YouTubeSearchViewController searchViewController) {
|
||||
if (searchViewController != null && searchViewController.isSearchActive()) {
|
||||
searchViewController.closeSearch();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adapter to wrap YouTubePreferenceFragment to BasePreferenceFragment interface.
|
||||
private record PreferenceFragmentAdapter(YouTubePreferenceFragment fragment) implements BasePreferenceFragment {
|
||||
@Override
|
||||
public PreferenceScreen getPreferenceScreenForSearch() {
|
||||
return fragment.getPreferenceScreenForSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return fragment.getView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return fragment.getActivity();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,9 @@ import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
@@ -48,9 +47,10 @@ import kotlin.Unit;
|
||||
|
||||
/**
|
||||
* Handles showing, scheduling, and skipping of all {@link SponsorSegment} for the current video.
|
||||
*
|
||||
* <p>
|
||||
* Class is not thread safe. All methods must be called on the main thread unless otherwise specified.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public class SegmentPlaybackController {
|
||||
|
||||
/**
|
||||
@@ -122,7 +122,6 @@ public class SegmentPlaybackController {
|
||||
/**
|
||||
* Used to prevent re-showing a previously hidden skip button when exiting an embedded segment.
|
||||
* Only used when {@link Settings#SB_AUTO_HIDE_SKIP_BUTTON} is enabled.
|
||||
*
|
||||
* A collection of segments that have automatically hidden the skip button for, and all segments in this list
|
||||
* contain the current video time. Segment are removed when playback exits the segment.
|
||||
*/
|
||||
@@ -867,23 +866,11 @@ public class SegmentPlaybackController {
|
||||
|
||||
Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
// Remove window animations and use custom fade animation.
|
||||
window.setWindowAnimations(0);
|
||||
|
||||
WindowManager.LayoutParams params = window.getAttributes();
|
||||
params.gravity = Gravity.BOTTOM;
|
||||
params.y = dipToPixels(72);
|
||||
int portraitWidth = Utils.percentageWidthToPixels(60); // 60% of the screen width.
|
||||
|
||||
if (Resources.getSystem().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
portraitWidth = Math.min(portraitWidth, Utils.percentageHeightToPixels(60)); // 60% of the screen height.
|
||||
}
|
||||
params.width = portraitWidth;
|
||||
params.dimAmount = 0.0f;
|
||||
window.setAttributes(params);
|
||||
window.setBackgroundDrawable(null);
|
||||
window.setWindowAnimations(0); // Remove window animations and use custom fade animation.
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
|
||||
|
||||
Utils.setDialogWindowParameters(window, Gravity.BOTTOM, 72, 60, true);
|
||||
}
|
||||
|
||||
if (dismissUndoToast()) {
|
||||
|
||||
@@ -14,16 +14,19 @@ import androidx.annotation.Nullable;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.ui.CustomDialog;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
|
||||
import app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour;
|
||||
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory;
|
||||
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
|
||||
|
||||
@SuppressWarnings("NewApi")
|
||||
public class SponsorBlockSettings {
|
||||
/**
|
||||
* Minimum length a SB user id must be, as set by SB API.
|
||||
@@ -50,11 +53,15 @@ public class SponsorBlockSettings {
|
||||
JSONArray categorySelectionsArray = settingsJson.getJSONArray("categorySelections");
|
||||
|
||||
for (SegmentCategory category : SegmentCategory.categoriesWithoutUnsubmitted()) {
|
||||
// clear existing behavior, as browser plugin exports no behavior for ignored categories
|
||||
// Clear existing behavior, as browser plugin exports no behavior for ignored categories.
|
||||
category.setBehaviour(CategoryBehaviour.IGNORE);
|
||||
if (barTypesObject.has(category.keyValue)) {
|
||||
JSONObject categoryObject = barTypesObject.getJSONObject(category.keyValue);
|
||||
category.setColor(categoryObject.getString("color"));
|
||||
// Older ReVanced SB exports lack an opacity value.
|
||||
if (categoryObject.has("color") && categoryObject.has("opacity")) {
|
||||
category.setColorWithOpacity(categoryObject.getString("color"));
|
||||
category.setOpacity((float) categoryObject.getDouble("opacity"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +71,7 @@ public class SponsorBlockSettings {
|
||||
String categoryKey = categorySelectionObject.getString("name");
|
||||
SegmentCategory category = SegmentCategory.byCategoryKey(categoryKey);
|
||||
if (category == null) {
|
||||
continue; // unsupported category, ignore
|
||||
continue; // Unsupported category, ignore.
|
||||
}
|
||||
|
||||
final int desktopValue = categorySelectionObject.getInt("option");
|
||||
@@ -73,7 +80,7 @@ public class SponsorBlockSettings {
|
||||
Utils.showToastLong(categoryKey + " unknown behavior key: " + categoryKey);
|
||||
} else if (category == SegmentCategory.HIGHLIGHT && behaviour == CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE) {
|
||||
Utils.showToastLong("Skip-once behavior not allowed for " + category.keyValue);
|
||||
category.setBehaviour(CategoryBehaviour.SKIP_AUTOMATICALLY); // use closest match
|
||||
category.setBehaviour(CategoryBehaviour.SKIP_AUTOMATICALLY); // Use closest match.
|
||||
} else {
|
||||
category.setBehaviour(behaviour);
|
||||
}
|
||||
@@ -93,7 +100,7 @@ public class SponsorBlockSettings {
|
||||
Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.save(settingsJson.getBoolean("showTimeWithSkips"));
|
||||
|
||||
String serverAddress = settingsJson.getString("serverAddress");
|
||||
if (isValidSBServerAddress(serverAddress)) { // Old versions of ReVanced exported wrong url format
|
||||
if (isValidSBServerAddress(serverAddress)) { // Old versions of ReVanced exported wrong url format.
|
||||
Settings.SB_API_URL.save(serverAddress);
|
||||
}
|
||||
|
||||
@@ -103,7 +110,7 @@ public class SponsorBlockSettings {
|
||||
}
|
||||
Settings.SB_SEGMENT_MIN_DURATION.save(minDuration);
|
||||
|
||||
if (settingsJson.has("skipCount")) { // Value not exported in old versions of ReVanced
|
||||
if (settingsJson.has("skipCount")) { // Value not exported in old versions of ReVanced.
|
||||
int skipCount = settingsJson.getInt("skipCount");
|
||||
if (skipCount < 0) {
|
||||
throw new IllegalArgumentException("invalid skipCount: " + skipCount);
|
||||
@@ -121,7 +128,7 @@ public class SponsorBlockSettings {
|
||||
|
||||
Utils.showToastLong(str("revanced_sb_settings_import_successful"));
|
||||
} catch (Exception ex) {
|
||||
Logger.printInfo(() -> "failed to import settings", ex); // use info level, as we are showing our own toast
|
||||
Logger.printInfo(() -> "failed to import settings", ex); // Use info level, as we are showing our own toast.
|
||||
Utils.showToastLong(str("revanced_sb_settings_import_failed", ex.getMessage()));
|
||||
}
|
||||
}
|
||||
@@ -133,14 +140,16 @@ public class SponsorBlockSettings {
|
||||
Logger.printDebug(() -> "Creating SponsorBlock export settings string");
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
JSONObject barTypesObject = new JSONObject(); // categories' colors
|
||||
JSONArray categorySelectionsArray = new JSONArray(); // categories' behavior
|
||||
JSONObject barTypesObject = new JSONObject(); // Categories' colors.
|
||||
JSONArray categorySelectionsArray = new JSONArray(); // Categories' behavior.
|
||||
|
||||
SegmentCategory[] categories = SegmentCategory.categoriesWithoutUnsubmitted();
|
||||
for (SegmentCategory category : categories) {
|
||||
JSONObject categoryObject = new JSONObject();
|
||||
String categoryKey = category.keyValue;
|
||||
categoryObject.put("color", category.getColorString());
|
||||
// SB settings use separate color and opacity.
|
||||
categoryObject.put("color", category.getColorStringWithoutOpacity());
|
||||
categoryObject.put("opacity", category.getOpacity());
|
||||
barTypesObject.put(categoryKey, categoryObject);
|
||||
|
||||
if (category.behaviour != CategoryBehaviour.IGNORE) {
|
||||
@@ -167,7 +176,7 @@ public class SponsorBlockSettings {
|
||||
|
||||
return json.toString(2);
|
||||
} catch (Exception ex) {
|
||||
Logger.printInfo(() -> "failed to export settings", ex); // use info level, as we are showing our own toast
|
||||
Logger.printInfo(() -> "failed to export settings", ex); // Use info level, as we are showing our own toast.
|
||||
Utils.showToastLong(str("revanced_sb_settings_export_failed", ex));
|
||||
return "";
|
||||
}
|
||||
@@ -184,7 +193,7 @@ public class SponsorBlockSettings {
|
||||
if (dialogContext != null && SponsorBlockSettings.userHasSBPrivateId()
|
||||
&& !Settings.SB_HIDE_EXPORT_WARNING.get()) {
|
||||
// Create the custom dialog.
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||
dialogContext,
|
||||
null, // No title.
|
||||
str("revanced_sb_settings_revanced_export_user_id_warning"), // Message.
|
||||
@@ -217,15 +226,12 @@ public class SponsorBlockSettings {
|
||||
return false;
|
||||
}
|
||||
// Verify url is only the server address and does not contain a path such as: "https://sponsor.ajay.app/api/"
|
||||
// Could use Patterns.compile, but this is simpler
|
||||
// Could use Patterns.compile, but this is simpler.
|
||||
final int lastDotIndex = serverAddress.lastIndexOf('.');
|
||||
if (lastDotIndex != -1 && serverAddress.substring(lastDotIndex).contains("/")) {
|
||||
return false;
|
||||
}
|
||||
return lastDotIndex > 0 && !serverAddress.substring(lastDotIndex).contains("/");
|
||||
// Optionally, could also verify the domain exists using "InetAddress.getByName(serverAddress)"
|
||||
// but that should not be done on the main thread.
|
||||
// Instead, assume the domain exists and the user knows what they're doing.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,6 +257,22 @@ public class SponsorBlockSettings {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public static String migrateOldColorString(String colorString, float opacity) {
|
||||
if (colorString.length() >= 8) {
|
||||
return colorString;
|
||||
}
|
||||
|
||||
// Change color string from #RGB to #ARGB using default alpha.
|
||||
if (colorString.startsWith("#")) {
|
||||
colorString = colorString.substring(1);
|
||||
}
|
||||
|
||||
String alphaHex = String.format(Locale.US, "%02X", (int)(opacity * 255));
|
||||
String argbColorString = '#' + alphaHex + colorString.substring(0, 6);
|
||||
Logger.printDebug(() -> "Migrating old color string with default opacity: " + argbColorString);
|
||||
return argbColorString;
|
||||
}
|
||||
|
||||
private static boolean initialized;
|
||||
|
||||
public static void initialize() {
|
||||
|
||||
@@ -13,8 +13,6 @@ import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.NumberFormat;
|
||||
import java.time.Duration;
|
||||
@@ -53,11 +51,11 @@ public class SponsorBlockUtils {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
// start
|
||||
// Start.
|
||||
newSponsorSegmentStartMillis = newSponsorSegmentDialogShownMillis;
|
||||
break;
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
// end
|
||||
// End.
|
||||
newSponsorSegmentEndMillis = newSponsorSegmentDialogShownMillis;
|
||||
break;
|
||||
}
|
||||
@@ -98,7 +96,7 @@ public class SponsorBlockUtils {
|
||||
SegmentCategory[] categories = SegmentCategory.categoriesWithoutHighlights();
|
||||
CharSequence[] titles = new CharSequence[categories.length];
|
||||
for (int i = 0, length = categories.length; i < length; i++) {
|
||||
titles[i] = categories[i].getTitleWithColorDot();
|
||||
titles[i] = categories[i].getTitle().toString();
|
||||
}
|
||||
|
||||
newUserCreatedSegmentCategory = null;
|
||||
@@ -163,7 +161,7 @@ public class SponsorBlockUtils {
|
||||
SponsorSegment segment = segments[which];
|
||||
|
||||
SegmentVote[] voteOptions = (segment.category == SegmentCategory.HIGHLIGHT)
|
||||
? SegmentVote.voteTypesWithoutCategoryChange // highlight segments cannot change category
|
||||
? SegmentVote.voteTypesWithoutCategoryChange // Highlight segments cannot change category.
|
||||
: SegmentVote.values();
|
||||
final int voteOptionsLength = voteOptions.length;
|
||||
final boolean userIsVip = Settings.SB_USER_IS_VIP.get();
|
||||
@@ -282,7 +280,7 @@ public class SponsorBlockUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void onVotingClicked(@NonNull Context context) {
|
||||
public static void onVotingClicked(Context context) {
|
||||
try {
|
||||
Utils.verifyOnMainThread();
|
||||
SponsorSegment[] segments = SegmentPlaybackController.getSegments();
|
||||
@@ -304,7 +302,7 @@ public class SponsorBlockUtils {
|
||||
|
||||
SpannableStringBuilder spannableBuilder = new SpannableStringBuilder();
|
||||
|
||||
spannableBuilder.append(segment.category.getTitleWithColorDot());
|
||||
spannableBuilder.append(segment.category.getTitle().toString());
|
||||
spannableBuilder.append('\n');
|
||||
|
||||
String startTime = formatSegmentTime(segment.start);
|
||||
@@ -317,7 +315,7 @@ public class SponsorBlockUtils {
|
||||
}
|
||||
|
||||
if (i + 1 != numberOfSegments) {
|
||||
// prevents trailing new line after last segment
|
||||
// Prevents trailing new line after last segment.
|
||||
spannableBuilder.append('\n');
|
||||
}
|
||||
|
||||
@@ -333,13 +331,13 @@ public class SponsorBlockUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private static void onNewCategorySelect(@NonNull SponsorSegment segment, @NonNull Context context) {
|
||||
private static void onNewCategorySelect(SponsorSegment segment, Context context) {
|
||||
try {
|
||||
Utils.verifyOnMainThread();
|
||||
final SegmentCategory[] values = SegmentCategory.categoriesWithoutHighlights();
|
||||
CharSequence[] titles = new CharSequence[values.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
titles[i] = values[i].getTitleWithColorDot();
|
||||
for (int i = 0, length = values.length; i < length; i++) {
|
||||
titles[i] = values[i].getTitle().toString();
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
@@ -370,7 +368,6 @@ public class SponsorBlockUtils {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void sendViewRequestAsync(SponsorSegment segment) {
|
||||
if (segment.recordedAsSkipped || segment.category == SegmentCategory.UNSUBMITTED) {
|
||||
return;
|
||||
@@ -424,7 +421,6 @@ public class SponsorBlockUtils {
|
||||
String secondsStr = matcher.group(4);
|
||||
String millisecondsStr = matcher.group(6); // Milliseconds is optional.
|
||||
|
||||
|
||||
try {
|
||||
final int hours = (hoursStr != null) ? Integer.parseInt(hoursStr) : 0;
|
||||
//noinspection ConstantConditions
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
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;
|
||||
import android.graphics.Paint;
|
||||
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;
|
||||
@@ -26,40 +21,39 @@ import java.util.Objects;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.StringRef;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.FloatSetting;
|
||||
import app.revanced.extension.shared.settings.StringSetting;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
public enum SegmentCategory {
|
||||
SPONSOR("sponsor", sf("revanced_sb_segments_sponsor"), sf("revanced_sb_segments_sponsor_sum"), sf("revanced_sb_skip_button_sponsor"), sf("revanced_sb_skipped_sponsor"),
|
||||
SB_CATEGORY_SPONSOR, SB_CATEGORY_SPONSOR_COLOR, SB_CATEGORY_SPONSOR_OPACITY),
|
||||
SB_CATEGORY_SPONSOR, SB_CATEGORY_SPONSOR_COLOR),
|
||||
SELF_PROMO("selfpromo", sf("revanced_sb_segments_selfpromo"), sf("revanced_sb_segments_selfpromo_sum"), sf("revanced_sb_skip_button_selfpromo"), sf("revanced_sb_skipped_selfpromo"),
|
||||
SB_CATEGORY_SELF_PROMO, SB_CATEGORY_SELF_PROMO_COLOR, SB_CATEGORY_SELF_PROMO_OPACITY),
|
||||
SB_CATEGORY_SELF_PROMO, SB_CATEGORY_SELF_PROMO_COLOR),
|
||||
INTERACTION("interaction", sf("revanced_sb_segments_interaction"), sf("revanced_sb_segments_interaction_sum"), sf("revanced_sb_skip_button_interaction"), sf("revanced_sb_skipped_interaction"),
|
||||
SB_CATEGORY_INTERACTION, SB_CATEGORY_INTERACTION_COLOR, SB_CATEGORY_INTERACTION_OPACITY),
|
||||
SB_CATEGORY_INTERACTION, SB_CATEGORY_INTERACTION_COLOR),
|
||||
/**
|
||||
* Unique category that is treated differently than the rest.
|
||||
*/
|
||||
HIGHLIGHT("poi_highlight", sf("revanced_sb_segments_highlight"), sf("revanced_sb_segments_highlight_sum"), sf("revanced_sb_skip_button_highlight"), sf("revanced_sb_skipped_highlight"),
|
||||
SB_CATEGORY_HIGHLIGHT, SB_CATEGORY_HIGHLIGHT_COLOR, SB_CATEGORY_HIGHLIGHT_OPACITY),
|
||||
SB_CATEGORY_HIGHLIGHT, SB_CATEGORY_HIGHLIGHT_COLOR),
|
||||
INTRO("intro", sf("revanced_sb_segments_intro"), sf("revanced_sb_segments_intro_sum"),
|
||||
sf("revanced_sb_skip_button_intro_beginning"), sf("revanced_sb_skip_button_intro_middle"), sf("revanced_sb_skip_button_intro_end"),
|
||||
sf("revanced_sb_skipped_intro_beginning"), sf("revanced_sb_skipped_intro_middle"), sf("revanced_sb_skipped_intro_end"),
|
||||
SB_CATEGORY_INTRO, SB_CATEGORY_INTRO_COLOR, SB_CATEGORY_INTRO_OPACITY),
|
||||
SB_CATEGORY_INTRO, SB_CATEGORY_INTRO_COLOR),
|
||||
OUTRO("outro", sf("revanced_sb_segments_outro"), sf("revanced_sb_segments_outro_sum"), sf("revanced_sb_skip_button_outro"), sf("revanced_sb_skipped_outro"),
|
||||
SB_CATEGORY_OUTRO, SB_CATEGORY_OUTRO_COLOR, SB_CATEGORY_OUTRO_OPACITY),
|
||||
SB_CATEGORY_OUTRO, SB_CATEGORY_OUTRO_COLOR),
|
||||
PREVIEW("preview", sf("revanced_sb_segments_preview"), sf("revanced_sb_segments_preview_sum"),
|
||||
sf("revanced_sb_skip_button_preview_beginning"), sf("revanced_sb_skip_button_preview_middle"), sf("revanced_sb_skip_button_preview_end"),
|
||||
sf("revanced_sb_skipped_preview_beginning"), sf("revanced_sb_skipped_preview_middle"), sf("revanced_sb_skipped_preview_end"),
|
||||
SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR, SB_CATEGORY_PREVIEW_OPACITY),
|
||||
SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR),
|
||||
HOOK("hook", sf("revanced_sb_segments_hook"), sf("revanced_sb_segments_hook_sum"), sf("revanced_sb_skip_button_hook"), sf("revanced_sb_skipped_hook"),
|
||||
SB_CATEGORY_HOOK, SB_CATEGORY_HOOK_COLOR, SB_CATEGORY_HOOK_OPACITY),
|
||||
SB_CATEGORY_HOOK, SB_CATEGORY_HOOK_COLOR),
|
||||
FILLER("filler", sf("revanced_sb_segments_filler"), sf("revanced_sb_segments_filler_sum"), sf("revanced_sb_skip_button_filler"), sf("revanced_sb_skipped_filler"),
|
||||
SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR, SB_CATEGORY_FILLER_OPACITY),
|
||||
SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR),
|
||||
MUSIC_OFFTOPIC("music_offtopic", sf("revanced_sb_segments_nomusic"), sf("revanced_sb_segments_nomusic_sum"), sf("revanced_sb_skip_button_nomusic"), sf("revanced_sb_skipped_nomusic"),
|
||||
SB_CATEGORY_MUSIC_OFFTOPIC, SB_CATEGORY_MUSIC_OFFTOPIC_COLOR, SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY),
|
||||
SB_CATEGORY_MUSIC_OFFTOPIC, SB_CATEGORY_MUSIC_OFFTOPIC_COLOR),
|
||||
UNSUBMITTED("unsubmitted", StringRef.empty, StringRef.empty, sf("revanced_sb_skip_button_unsubmitted"), sf("revanced_sb_skipped_unsubmitted"),
|
||||
SB_CATEGORY_UNSUBMITTED, SB_CATEGORY_UNSUBMITTED_COLOR, SB_CATEGORY_UNSUBMITTED_OPACITY);
|
||||
SB_CATEGORY_UNSUBMITTED, SB_CATEGORY_UNSUBMITTED_COLOR);
|
||||
|
||||
private static final StringRef skipSponsorTextCompact = sf("revanced_sb_skip_button_compact");
|
||||
private static final StringRef skipSponsorTextCompactHighlight = sf("revanced_sb_skip_button_compact_highlight");
|
||||
@@ -88,10 +82,13 @@ public enum SegmentCategory {
|
||||
FILLER,
|
||||
MUSIC_OFFTOPIC,
|
||||
};
|
||||
|
||||
public static final float CATEGORY_DEFAULT_OPACITY = 0.7f;
|
||||
|
||||
private static final Map<String, SegmentCategory> mValuesMap = new HashMap<>(2 * categoriesWithoutUnsubmitted.length);
|
||||
|
||||
/**
|
||||
* Categories currently enabled, formatted for an API call
|
||||
* Categories currently enabled, formatted for an API call.
|
||||
*/
|
||||
public static String sponsorBlockAPIFetchCategories = "[]";
|
||||
|
||||
@@ -100,21 +97,30 @@ public enum SegmentCategory {
|
||||
mValuesMap.put(value.keyValue, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of categories excluding the unsubmitted category.
|
||||
*/
|
||||
public static SegmentCategory[] categoriesWithoutUnsubmitted() {
|
||||
return categoriesWithoutUnsubmitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of categories excluding the highlight category.
|
||||
*/
|
||||
public static SegmentCategory[] categoriesWithoutHighlights() {
|
||||
return categoriesWithoutHighlights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a category by its key.
|
||||
*/
|
||||
@Nullable
|
||||
public static SegmentCategory byCategoryKey(@NonNull String key) {
|
||||
return mValuesMap.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called if behavior of any category is changed.
|
||||
* Updates the list of enabled categories for API calls. Must be called when any category's behavior changes.
|
||||
*/
|
||||
public static void updateEnabledCategories() {
|
||||
Utils.verifyOnMainThread();
|
||||
@@ -134,6 +140,9 @@ public enum SegmentCategory {
|
||||
sponsorBlockAPIFetchCategories = "[%22" + TextUtils.join("%22,%22", enabledCategories) + "%22]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all category settings from persistent storage.
|
||||
*/
|
||||
public static void loadAllCategoriesFromSettings() {
|
||||
for (SegmentCategory category : values()) {
|
||||
category.loadFromSettings();
|
||||
@@ -141,45 +150,35 @@ public enum SegmentCategory {
|
||||
updateEnabledCategories();
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int applyOpacityToColor(@ColorInt int color, float opacity) {
|
||||
if (opacity < 0 || opacity > 1.0f) {
|
||||
throw new IllegalArgumentException("Invalid opacity: " + opacity);
|
||||
}
|
||||
final int opacityInt = (int) (255 * opacity);
|
||||
return (color & 0x00FFFFFF) | (opacityInt << 24);
|
||||
}
|
||||
|
||||
public final String keyValue;
|
||||
public final StringSetting behaviorSetting; // TODO: Replace with EnumSetting.
|
||||
private final StringSetting colorSetting;
|
||||
private final FloatSetting opacitySetting;
|
||||
public final StringSetting behaviorSetting;
|
||||
public final StringSetting colorSetting;
|
||||
|
||||
public final StringRef title;
|
||||
public final StringRef description;
|
||||
|
||||
/**
|
||||
* Skip button text, if the skip occurs in the first quarter of the video
|
||||
* Skip button text, if the skip occurs in the first quarter of the video.
|
||||
*/
|
||||
public final StringRef skipButtonTextBeginning;
|
||||
/**
|
||||
* Skip button text, if the skip occurs in the middle half of the video
|
||||
* Skip button text, if the skip occurs in the middle half of the video.
|
||||
*/
|
||||
public final StringRef skipButtonTextMiddle;
|
||||
/**
|
||||
* Skip button text, if the skip occurs in the last quarter of the video
|
||||
* Skip button text, if the skip occurs in the last quarter of the video.
|
||||
*/
|
||||
public final StringRef skipButtonTextEnd;
|
||||
/**
|
||||
* Skipped segment toast, if the skip occurred in the first quarter of the video
|
||||
* Skipped segment toast, if the skip occurred in the first quarter of the video.
|
||||
*/
|
||||
public final StringRef skippedToastBeginning;
|
||||
/**
|
||||
* Skipped segment toast, if the skip occurred in the middle half of the video
|
||||
* Skipped segment toast, if the skip occurred in the middle half of the video.
|
||||
*/
|
||||
public final StringRef skippedToastMiddle;
|
||||
/**
|
||||
* Skipped segment toast, if the skip occurred in the last quarter of the video
|
||||
* Skipped segment toast, if the skip occurred in the last quarter of the video.
|
||||
*/
|
||||
public final StringRef skippedToastEnd;
|
||||
|
||||
@@ -193,7 +192,7 @@ public enum SegmentCategory {
|
||||
|
||||
/**
|
||||
* Value must be changed using {@link #setBehaviour(CategoryBehaviour)}.
|
||||
* Caller must also {@link #updateEnabledCategories()}.
|
||||
* Caller must also call {@link #updateEnabledCategories()}.
|
||||
*/
|
||||
public CategoryBehaviour behaviour = CategoryBehaviour.IGNORE;
|
||||
|
||||
@@ -201,19 +200,19 @@ public enum SegmentCategory {
|
||||
StringRef skipButtonText,
|
||||
StringRef skippedToastText,
|
||||
StringSetting behavior,
|
||||
StringSetting color, FloatSetting opacity) {
|
||||
StringSetting color) {
|
||||
this(keyValue, title, description,
|
||||
skipButtonText, skipButtonText, skipButtonText,
|
||||
skippedToastText, skippedToastText, skippedToastText,
|
||||
behavior,
|
||||
color, opacity);
|
||||
color);
|
||||
}
|
||||
|
||||
SegmentCategory(String keyValue, StringRef title, StringRef description,
|
||||
StringRef skipButtonTextBeginning, StringRef skipButtonTextMiddle, StringRef skipButtonTextEnd,
|
||||
StringRef skippedToastBeginning, StringRef skippedToastMiddle, StringRef skippedToastEnd,
|
||||
StringSetting behavior,
|
||||
StringSetting color, FloatSetting opacity) {
|
||||
StringSetting color) {
|
||||
this.keyValue = Objects.requireNonNull(keyValue);
|
||||
this.title = Objects.requireNonNull(title);
|
||||
this.description = Objects.requireNonNull(description);
|
||||
@@ -225,11 +224,13 @@ public enum SegmentCategory {
|
||||
this.skippedToastEnd = Objects.requireNonNull(skippedToastEnd);
|
||||
this.behaviorSetting = Objects.requireNonNull(behavior);
|
||||
this.colorSetting = Objects.requireNonNull(color);
|
||||
this.opacitySetting = Objects.requireNonNull(opacity);
|
||||
this.paint = new Paint();
|
||||
loadFromSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the category's behavior and color from settings.
|
||||
*/
|
||||
private void loadFromSettings() {
|
||||
String behaviorString = behaviorSetting.get();
|
||||
CategoryBehaviour savedBehavior = CategoryBehaviour.byReVancedKeyValue(behaviorString);
|
||||
@@ -242,118 +243,93 @@ public enum SegmentCategory {
|
||||
this.behaviour = savedBehavior;
|
||||
|
||||
String colorString = colorSetting.get();
|
||||
final float opacity = opacitySetting.get();
|
||||
try {
|
||||
setColor(colorString);
|
||||
setOpacity(opacity);
|
||||
setColorWithOpacity(colorString);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Invalid color: " + colorString + " opacity: " + opacity, ex);
|
||||
Logger.printException(() -> "Invalid color: " + colorString, ex);
|
||||
colorSetting.resetToDefault();
|
||||
opacitySetting.resetToDefault();
|
||||
loadFromSettings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the behavior of the category and saves it to settings.
|
||||
*/
|
||||
public void setBehaviour(CategoryBehaviour behaviour) {
|
||||
this.behaviour = Objects.requireNonNull(behaviour);
|
||||
this.behaviorSetting.save(behaviour.reVancedKeyValue);
|
||||
}
|
||||
|
||||
private void updateColor() {
|
||||
color = applyOpacityToColor(color, opacitySetting.get());
|
||||
/**
|
||||
* Sets the segment color with opacity from a color string in #AARRGGBB format.
|
||||
*/
|
||||
public void setColorWithOpacity(String colorString) throws IllegalArgumentException {
|
||||
int colorWithOpacity = Color.parseColor(colorString);
|
||||
colorSetting.save(String.format(Locale.US, "#%08X", colorWithOpacity));
|
||||
color = colorWithOpacity;
|
||||
paint.setColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param opacity Segment color opacity between [0, 1].
|
||||
* @param opacity [0, 1] opacity value.
|
||||
*/
|
||||
public void setOpacity(float opacity) throws IllegalArgumentException {
|
||||
if (opacity < 0 || opacity > 1) {
|
||||
throw new IllegalArgumentException("Invalid opacity: " + opacity);
|
||||
}
|
||||
|
||||
opacitySetting.save(opacity);
|
||||
updateColor();
|
||||
}
|
||||
|
||||
public float getOpacity() {
|
||||
return opacitySetting.get();
|
||||
}
|
||||
|
||||
public float getOpacityDefault() {
|
||||
return opacitySetting.defaultValue;
|
||||
}
|
||||
|
||||
public void resetColorAndOpacity() {
|
||||
setColor(colorSetting.defaultValue);
|
||||
setOpacity(opacitySetting.defaultValue);
|
||||
public void setOpacity(double opacity) {
|
||||
color = Color.argb((int) (opacity * 255), Color.red(color), Color.green(color), Color.blue(color));
|
||||
paint.setColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param colorString Segment color with #RRGGBB format.
|
||||
*/
|
||||
public void setColor(String colorString) throws IllegalArgumentException {
|
||||
color = Color.parseColor(colorString);
|
||||
colorSetting.save(colorString);
|
||||
|
||||
updateColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Integer color of #RRGGBB format.
|
||||
* Gets the color with opacity applied (ARGB).
|
||||
*/
|
||||
@ColorInt
|
||||
public int getColorNoOpacity() {
|
||||
return color & 0x00FFFFFF;
|
||||
public int getColorWithOpacity() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Integer color of #RRGGBB format.
|
||||
* @return The default color with opacity applied.
|
||||
*/
|
||||
@ColorInt
|
||||
public int getColorNoOpacityDefault() {
|
||||
return Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF;
|
||||
public int getDefaultColorWithOpacity() {
|
||||
return Color.parseColor(colorSetting.defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Hex color string of #RRGGBB format with no opacity level.
|
||||
* Gets the color as a hex string with opacity (#AARRGGBB).
|
||||
*/
|
||||
public String getColorString() {
|
||||
return String.format(Locale.US, "#%06X", getColorNoOpacity());
|
||||
}
|
||||
|
||||
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(@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(@ColorInt int categoryColor) {
|
||||
return getCategoryColorDotSpan(" " + title, categoryColor);
|
||||
}
|
||||
|
||||
public SpannableString getTitleWithColorDot() {
|
||||
return getTitleWithColorDot(color);
|
||||
public String getColorStringWithOpacity() {
|
||||
return String.format(Locale.US, "#%08X", getColorWithOpacity());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param segmentStartTime video time the segment category started
|
||||
* @param videoLength length of the video
|
||||
* @return the skip button text
|
||||
* @return The color as a hex string without opacity (#RRGGBB).
|
||||
*/
|
||||
public String getColorStringWithoutOpacity() {
|
||||
final int colorNoOpacity = getColorWithOpacity() & 0x00FFFFFF;
|
||||
return String.format(Locale.US, "#%06X", colorNoOpacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return [0, 1] opacity value.
|
||||
*/
|
||||
public double getOpacity() {
|
||||
double opacity = Color.alpha(color) / 255.0;
|
||||
return Math.round(opacity * 100.0) / 100.0; // Round to 2 decimal digits.
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of the category.
|
||||
*/
|
||||
public StringRef getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the skip button text based on segment position.
|
||||
*
|
||||
* @param segmentStartTime Video time the segment category started.
|
||||
* @param videoLength Length of the video.
|
||||
* @return The skip button text.
|
||||
*/
|
||||
StringRef getSkipButtonText(long segmentStartTime, long videoLength) {
|
||||
if (Settings.SB_COMPACT_SKIP_BUTTON.get()) {
|
||||
@@ -375,9 +351,11 @@ public enum SegmentCategory {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param segmentStartTime video time the segment category started
|
||||
* @param videoLength length of the video
|
||||
* @return 'skipped segment' toast message
|
||||
* Gets the skipped segment toast message based on segment position.
|
||||
*
|
||||
* @param segmentStartTime Video time the segment category started.
|
||||
* @param videoLength Length of the video.
|
||||
* @return The skipped segment toast message.
|
||||
*/
|
||||
StringRef getSkippedToastText(long segmentStartTime, long videoLength) {
|
||||
if (videoLength == 0) {
|
||||
|
||||
@@ -1,371 +0,0 @@
|
||||
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.Utils.dipToPixels;
|
||||
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
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.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.*;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* RGB format (no alpha).
|
||||
*/
|
||||
@ColorInt
|
||||
private int categoryColor;
|
||||
/**
|
||||
* [0, 1]
|
||||
*/
|
||||
private float categoryOpacity;
|
||||
private int selectedDialogEntryIndex;
|
||||
|
||||
private TextView dialogColorDotView;
|
||||
private EditText dialogColorEditText;
|
||||
private EditText dialogOpacityEditText;
|
||||
private ColorPickerView dialogColorPickerView;
|
||||
private Dialog dialog;
|
||||
|
||||
public SegmentCategoryListPreference(Context context, SegmentCategory category) {
|
||||
super(context);
|
||||
this.category = Objects.requireNonNull(category);
|
||||
|
||||
// Edit: Using preferences to sync together multiple pieces
|
||||
// of code is messy and should be rethought.
|
||||
setKey(category.behaviorSetting.key);
|
||||
setDefaultValue(category.behaviorSetting.defaultValue);
|
||||
|
||||
final boolean isHighlightCategory = category == SegmentCategory.HIGHLIGHT;
|
||||
setEntries(isHighlightCategory
|
||||
? CategoryBehaviour.getBehaviorDescriptionsWithoutSkipOnce()
|
||||
: CategoryBehaviour.getBehaviorDescriptions());
|
||||
setEntryValues(isHighlightCategory
|
||||
? CategoryBehaviour.getBehaviorKeyValuesWithoutSkipOnce()
|
||||
: CategoryBehaviour.getBehaviorKeyValues());
|
||||
super.setSummary(category.description.toString());
|
||||
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showDialog(Bundle state) {
|
||||
try {
|
||||
Context context = getContext();
|
||||
categoryColor = category.getColorNoOpacity();
|
||||
categoryOpacity = category.getOpacity();
|
||||
selectedDialogEntryIndex = findIndexOfValue(getValue());
|
||||
|
||||
// Create the main layout for the dialog content.
|
||||
LinearLayout contentLayout = new LinearLayout(context);
|
||||
contentLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
// 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(dipToPixels(10), 0, 0, 0);
|
||||
contentLayout.addView(radioGroup);
|
||||
|
||||
// Inflate the color picker view.
|
||||
View colorPickerContainer = LayoutInflater.from(context)
|
||||
.inflate(getResourceIdentifier("revanced_color_picker", "layout"), null);
|
||||
dialogColorPickerView = colorPickerContainer.findViewById(
|
||||
getResourceIdentifier("revanced_color_picker_view", "id"));
|
||||
dialogColorPickerView.setColor(categoryColor);
|
||||
contentLayout.addView(colorPickerContainer);
|
||||
|
||||
// Grid layout for color and opacity inputs.
|
||||
GridLayout gridLayout = new GridLayout(context);
|
||||
gridLayout.setColumnCount(3);
|
||||
gridLayout.setRowCount(2);
|
||||
gridLayout.setPadding(dipToPixels(16), 0, 0, 0);
|
||||
|
||||
GridLayout.LayoutParams gridParams = new GridLayout.LayoutParams();
|
||||
gridParams.rowSpec = GridLayout.spec(0); // First row.
|
||||
gridParams.columnSpec = GridLayout.spec(0); // First column.
|
||||
TextView colorTextLabel = new TextView(context);
|
||||
colorTextLabel.setText(str("revanced_sb_color_dot_label"));
|
||||
colorTextLabel.setLayoutParams(gridParams);
|
||||
gridLayout.addView(colorTextLabel);
|
||||
|
||||
gridParams = new GridLayout.LayoutParams();
|
||||
gridParams.rowSpec = GridLayout.spec(0); // First row.
|
||||
gridParams.columnSpec = GridLayout.spec(1); // Second column.
|
||||
gridParams.setMargins(0, 0, dipToPixels(10), 0);
|
||||
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.
|
||||
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) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable edit) {
|
||||
try {
|
||||
String colorString = edit.toString();
|
||||
String normalizedColorString = ColorPickerPreference.cleanupColorCodeString(colorString);
|
||||
|
||||
if (!normalizedColorString.equals(colorString)) {
|
||||
edit.replace(0, colorString.length(), normalizedColorString);
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalizedColorString.length() != ColorPickerPreference.COLOR_STRING_LENGTH) {
|
||||
// User is still typing out the color.
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
gridLayout.addView(dialogColorEditText, gridParams);
|
||||
|
||||
gridParams = new GridLayout.LayoutParams();
|
||||
gridParams.rowSpec = GridLayout.spec(1); // Second row.
|
||||
gridParams.columnSpec = GridLayout.spec(0, 1); // First and second column.
|
||||
TextView opacityLabel = new TextView(context);
|
||||
opacityLabel.setText(str("revanced_sb_color_opacity_label"));
|
||||
opacityLabel.setLayoutParams(gridParams);
|
||||
gridLayout.addView(opacityLabel);
|
||||
|
||||
gridParams = new GridLayout.LayoutParams();
|
||||
gridParams.rowSpec = GridLayout.spec(1); // Second row.
|
||||
gridParams.columnSpec = GridLayout.spec(2); // Third column.
|
||||
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) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable edit) {
|
||||
try {
|
||||
String editString = edit.toString();
|
||||
final int opacityStringLength = editString.length();
|
||||
|
||||
final int maxOpacityStringLength = 4; // [0.00, 1.00]
|
||||
if (opacityStringLength > maxOpacityStringLength) {
|
||||
edit.delete(maxOpacityStringLength, opacityStringLength);
|
||||
return;
|
||||
}
|
||||
|
||||
final float opacity = opacityStringLength == 0
|
||||
? 0
|
||||
: Float.parseFloat(editString);
|
||||
if (opacity < 0) {
|
||||
categoryOpacity = 0;
|
||||
edit.replace(0, opacityStringLength, "0");
|
||||
return;
|
||||
} else if (opacity > 1.0f) {
|
||||
categoryOpacity = 1;
|
||||
edit.replace(0, opacityStringLength, "1.0");
|
||||
return;
|
||||
} else if (!editString.endsWith(".")) {
|
||||
// Ignore "0." and "1." until the user finishes entering a valid number.
|
||||
categoryOpacity = opacity;
|
||||
}
|
||||
|
||||
updateCategoryColorDot();
|
||||
} catch (Exception ex) {
|
||||
// Should never happen.
|
||||
Logger.printException(() -> "opacityEditText afterTextChanged failure", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
gridLayout.addView(dialogOpacityEditText, gridParams);
|
||||
updateOpacityText();
|
||||
|
||||
contentLayout.addView(gridLayout);
|
||||
|
||||
// Create ScrollView to wrap the content layout.
|
||||
ScrollView contentScrollView = new ScrollView(context);
|
||||
contentScrollView.setVerticalScrollBarEnabled(false); // Disable vertical scrollbar.
|
||||
contentScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); // Disable overscroll effect.
|
||||
LinearLayout.LayoutParams scrollViewParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
0,
|
||||
1.0f
|
||||
);
|
||||
contentScrollView.setLayoutParams(scrollViewParams);
|
||||
contentScrollView.addView(contentLayout);
|
||||
|
||||
// Create the custom dialog.
|
||||
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();
|
||||
}
|
||||
|
||||
try {
|
||||
category.setColor(dialogColorEditText.getText().toString());
|
||||
category.setOpacity(categoryOpacity);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Utils.showToastShort(str("revanced_settings_color_invalid"));
|
||||
}
|
||||
|
||||
updateUI();
|
||||
}
|
||||
},
|
||||
() -> {}, // Cancel button action (dismiss only).
|
||||
str("revanced_settings_reset_color"), // Neutral button text.
|
||||
() -> {
|
||||
// Neutral button action (Reset).
|
||||
try {
|
||||
// Setting view color causes callback to update the UI.
|
||||
dialogColorPickerView.setColor(category.getColorNoOpacityDefault());
|
||||
|
||||
categoryOpacity = category.getOpacityDefault();
|
||||
updateOpacityText();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "resetButton onClick failure", ex);
|
||||
}
|
||||
},
|
||||
false // Do not dismiss dialog on Neutral button click.
|
||||
);
|
||||
|
||||
// Add the ScrollView to the dialog's main layout.
|
||||
LinearLayout dialogMainLayout = dialogPair.second;
|
||||
dialogMainLayout.addView(contentScrollView, dialogMainLayout.getChildCount() - 1);
|
||||
|
||||
// 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());
|
||||
});
|
||||
|
||||
// Show the dialog.
|
||||
dialog = dialogPair.first;
|
||||
dialog.show();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "showDialog failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
// Nullify dialog references.
|
||||
dialogColorDotView = null;
|
||||
dialogColorEditText = null;
|
||||
dialogOpacityEditText = null;
|
||||
dialogColorPickerView = null;
|
||||
|
||||
if (dialog != null) {
|
||||
dialog.dismiss();
|
||||
dialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
private int applyOpacityToCategoryColor() {
|
||||
return applyOpacityToColor(categoryColor, categoryOpacity);
|
||||
}
|
||||
|
||||
public void updateUI() {
|
||||
categoryColor = category.getColorNoOpacity();
|
||||
categoryOpacity = category.getOpacity();
|
||||
|
||||
setTitle(category.getTitleWithColorDot(applyOpacityToCategoryColor()));
|
||||
}
|
||||
|
||||
private void updateCategoryColorDot() {
|
||||
dialogColorDotView.setText(SegmentCategory.getCategoryColorDot(applyOpacityToCategoryColor()));
|
||||
}
|
||||
|
||||
private void updateOpacityText() {
|
||||
dialogOpacityEditText.setText(String.format(Locale.US, "%.2f", categoryOpacity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSummary(CharSequence summary) {
|
||||
// Ignore calls to set the summary.
|
||||
// Summary is always the description of the category.
|
||||
//
|
||||
// This is required otherwise the ReVanced preference fragment
|
||||
// sets all ListPreference summaries to show the current selection.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package app.revanced.extension.youtube.sponsorblock.objects;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings.migrateOldColorString;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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.ui.ColorDot;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class SegmentCategoryPreference extends ColorPickerPreference {
|
||||
public final SegmentCategory category;
|
||||
|
||||
/**
|
||||
* View displaying a colored dot in the widget area.
|
||||
*/
|
||||
private View widgetColorDot;
|
||||
|
||||
// Fields to store dialog state for the OK button handler.
|
||||
private int selectedDialogEntryIndex;
|
||||
private CharSequence[] entryValues;
|
||||
|
||||
|
||||
public SegmentCategoryPreference(Context context, SegmentCategory category) {
|
||||
super(context);
|
||||
this.category = Objects.requireNonNull(category);
|
||||
|
||||
// Set key to color setting for persistence.
|
||||
// Edit: Using preferences to sync together multiple pieces of code is messy and should be rethought.
|
||||
setKey(category.colorSetting.key);
|
||||
setTitle(category.title.toString());
|
||||
setSummary(category.description.toString());
|
||||
|
||||
// Enable opacity slider for this preference.
|
||||
setOpacitySliderEnabled(true);
|
||||
|
||||
setWidgetLayoutResource(LAYOUT_REVANCED_COLOR_DOT_WIDGET);
|
||||
|
||||
// Sync initial color from category.
|
||||
setText(category.getColorStringWithOpacity());
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setText(String colorString) {
|
||||
try {
|
||||
// Migrate old data imported in the settings UI.
|
||||
// This migration is needed here because pasting into the settings
|
||||
// immediately syncs the data with the preferences.
|
||||
colorString = migrateOldColorString(colorString, SegmentCategory.CATEGORY_DEFAULT_OPACITY);
|
||||
super.setText(colorString);
|
||||
|
||||
// Save to category.
|
||||
category.setColorWithOpacity(colorString);
|
||||
updateUI();
|
||||
|
||||
// Notify the listener about the color change.
|
||||
if (colorChangeListener != null) {
|
||||
colorChangeListener.onColorChanged(getKey(), category.getColorWithOpacity());
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Utils.showToastShort(str("revanced_settings_color_invalid"));
|
||||
setText(category.colorSetting.defaultValue);
|
||||
} catch (Exception ex) {
|
||||
String colorStringFinal = colorString;
|
||||
Logger.printException(() -> "setText failure: " + colorStringFinal, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected View createExtraDialogContentView(Context context) {
|
||||
final boolean isHighlightCategory = category == SegmentCategory.HIGHLIGHT;
|
||||
entryValues = isHighlightCategory
|
||||
? CategoryBehaviour.getBehaviorKeyValuesWithoutSkipOnce()
|
||||
: CategoryBehaviour.getBehaviorKeyValues();
|
||||
|
||||
String currentBehavior = category.behaviorSetting.get();
|
||||
selectedDialogEntryIndex = -1;
|
||||
for (int i = 0; i < entryValues.length; i++) {
|
||||
if (entryValues[i].equals(currentBehavior)) {
|
||||
selectedDialogEntryIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RadioGroup radioGroup = new RadioGroup(context);
|
||||
radioGroup.setOrientation(RadioGroup.VERTICAL);
|
||||
CharSequence[] entries = isHighlightCategory
|
||||
? CategoryBehaviour.getBehaviorDescriptionsWithoutSkipOnce()
|
||||
: CategoryBehaviour.getBehaviorDescriptions();
|
||||
|
||||
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(dipToPixels(10), 0, dipToPixels(10), dipToPixels(10));
|
||||
return radioGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogOkClicked() {
|
||||
if (selectedDialogEntryIndex >= 0 && entryValues != null) {
|
||||
String value = entryValues[selectedDialogEntryIndex].toString();
|
||||
category.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value)));
|
||||
SegmentCategory.updateEnabledCategories();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogNeutralClicked() {
|
||||
try {
|
||||
final int defaultColor = category.getDefaultColorWithOpacity();
|
||||
dialogColorPickerView.setColor(defaultColor);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Reset button failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateUI() {
|
||||
try {
|
||||
if (category.behaviorSetting != null) {
|
||||
setEnabled(category.behaviorSetting.isAvailable());
|
||||
}
|
||||
|
||||
updateWidgetColorDot();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "updateUI failure for category: " + category.keyValue, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
|
||||
widgetColorDot = view.findViewById(ID_PREFERENCE_COLOR_DOT);
|
||||
updateWidgetColorDot();
|
||||
}
|
||||
|
||||
private void updateWidgetColorDot() {
|
||||
if (widgetColorDot == null) return;
|
||||
|
||||
ColorDot.applyColorDot(
|
||||
widgetColorDot,
|
||||
category.getColorWithOpacity(),
|
||||
isEnabled()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||
public enum SegmentVote {
|
||||
UPVOTE(sf("revanced_sb_vote_upvote"), 1,false),
|
||||
DOWNVOTE(sf("revanced_sb_vote_downvote"), 0, true),
|
||||
CATEGORY_CHANGE(sf("revanced_sb_vote_category"), -1, true); // apiVoteType is not used for category change
|
||||
CATEGORY_CHANGE(sf("revanced_sb_vote_category"), -1, true); // ApiVoteType is not used for category change.
|
||||
|
||||
public static final SegmentVote[] voteTypesWithoutCategoryChange = {
|
||||
UPVOTE,
|
||||
@@ -104,7 +104,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||
/**
|
||||
* @return The start/end time in range form.
|
||||
* Range times are adjusted since it uses inclusive and Segments use exclusive.
|
||||
*
|
||||
* <p>
|
||||
* {@link SegmentCategory#HIGHLIGHT} is unique and
|
||||
* returns a range from the start of the video until the highlight.
|
||||
*/
|
||||
@@ -116,7 +116,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length of this segment, in milliseconds. Always a positive number.
|
||||
* @return the length of this segment, in milliseconds. Always a positive number.
|
||||
*/
|
||||
public long length() {
|
||||
return end - start;
|
||||
@@ -148,8 +148,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof SponsorSegment)) return false;
|
||||
SponsorSegment other = (SponsorSegment) o;
|
||||
if (!(o instanceof SponsorSegment other)) return false;
|
||||
return Objects.equals(UUID, other.UUID)
|
||||
&& category == other.category
|
||||
&& start == other.start
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package app.revanced.extension.youtube.sponsorblock.ui;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.getResourceColor;
|
||||
import static app.revanced.extension.shared.Utils.getResourceDimensionPixelSize;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
@@ -10,14 +14,10 @@ import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.youtube.patches.VideoInformation;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.getResourceColor;
|
||||
import static app.revanced.extension.shared.Utils.getResourceDimensionPixelSize;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||
|
||||
public final class NewSegmentLayout extends FrameLayout {
|
||||
private static final ColorStateList rippleColorStateList = new ColorStateList(
|
||||
@@ -45,7 +45,7 @@ public final class NewSegmentLayout extends FrameLayout {
|
||||
super(context, attributeSet, defStyleAttr, defStyleRes);
|
||||
|
||||
LayoutInflater.from(context).inflate(
|
||||
getResourceIdentifier(context, "revanced_sb_new_segment", "layout"), this, true
|
||||
getResourceIdentifierOrThrow(context, "revanced_sb_new_segment", "layout"), this, true
|
||||
);
|
||||
|
||||
initializeButton(
|
||||
@@ -104,7 +104,7 @@ public final class NewSegmentLayout extends FrameLayout {
|
||||
*/
|
||||
private void initializeButton(final Context context, final String resourceIdentifierName,
|
||||
final ButtonOnClickHandlerFunction handler, final String debugMessage) {
|
||||
ImageButton button = findViewById(getResourceIdentifier(context, resourceIdentifierName, "id"));
|
||||
ImageButton button = findViewById(getResourceIdentifierOrThrow(context, resourceIdentifierName, "id"));
|
||||
|
||||
// Add ripple effect
|
||||
RippleDrawable rippleDrawable = new RippleDrawable(
|
||||
|
||||
@@ -4,6 +4,7 @@ import static app.revanced.extension.shared.Utils.getResourceColor;
|
||||
import static app.revanced.extension.shared.Utils.getResourceDimension;
|
||||
import static app.revanced.extension.shared.Utils.getResourceDimensionPixelSize;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
@@ -56,9 +57,11 @@ public class SkipSponsorButton extends FrameLayout {
|
||||
public SkipSponsorButton(Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attributeSet, defStyleAttr, defStyleRes);
|
||||
|
||||
LayoutInflater.from(context).inflate(getResourceIdentifier(context, "revanced_sb_skip_sponsor_button", "layout"), this, true); // layout:skip_ad_button
|
||||
LayoutInflater.from(context).inflate(getResourceIdentifierOrThrow(context,
|
||||
"revanced_sb_skip_sponsor_button", "layout"), this, true); // layout:skip_ad_button
|
||||
setMinimumHeight(getResourceDimensionPixelSize("ad_skip_ad_button_min_height")); // dimen:ad_skip_ad_button_min_height
|
||||
skipSponsorBtnContainer = Objects.requireNonNull(findViewById(getResourceIdentifier(context, "revanced_sb_skip_sponsor_button_container", "id"))); // id:skip_ad_button_container
|
||||
skipSponsorBtnContainer = Objects.requireNonNull(findViewById(getResourceIdentifierOrThrow(
|
||||
context, "revanced_sb_skip_sponsor_button_container", "id"))); // id:skip_ad_button_container
|
||||
|
||||
background = new Paint();
|
||||
background.setColor(getResourceColor("skip_ad_button_background_color")); // color:skip_ad_button_background_color);
|
||||
|
||||
@@ -3,7 +3,7 @@ package app.revanced.extension.youtube.sponsorblock.ui;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import app.revanced.extension.youtube.settings.preference.UrlLinkPreference;
|
||||
import app.revanced.extension.shared.settings.preference.UrlLinkPreference;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SponsorBlockAboutPreference extends UrlLinkPreference {
|
||||
|
||||
@@ -10,11 +10,11 @@ import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.text.Html;
|
||||
import android.text.InputType;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Pair;
|
||||
@@ -29,13 +29,16 @@ import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||
import app.revanced.extension.shared.settings.preference.ResettableEditTextPreference;
|
||||
import app.revanced.extension.shared.ui.CustomDialog;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
|
||||
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
|
||||
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory;
|
||||
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategoryListPreference;
|
||||
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategoryPreference;
|
||||
|
||||
/**
|
||||
* Lots of old code that could be converted to a half dozen custom preferences,
|
||||
@@ -54,27 +57,9 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
*/
|
||||
private boolean preferencesInitialized;
|
||||
|
||||
private SwitchPreference sbEnabled;
|
||||
private SwitchPreference addNewSegment;
|
||||
private SwitchPreference votingEnabled;
|
||||
private SwitchPreference autoHideSkipSegmentButton;
|
||||
private SwitchPreference compactSkipButton;
|
||||
private SwitchPreference squareLayout;
|
||||
private SwitchPreference showSkipToast;
|
||||
private SwitchPreference trackSkips;
|
||||
private SwitchPreference showTimeWithoutSegments;
|
||||
private SwitchPreference toastOnConnectionError;
|
||||
private CustomDialogListPreference autoHideSkipSegmentButtonDuration;
|
||||
private CustomDialogListPreference showSkipToastDuration;
|
||||
|
||||
private ResettableEditTextPreference newSegmentStep;
|
||||
private ResettableEditTextPreference minSegmentDuration;
|
||||
private EditTextPreference privateUserId;
|
||||
private EditTextPreference importExport;
|
||||
private Preference apiUrl;
|
||||
|
||||
private PreferenceCategory segmentCategory;
|
||||
private final List<SegmentCategoryListPreference> segmentCategories = new ArrayList<>();
|
||||
private final List<SegmentCategoryPreference> segmentCategories = new ArrayList<>();
|
||||
|
||||
public SponsorBlockPreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
@@ -99,60 +84,17 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
try {
|
||||
Logger.printDebug(() -> "updateUI");
|
||||
|
||||
final boolean enabled = Settings.SB_ENABLED.get();
|
||||
if (!enabled) {
|
||||
if (!Settings.SB_ENABLED.get()) {
|
||||
SponsorBlockViewController.hideAll();
|
||||
SegmentPlaybackController.setCurrentVideoId(null);
|
||||
} else if (!Settings.SB_CREATE_NEW_SEGMENT.get()) {
|
||||
SponsorBlockViewController.hideNewSegmentLayout();
|
||||
}
|
||||
// Voting and add new segment buttons automatically show/hide themselves.
|
||||
|
||||
SponsorBlockViewController.updateLayout();
|
||||
|
||||
sbEnabled.setChecked(enabled);
|
||||
|
||||
addNewSegment.setChecked(Settings.SB_CREATE_NEW_SEGMENT.get());
|
||||
addNewSegment.setEnabled(enabled);
|
||||
|
||||
votingEnabled.setChecked(Settings.SB_VOTING_BUTTON.get());
|
||||
votingEnabled.setEnabled(enabled);
|
||||
|
||||
autoHideSkipSegmentButton.setChecked(Settings.SB_AUTO_HIDE_SKIP_BUTTON.get());
|
||||
autoHideSkipSegmentButton.setEnabled(enabled);
|
||||
|
||||
autoHideSkipSegmentButtonDuration.setValue(Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION.get().toString());
|
||||
autoHideSkipSegmentButtonDuration.setEnabled(Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION.isAvailable());
|
||||
|
||||
compactSkipButton.setChecked(Settings.SB_COMPACT_SKIP_BUTTON.get());
|
||||
compactSkipButton.setEnabled(enabled);
|
||||
|
||||
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
|
||||
showSkipToast.setEnabled(enabled);
|
||||
|
||||
squareLayout.setChecked(Settings.SB_SQUARE_LAYOUT.get());
|
||||
squareLayout.setEnabled(enabled);
|
||||
|
||||
showSkipToastDuration.setValue(Settings.SB_TOAST_ON_SKIP_DURATION.get().toString());
|
||||
showSkipToastDuration.setEnabled(Settings.SB_TOAST_ON_SKIP_DURATION.isAvailable());
|
||||
|
||||
toastOnConnectionError.setChecked(Settings.SB_TOAST_ON_CONNECTION_ERROR.get());
|
||||
toastOnConnectionError.setEnabled(enabled);
|
||||
|
||||
trackSkips.setChecked(Settings.SB_TRACK_SKIP_COUNT.get());
|
||||
trackSkips.setEnabled(enabled);
|
||||
|
||||
showTimeWithoutSegments.setChecked(Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get());
|
||||
showTimeWithoutSegments.setEnabled(enabled);
|
||||
|
||||
newSegmentStep.setText((Settings.SB_CREATE_NEW_SEGMENT_STEP.get()).toString());
|
||||
newSegmentStep.setEnabled(enabled);
|
||||
|
||||
minSegmentDuration.setText((Settings.SB_SEGMENT_MIN_DURATION.get()).toString());
|
||||
minSegmentDuration.setEnabled(enabled);
|
||||
|
||||
privateUserId.setText(Settings.SB_PRIVATE_USER_ID.get());
|
||||
privateUserId.setEnabled(enabled);
|
||||
// Preferences are synced by AbstractPreferenceFragment since keys are set
|
||||
// and a Setting exist with the same key.
|
||||
|
||||
// If the user has a private user id, then include a subtext that mentions not to share it.
|
||||
String importExportSummary = SponsorBlockSettings.userHasSBPrivateId()
|
||||
@@ -160,11 +102,7 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
: str("revanced_sb_settings_ie_sum");
|
||||
importExport.setSummary(importExportSummary);
|
||||
|
||||
apiUrl.setEnabled(enabled);
|
||||
importExport.setEnabled(enabled);
|
||||
segmentCategory.setEnabled(enabled);
|
||||
|
||||
for (SegmentCategoryListPreference category : segmentCategories) {
|
||||
for (SegmentCategoryPreference category : segmentCategories) {
|
||||
category.updateUI();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
@@ -172,6 +110,50 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateUIDelayed() {
|
||||
// Must use a delay, so AbstractPreferenceFragment can
|
||||
// update the availability of the settings.
|
||||
Utils.runOnMainThreadDelayed(this::updateUI, 50);
|
||||
}
|
||||
|
||||
private void initializePreference(Preference preference, Setting<?> setting, String key) {
|
||||
initializePreference(preference, setting, key, true);
|
||||
}
|
||||
|
||||
private void initializePreference(Preference preference, Setting<?> setting,
|
||||
String key, boolean setDetailedSummary) {
|
||||
preference.setKey(setting.key);
|
||||
preference.setTitle(str(key));
|
||||
preference.setEnabled(setting.isAvailable());
|
||||
boolean shouldSetSummary = true;
|
||||
|
||||
if (preference instanceof SwitchPreference switchPref && setting instanceof BooleanSetting boolSetting) {
|
||||
switchPref.setChecked(boolSetting.get());
|
||||
if (setDetailedSummary) {
|
||||
switchPref.setSummaryOn(str(key + "_sum_on"));
|
||||
switchPref.setSummaryOff(str(key + "_sum_off"));
|
||||
shouldSetSummary = false;
|
||||
}
|
||||
} else if (preference instanceof ResettableEditTextPreference resetPref) {
|
||||
resetPref.setText(setting.get().toString());
|
||||
} else if (preference instanceof EditTextPreference editPref) {
|
||||
editPref.setText(setting.get().toString());
|
||||
} else if (preference instanceof ListPreference listPref) {
|
||||
listPref.setEntries(Utils.getResourceStringArray(key + "_entries"));
|
||||
listPref.setEntryValues(Utils.getResourceStringArray(key + "_entry_values"));
|
||||
listPref.setValue(setting.get().toString());
|
||||
|
||||
if (preference instanceof CustomDialogListPreference dialogPref) {
|
||||
// Sets a static summary without overwriting it.
|
||||
dialogPref.setStaticSummary(str(key + "_sum"));
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSetSummary) {
|
||||
preference.setSummary(str(key + "_sum"));
|
||||
}
|
||||
}
|
||||
|
||||
protected void onAttachedToActivity() {
|
||||
try {
|
||||
super.onAttachedToActivity();
|
||||
@@ -183,20 +165,19 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
preferencesInitialized = true;
|
||||
|
||||
Logger.printDebug(() -> "Creating settings preferences");
|
||||
Context context = getContext();
|
||||
SponsorBlockSettings.initialize();
|
||||
|
||||
sbEnabled = new SwitchPreference(context);
|
||||
sbEnabled.setTitle(str("revanced_sb_enable_sb"));
|
||||
sbEnabled.setSummary(str("revanced_sb_enable_sb_sum"));
|
||||
SwitchPreference sbEnabled = new SwitchPreference(context);
|
||||
initializePreference(sbEnabled, Settings.SB_ENABLED,
|
||||
"revanced_sb_enable_sb", false);
|
||||
addPreference(sbEnabled);
|
||||
sbEnabled.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_ENABLED.save((Boolean) newValue);
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -204,109 +185,98 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
appearanceCategory.setTitle(str("revanced_sb_appearance_category"));
|
||||
addPreference(appearanceCategory);
|
||||
|
||||
votingEnabled = new SwitchPreference(context);
|
||||
votingEnabled.setTitle(str("revanced_sb_enable_voting"));
|
||||
votingEnabled.setSummaryOn(str("revanced_sb_enable_voting_sum_on"));
|
||||
votingEnabled.setSummaryOff(str("revanced_sb_enable_voting_sum_off"));
|
||||
SwitchPreference votingEnabled = new SwitchPreference(context);
|
||||
initializePreference(votingEnabled, Settings.SB_VOTING_BUTTON,
|
||||
"revanced_sb_enable_voting");
|
||||
votingEnabled.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_VOTING_BUTTON.save((Boolean) newValue);
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(votingEnabled);
|
||||
|
||||
compactSkipButton = new SwitchPreference(context);
|
||||
compactSkipButton.setTitle(str("revanced_sb_enable_compact_skip_button"));
|
||||
compactSkipButton.setSummaryOn(str("revanced_sb_enable_compact_skip_button_sum_on"));
|
||||
compactSkipButton.setSummaryOff(str("revanced_sb_enable_compact_skip_button_sum_off"));
|
||||
SwitchPreference compactSkipButton = new SwitchPreference(context);
|
||||
initializePreference(compactSkipButton, Settings.SB_COMPACT_SKIP_BUTTON,
|
||||
"revanced_sb_enable_compact_skip_button");
|
||||
compactSkipButton.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_COMPACT_SKIP_BUTTON.save((Boolean) newValue);
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(compactSkipButton);
|
||||
|
||||
autoHideSkipSegmentButton = new SwitchPreference(context);
|
||||
autoHideSkipSegmentButton.setTitle(str("revanced_sb_enable_auto_hide_skip_segment_button"));
|
||||
autoHideSkipSegmentButton.setSummaryOn(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_on"));
|
||||
autoHideSkipSegmentButton.setSummaryOff(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_off"));
|
||||
SwitchPreference autoHideSkipSegmentButton = new SwitchPreference(context);
|
||||
initializePreference(autoHideSkipSegmentButton, Settings.SB_AUTO_HIDE_SKIP_BUTTON,
|
||||
"revanced_sb_enable_auto_hide_skip_segment_button");
|
||||
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(autoHideSkipSegmentButton);
|
||||
|
||||
String[] durationEntries = Utils.getResourceStringArray("revanced_sb_duration_entries");
|
||||
String[] durationEntryValues = Utils.getResourceStringArray("revanced_sb_duration_entry_values");
|
||||
|
||||
autoHideSkipSegmentButtonDuration = new CustomDialogListPreference(context);
|
||||
autoHideSkipSegmentButtonDuration.setTitle(str("revanced_sb_auto_hide_skip_button_duration"));
|
||||
autoHideSkipSegmentButtonDuration.setSummary(str("revanced_sb_auto_hide_skip_button_duration_sum"));
|
||||
autoHideSkipSegmentButtonDuration.setEntries(durationEntries);
|
||||
autoHideSkipSegmentButtonDuration.setEntryValues(durationEntryValues);
|
||||
CustomDialogListPreference autoHideSkipSegmentButtonDuration = new CustomDialogListPreference(context);
|
||||
initializePreference(autoHideSkipSegmentButtonDuration, Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION,
|
||||
"revanced_sb_auto_hide_skip_button_duration");
|
||||
autoHideSkipSegmentButtonDuration.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION.save(
|
||||
SponsorBlockDuration.valueOf((String) newValue)
|
||||
);
|
||||
updateUI();
|
||||
SponsorBlockDuration newDuration = SponsorBlockDuration.valueOf((String) newValue);
|
||||
Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION.save(newDuration);
|
||||
((CustomDialogListPreference) preference1).setValue(newDuration.name());
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(autoHideSkipSegmentButtonDuration);
|
||||
|
||||
showSkipToast = new SwitchPreference(context);
|
||||
showSkipToast.setTitle(str("revanced_sb_general_skiptoast"));
|
||||
showSkipToast.setSummaryOn(str("revanced_sb_general_skiptoast_sum_on"));
|
||||
showSkipToast.setSummaryOff(str("revanced_sb_general_skiptoast_sum_off"));
|
||||
SwitchPreference showSkipToast = new SwitchPreference(context);
|
||||
initializePreference(showSkipToast, Settings.SB_TOAST_ON_SKIP,
|
||||
"revanced_sb_general_skiptoast");
|
||||
showSkipToast.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_TOAST_ON_SKIP.save((Boolean) newValue);
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(showSkipToast);
|
||||
|
||||
showSkipToastDuration = new CustomDialogListPreference(context);
|
||||
showSkipToastDuration.setTitle(str("revanced_sb_toast_on_skip_duration"));
|
||||
showSkipToastDuration.setSummary(str("revanced_sb_toast_on_skip_duration_sum"));
|
||||
showSkipToastDuration.setEntries(durationEntries);
|
||||
showSkipToastDuration.setEntryValues(durationEntryValues);
|
||||
CustomDialogListPreference showSkipToastDuration = new CustomDialogListPreference(context);
|
||||
initializePreference(showSkipToastDuration, Settings.SB_TOAST_ON_SKIP_DURATION,
|
||||
"revanced_sb_toast_on_skip_duration");
|
||||
// Sets a static summary without overwriting it.
|
||||
showSkipToastDuration.setStaticSummary(str("revanced_sb_toast_on_skip_duration_sum"));
|
||||
showSkipToastDuration.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_TOAST_ON_SKIP_DURATION.save(
|
||||
SponsorBlockDuration.valueOf((String) newValue)
|
||||
);
|
||||
updateUI();
|
||||
SponsorBlockDuration newDuration = SponsorBlockDuration.valueOf((String) newValue);
|
||||
Settings.SB_TOAST_ON_SKIP_DURATION.save(newDuration);
|
||||
((CustomDialogListPreference) preference1).setValue(newDuration.name());
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(showSkipToastDuration);
|
||||
|
||||
showTimeWithoutSegments = new SwitchPreference(context);
|
||||
showTimeWithoutSegments.setTitle(str("revanced_sb_general_time_without"));
|
||||
showTimeWithoutSegments.setSummaryOn(str("revanced_sb_general_time_without_sum_on"));
|
||||
showTimeWithoutSegments.setSummaryOff(str("revanced_sb_general_time_without_sum_off"));
|
||||
SwitchPreference showTimeWithoutSegments = new SwitchPreference(context);
|
||||
initializePreference(showTimeWithoutSegments, Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS,
|
||||
"revanced_sb_general_time_without");
|
||||
showTimeWithoutSegments.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.save((Boolean) newValue);
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(showTimeWithoutSegments);
|
||||
|
||||
squareLayout = new SwitchPreference(context);
|
||||
squareLayout.setTitle(str("revanced_sb_square_layout"));
|
||||
squareLayout.setSummaryOn(str("revanced_sb_square_layout_sum_on"));
|
||||
squareLayout.setSummaryOff(str("revanced_sb_square_layout_sum_off"));
|
||||
SwitchPreference squareLayout = new SwitchPreference(context);
|
||||
initializePreference(squareLayout, Settings.SB_SQUARE_LAYOUT,
|
||||
"revanced_sb_square_layout");
|
||||
squareLayout.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_SQUARE_LAYOUT.save((Boolean) newValue);
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(squareLayout);
|
||||
|
||||
segmentCategory = new PreferenceCategory(context);
|
||||
PreferenceCategory segmentCategory = new PreferenceCategory(context);
|
||||
segmentCategory.setTitle(str("revanced_sb_diff_segments"));
|
||||
addPreference(segmentCategory);
|
||||
|
||||
for (SegmentCategory category : SegmentCategory.categoriesWithoutUnsubmitted()) {
|
||||
SegmentCategoryListPreference categoryPreference = new SegmentCategoryListPreference(context, category);
|
||||
SegmentCategoryPreference categoryPreference = new SegmentCategoryPreference(context, category);
|
||||
segmentCategories.add(categoryPreference);
|
||||
segmentCategory.addPreference(categoryPreference);
|
||||
}
|
||||
@@ -315,24 +285,23 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
createSegmentCategory.setTitle(str("revanced_sb_create_segment_category"));
|
||||
addPreference(createSegmentCategory);
|
||||
|
||||
addNewSegment = new SwitchPreference(context);
|
||||
addNewSegment.setTitle(str("revanced_sb_enable_create_segment"));
|
||||
addNewSegment.setSummaryOn(str("revanced_sb_enable_create_segment_sum_on"));
|
||||
addNewSegment.setSummaryOff(str("revanced_sb_enable_create_segment_sum_off"));
|
||||
SwitchPreference addNewSegment = new SwitchPreference(context);
|
||||
initializePreference(addNewSegment, Settings.SB_CREATE_NEW_SEGMENT,
|
||||
"revanced_sb_enable_create_segment");
|
||||
addNewSegment.setOnPreferenceChangeListener((preference1, o) -> {
|
||||
Boolean newValue = (Boolean) o;
|
||||
if (newValue && !Settings.SB_SEEN_GUIDELINES.get()) {
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||
preference1.getContext(),
|
||||
str("revanced_sb_guidelines_popup_title"), // Title.
|
||||
str("revanced_sb_guidelines_popup_content"), // Message.
|
||||
null, // No EditText.
|
||||
str("revanced_sb_guidelines_popup_open"), // OK button text.
|
||||
() -> openGuidelines(), // OK button action.
|
||||
null, // Cancel button action.
|
||||
str("revanced_sb_guidelines_popup_title"), // Title.
|
||||
str("revanced_sb_guidelines_popup_content"), // Message.
|
||||
null, // No EditText.
|
||||
str("revanced_sb_guidelines_popup_open"), // OK button text.
|
||||
this::openGuidelines, // OK button action.
|
||||
null, // Cancel button action.
|
||||
str("revanced_sb_guidelines_popup_already_read"), // Neutral button text.
|
||||
() -> {}, // Neutral button action (dismiss only).
|
||||
true // Dismiss dialog when onNeutralClick.
|
||||
() -> {}, // Neutral button action (dismiss only).
|
||||
true // Dismiss dialog when onNeutralClick.
|
||||
);
|
||||
|
||||
// Set dialog as non-cancelable.
|
||||
@@ -344,21 +313,21 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
dialogPair.first.show();
|
||||
}
|
||||
Settings.SB_CREATE_NEW_SEGMENT.save(newValue);
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
createSegmentCategory.addPreference(addNewSegment);
|
||||
|
||||
newSegmentStep = new ResettableEditTextPreference(context);
|
||||
newSegmentStep.setSetting(Settings.SB_CREATE_NEW_SEGMENT_STEP);
|
||||
newSegmentStep.setTitle(str("revanced_sb_general_adjusting"));
|
||||
newSegmentStep.setSummary(str("revanced_sb_general_adjusting_sum"));
|
||||
ResettableEditTextPreference newSegmentStep = new ResettableEditTextPreference(context);
|
||||
initializePreference(newSegmentStep, Settings.SB_CREATE_NEW_SEGMENT_STEP,
|
||||
"revanced_sb_general_adjusting");
|
||||
newSegmentStep.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
newSegmentStep.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
try {
|
||||
final int newAdjustmentValue = Integer.parseInt(newValue.toString());
|
||||
if (newAdjustmentValue != 0) {
|
||||
Settings.SB_CREATE_NEW_SEGMENT_STEP.save(newAdjustmentValue);
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
@@ -366,7 +335,7 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
}
|
||||
|
||||
Utils.showToastLong(str("revanced_sb_general_adjusting_invalid"));
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return false;
|
||||
});
|
||||
createSegmentCategory.addPreference(newSegmentStep);
|
||||
@@ -384,49 +353,47 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
generalCategory.setTitle(str("revanced_sb_general"));
|
||||
addPreference(generalCategory);
|
||||
|
||||
toastOnConnectionError = new SwitchPreference(context);
|
||||
toastOnConnectionError.setTitle(str("revanced_sb_toast_on_connection_error_title"));
|
||||
toastOnConnectionError.setSummaryOn(str("revanced_sb_toast_on_connection_error_summary_on"));
|
||||
toastOnConnectionError.setSummaryOff(str("revanced_sb_toast_on_connection_error_summary_off"));
|
||||
SwitchPreference toastOnConnectionError = new SwitchPreference(context);
|
||||
initializePreference(toastOnConnectionError, Settings.SB_TOAST_ON_CONNECTION_ERROR,
|
||||
"revanced_sb_toast_on_connection_error");
|
||||
toastOnConnectionError.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_TOAST_ON_CONNECTION_ERROR.save((Boolean) newValue);
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
generalCategory.addPreference(toastOnConnectionError);
|
||||
|
||||
trackSkips = new SwitchPreference(context);
|
||||
trackSkips.setTitle(str("revanced_sb_general_skipcount"));
|
||||
trackSkips.setSummaryOn(str("revanced_sb_general_skipcount_sum_on"));
|
||||
trackSkips.setSummaryOff(str("revanced_sb_general_skipcount_sum_off"));
|
||||
SwitchPreference trackSkips = new SwitchPreference(context);
|
||||
initializePreference(trackSkips, Settings.SB_TRACK_SKIP_COUNT,
|
||||
"revanced_sb_general_skipcount");
|
||||
trackSkips.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_TRACK_SKIP_COUNT.save((Boolean) newValue);
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
generalCategory.addPreference(trackSkips);
|
||||
|
||||
minSegmentDuration = new ResettableEditTextPreference(context);
|
||||
minSegmentDuration.setSetting(Settings.SB_SEGMENT_MIN_DURATION);
|
||||
minSegmentDuration.setTitle(str("revanced_sb_general_min_duration"));
|
||||
minSegmentDuration.setSummary(str("revanced_sb_general_min_duration_sum"));
|
||||
ResettableEditTextPreference minSegmentDuration = new ResettableEditTextPreference(context);
|
||||
initializePreference(minSegmentDuration, Settings.SB_SEGMENT_MIN_DURATION,
|
||||
"revanced_sb_general_min_duration");
|
||||
minSegmentDuration.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
minSegmentDuration.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
try {
|
||||
Float minTimeDuration = Float.valueOf(newValue.toString());
|
||||
Settings.SB_SEGMENT_MIN_DURATION.save(minTimeDuration);
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
} catch (NumberFormatException ex) {
|
||||
Logger.printInfo(() -> "Invalid minimum segment duration", ex);
|
||||
}
|
||||
|
||||
Utils.showToastLong(str("revanced_sb_general_min_duration_invalid"));
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return false;
|
||||
});
|
||||
generalCategory.addPreference(minSegmentDuration);
|
||||
|
||||
privateUserId = new EditTextPreference(context) {
|
||||
EditTextPreference privateUserId = new EditTextPreference(context) {
|
||||
@Override
|
||||
protected void showDialog(Bundle state) {
|
||||
try {
|
||||
@@ -439,7 +406,7 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
editText.setSelection(initialValue.length()); // Move cursor to end.
|
||||
|
||||
// Create custom dialog.
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||
context,
|
||||
getTitle() != null ? getTitle().toString() : "", // Title.
|
||||
null, // Message is replaced by EditText.
|
||||
@@ -475,31 +442,32 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
}
|
||||
}
|
||||
};
|
||||
privateUserId.setTitle(str("revanced_sb_general_uuid"));
|
||||
privateUserId.setSummary(str("revanced_sb_general_uuid_sum"));
|
||||
initializePreference(privateUserId, Settings.SB_PRIVATE_USER_ID,
|
||||
"revanced_sb_general_uuid");
|
||||
privateUserId.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
String newUUID = newValue.toString();
|
||||
if (!SponsorBlockSettings.isValidSBUserId(newUUID)) {
|
||||
Utils.showToastLong(str("revanced_sb_general_uuid_invalid"));
|
||||
updateUIDelayed();
|
||||
return false;
|
||||
}
|
||||
|
||||
Settings.SB_PRIVATE_USER_ID.save(newUUID);
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
generalCategory.addPreference(privateUserId);
|
||||
|
||||
apiUrl = new Preference(context);
|
||||
apiUrl.setTitle(str("revanced_sb_general_api_url"));
|
||||
apiUrl.setSummary(Html.fromHtml(str("revanced_sb_general_api_url_sum")));
|
||||
Preference apiUrl = new Preference(context);
|
||||
initializePreference(apiUrl, Settings.SB_API_URL,
|
||||
"revanced_sb_general_api_url");
|
||||
apiUrl.setOnPreferenceClickListener(preference1 -> {
|
||||
EditText editText = new EditText(context);
|
||||
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
|
||||
editText.setText(Settings.SB_API_URL.get());
|
||||
|
||||
// Create a custom dialog.
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||
context,
|
||||
str("revanced_sb_general_api_url"), // Title.
|
||||
null, // No message, EditText replaces it.
|
||||
@@ -538,8 +506,11 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
Context context = getContext();
|
||||
EditText editText = getEditText();
|
||||
|
||||
editText.setInputType(editText.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
editText.setTextSize(TypedValue.COMPLEX_UNIT_PT, 7); // Use a smaller font to reduce text wrap.
|
||||
|
||||
// Create a custom dialog.
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||
context,
|
||||
str("revanced_sb_settings_ie"), // Title.
|
||||
null, // No message, EditText replaces it.
|
||||
@@ -588,7 +559,7 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
});
|
||||
importExport.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
SponsorBlockSettings.importDesktopSettings((String) newValue);
|
||||
updateUI();
|
||||
updateUIDelayed();
|
||||
return true;
|
||||
});
|
||||
generalCategory.addPreference(importExport);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.revanced.extension.youtube.sponsorblock.ui;
|
||||
|
||||
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||
import static android.text.Html.fromHtml;
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
|
||||
@@ -7,7 +8,6 @@ import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.util.AttributeSet;
|
||||
@@ -19,6 +19,7 @@ import androidx.annotation.Nullable;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.preference.ResettableEditTextPreference;
|
||||
import app.revanced.extension.shared.ui.CustomDialog;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
|
||||
import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils;
|
||||
@@ -27,7 +28,6 @@ import app.revanced.extension.youtube.sponsorblock.requests.SBRequester;
|
||||
|
||||
/**
|
||||
* User skip stats.
|
||||
*
|
||||
* None of the preferences here show up in search results because
|
||||
* a category cannot be added to another category for the search results.
|
||||
* Additionally the stats must load remotely on a background thread which means the
|
||||
@@ -48,6 +48,7 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToActivity() {
|
||||
try {
|
||||
super.onAttachedToActivity();
|
||||
@@ -97,8 +98,8 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
|
||||
if (stats.totalSegmentCountIncludingIgnored > 0) {
|
||||
// If user has not created any segments, there's no reason to set a username.
|
||||
String userName = stats.userName;
|
||||
EditTextPreference preference = new ResettableEditTextPreference(context);
|
||||
preference.setTitle(fromHtml(str("revanced_sb_stats_username", userName)));
|
||||
ResettableEditTextPreference preference = new ResettableEditTextPreference(context);
|
||||
preference.setTitle(fromHtml(str("revanced_sb_stats_username", userName), FROM_HTML_MODE_COMPACT));
|
||||
preference.setSummary(str("revanced_sb_stats_username_change"));
|
||||
preference.setText(userName);
|
||||
preference.setOnPreferenceChangeListener((preference1, value) -> {
|
||||
@@ -107,7 +108,7 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
|
||||
String errorMessage = SBRequester.setUsername(newUserName);
|
||||
Utils.runOnMainThread(() -> {
|
||||
if (errorMessage == null) {
|
||||
preference.setTitle(fromHtml(str("revanced_sb_stats_username", newUserName)));
|
||||
preference.setTitle(fromHtml(str("revanced_sb_stats_username", newUserName), FROM_HTML_MODE_COMPACT));
|
||||
preference.setText(newUserName);
|
||||
Utils.showToastLong(str("revanced_sb_stats_username_changed"));
|
||||
} else {
|
||||
@@ -118,6 +119,7 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
|
||||
});
|
||||
return true;
|
||||
});
|
||||
preference.setEnabled(Settings.SB_PRIVATE_USER_ID.isAvailable()); // Sync with private user ID setting.
|
||||
addPreference(preference);
|
||||
}
|
||||
|
||||
@@ -125,7 +127,7 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
|
||||
// Number of segment submissions (does not include ignored segments).
|
||||
Preference preference = new Preference(context);
|
||||
String formatted = SponsorBlockUtils.getNumberOfSkipsString(stats.segmentCount);
|
||||
preference.setTitle(fromHtml(str("revanced_sb_stats_submissions", formatted)));
|
||||
preference.setTitle(fromHtml(str("revanced_sb_stats_submissions", formatted), FROM_HTML_MODE_COMPACT));
|
||||
preference.setSummary(str("revanced_sb_stats_submissions_sum"));
|
||||
if (stats.totalSegmentCountIncludingIgnored == 0) {
|
||||
preference.setSelectable(false);
|
||||
@@ -137,6 +139,7 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
preference.setEnabled(Settings.SB_ENABLED.isAvailable());
|
||||
addPreference(preference);
|
||||
}
|
||||
|
||||
@@ -144,8 +147,9 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
|
||||
// "user reputation". Usually not useful since it appears most users have zero reputation.
|
||||
// But if there is a reputation then show it here.
|
||||
Preference preference = new Preference(context);
|
||||
preference.setTitle(fromHtml(str("revanced_sb_stats_reputation", stats.reputation)));
|
||||
preference.setTitle(fromHtml(str("revanced_sb_stats_reputation", stats.reputation), FROM_HTML_MODE_COMPACT));
|
||||
preference.setSelectable(false);
|
||||
preference.setEnabled(Settings.SB_ENABLED.isAvailable());
|
||||
if (stats.reputation != 0) {
|
||||
addPreference(preference);
|
||||
}
|
||||
@@ -166,14 +170,15 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
|
||||
stats_saved_sum = str("revanced_sb_stats_saved_sum",
|
||||
SponsorBlockUtils.getTimeSavedString((long) (60 * stats.minutesSaved)));
|
||||
}
|
||||
preference.setTitle(fromHtml(stats_saved));
|
||||
preference.setSummary(fromHtml(stats_saved_sum));
|
||||
preference.setTitle(fromHtml(stats_saved, FROM_HTML_MODE_COMPACT));
|
||||
preference.setSummary(fromHtml(stats_saved_sum, FROM_HTML_MODE_COMPACT));
|
||||
preference.setOnPreferenceClickListener(preference1 -> {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse("https://sponsor.ajay.app/stats/"));
|
||||
preference1.getContext().startActivity(i);
|
||||
return false;
|
||||
});
|
||||
preference.setEnabled(Settings.SB_ENABLED.isAvailable());
|
||||
addPreference(preference);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
@@ -187,16 +192,16 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
|
||||
Runnable updateStatsSelfSaved = () -> {
|
||||
String formatted = SponsorBlockUtils.getNumberOfSkipsString(
|
||||
Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.get());
|
||||
preference.setTitle(fromHtml(str("revanced_sb_stats_self_saved", formatted)));
|
||||
preference.setTitle(fromHtml(str("revanced_sb_stats_self_saved", formatted), FROM_HTML_MODE_COMPACT));
|
||||
|
||||
String formattedSaved = SponsorBlockUtils.getTimeSavedString(
|
||||
Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.get() / 1000);
|
||||
preference.setSummary(fromHtml(str("revanced_sb_stats_self_saved_sum", formattedSaved)));
|
||||
preference.setSummary(fromHtml(str("revanced_sb_stats_self_saved_sum", formattedSaved), FROM_HTML_MODE_COMPACT));
|
||||
};
|
||||
updateStatsSelfSaved.run();
|
||||
|
||||
preference.setOnPreferenceClickListener(preference1 -> {
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||
preference.getContext(),
|
||||
str("revanced_sb_stats_self_saved_reset_title"), // Title.
|
||||
null, // No message.
|
||||
@@ -219,6 +224,7 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
|
||||
return true;
|
||||
});
|
||||
|
||||
preference.setEnabled(Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.isAvailable());
|
||||
addPreference(preference);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package app.revanced.extension.youtube.sponsorblock.ui;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -63,7 +63,8 @@ public class SponsorBlockViewController {
|
||||
Context context = Utils.getContext();
|
||||
RelativeLayout layout = new RelativeLayout(context);
|
||||
layout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,RelativeLayout.LayoutParams.MATCH_PARENT));
|
||||
LayoutInflater.from(context).inflate(getResourceIdentifier("revanced_sb_inline_sponsor_overlay", "layout"), layout);
|
||||
LayoutInflater.from(context).inflate(getResourceIdentifierOrThrow(
|
||||
"revanced_sb_inline_sponsor_overlay", "layout"), layout);
|
||||
inlineSponsorOverlayRef = new WeakReference<>(layout);
|
||||
|
||||
viewGroup.addView(layout);
|
||||
@@ -82,14 +83,14 @@ public class SponsorBlockViewController {
|
||||
});
|
||||
youtubeOverlaysLayoutRef = new WeakReference<>(viewGroup);
|
||||
|
||||
skipHighlightButtonRef = new WeakReference<>(Objects.requireNonNull(
|
||||
layout.findViewById(getResourceIdentifier("revanced_sb_skip_highlight_button", "id"))));
|
||||
skipHighlightButtonRef = new WeakReference<>(layout.findViewById(getResourceIdentifierOrThrow(
|
||||
"revanced_sb_skip_highlight_button", "id")));
|
||||
|
||||
skipSponsorButtonRef = new WeakReference<>(Objects.requireNonNull(
|
||||
layout.findViewById(getResourceIdentifier("revanced_sb_skip_sponsor_button", "id"))));
|
||||
skipSponsorButtonRef = new WeakReference<>(layout.findViewById(getResourceIdentifierOrThrow(
|
||||
"revanced_sb_skip_sponsor_button", "id")));
|
||||
|
||||
NewSegmentLayout newSegmentLayout = Objects.requireNonNull(
|
||||
layout.findViewById(getResourceIdentifier("revanced_sb_new_segment_view", "id")));
|
||||
NewSegmentLayout newSegmentLayout = layout.findViewById(getResourceIdentifierOrThrow(
|
||||
"revanced_sb_new_segment_view", "id"));
|
||||
newSegmentLayoutRef = new WeakReference<>(newSegmentLayout);
|
||||
newSegmentLayout.updateLayout();
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ class SwipeControlsConfigurationProvider {
|
||||
val overlayBackgroundOpacity: Int by lazy {
|
||||
var opacity = Settings.SWIPE_OVERLAY_OPACITY.get()
|
||||
|
||||
if (opacity < 0 || opacity > 100) {
|
||||
if (opacity !in 0..100) {
|
||||
Utils.showToastLong(str("revanced_swipe_overlay_background_opacity_invalid_toast"))
|
||||
opacity = Settings.SWIPE_OVERLAY_OPACITY.resetToDefault()
|
||||
}
|
||||
@@ -115,14 +115,13 @@ class SwipeControlsConfigurationProvider {
|
||||
* Resets to default and shows a toast if the color string is invalid or empty.
|
||||
*/
|
||||
val overlayVolumeProgressColor: Int by lazy {
|
||||
// Use lazy to avoid repeat parsing. Changing color requires app restart.
|
||||
getSettingColor(Settings.SWIPE_OVERLAY_VOLUME_COLOR)
|
||||
}
|
||||
|
||||
private fun getSettingColor(setting: StringSetting): Int {
|
||||
try {
|
||||
//noinspection UseKtx
|
||||
val color = Color.parseColor(setting.get())
|
||||
return (0xBF000000.toInt() or (color and 0x00FFFFFF))
|
||||
return try {
|
||||
Color.parseColor(setting.get())
|
||||
} catch (ex: IllegalArgumentException) {
|
||||
// This code should never be reached.
|
||||
// Color picker rejects and will not save bad colors to a setting.
|
||||
@@ -151,7 +150,7 @@ class SwipeControlsConfigurationProvider {
|
||||
*/
|
||||
val overlayTextSize: Int by lazy {
|
||||
val size = Settings.SWIPE_OVERLAY_TEXT_SIZE.get()
|
||||
if (size < 1 || size > 30) {
|
||||
if (size !in 1..30) {
|
||||
Utils.showToastLong(str("revanced_swipe_text_overlay_size_invalid_toast"))
|
||||
return@lazy Settings.SWIPE_OVERLAY_TEXT_SIZE.resetToDefault()
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class PlayerControlButton {
|
||||
boolean buttonEnabled();
|
||||
}
|
||||
|
||||
private static final int fadeInDuration = Utils.getResourceInteger("fade_duration_fast");
|
||||
public static final int fadeInDuration = Utils.getResourceInteger("fade_duration_fast");
|
||||
private static final int fadeOutDuration = Utils.getResourceInteger("fade_duration_scheduled");
|
||||
|
||||
private final WeakReference<View> containerRef;
|
||||
@@ -249,4 +249,13 @@ public class PlayerControlButton {
|
||||
textOverlay.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate dialog background color depending on the current theme.
|
||||
*/
|
||||
public static int getDialogBackgroundColor() {
|
||||
return Utils.getResourceColor(
|
||||
Utils.isDarkModeEnabled() ? "yt_black1" : "yt_white1"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,38 +2,30 @@ package app.revanced.extension.youtube.videoplayer;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.shared.settings.preference.CustomDialogListPreference.*;
|
||||
import static app.revanced.extension.youtube.patches.VideoInformation.AUTOMATIC_VIDEO_QUALITY_VALUE;
|
||||
import static app.revanced.extension.youtube.patches.VideoInformation.VIDEO_QUALITY_PREMIUM_NAME;
|
||||
import static app.revanced.extension.youtube.videoplayer.PlayerControlButton.fadeInDuration;
|
||||
import static app.revanced.extension.youtube.videoplayer.PlayerControlButton.getDialogBackgroundColor;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.RoundRectShape;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.*;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.shared.ui.SheetBottomDialog;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
import com.google.android.libraries.youtube.innertube.model.media.VideoQuality;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -43,6 +35,7 @@ import app.revanced.extension.youtube.patches.VideoInformation;
|
||||
import app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class VideoQualityDialogButton {
|
||||
@@ -60,6 +53,11 @@ public class VideoQualityDialogButton {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Weak reference to the currently open dialog.
|
||||
*/
|
||||
private static WeakReference<SheetBottomDialog.SlideDialog> currentDialog;
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@@ -216,41 +214,14 @@ public class VideoQualityDialogButton {
|
||||
}
|
||||
}
|
||||
|
||||
Dialog dialog = new Dialog(context);
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
dialog.setCanceledOnTouchOutside(true);
|
||||
dialog.setCancelable(true);
|
||||
// Preset size constants.
|
||||
final int dip8 = dipToPixels(8);
|
||||
final int dip12 = dipToPixels(12);
|
||||
final int dip16 = dipToPixels(16);
|
||||
|
||||
final int dip4 = dipToPixels(4); // Height for handle bar.
|
||||
final int dip5 = dipToPixels(5); // Padding for mainLayout.
|
||||
final int dip6 = dipToPixels(6); // Bottom margin.
|
||||
final int dip8 = dipToPixels(8); // Side padding.
|
||||
final int dip16 = dipToPixels(16); // Left padding for ListView.
|
||||
final int dip20 = dipToPixels(20); // Margin below handle.
|
||||
final int dip40 = dipToPixels(40); // Width for handle bar.
|
||||
|
||||
LinearLayout mainLayout = new LinearLayout(context);
|
||||
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
mainLayout.setPadding(dip5, dip8, dip5, dip8);
|
||||
|
||||
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(12), null, null));
|
||||
background.getPaint().setColor(Utils.getDialogBackgroundColor());
|
||||
mainLayout.setBackground(background);
|
||||
|
||||
View handleBar = new View(context);
|
||||
ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(4), null, null));
|
||||
final int baseColor = Utils.getDialogBackgroundColor();
|
||||
final int adjustedHandleBarBackgroundColor = Utils.adjustColorBrightness(
|
||||
baseColor, 0.9f, 1.25f);
|
||||
handleBackground.getPaint().setColor(adjustedHandleBarBackgroundColor);
|
||||
handleBar.setBackground(handleBackground);
|
||||
LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(dip40, dip4);
|
||||
handleParams.gravity = Gravity.CENTER_HORIZONTAL;
|
||||
handleParams.setMargins(0, 0, 0, dip20);
|
||||
handleBar.setLayoutParams(handleParams);
|
||||
mainLayout.addView(handleBar);
|
||||
// Create main layout.
|
||||
SheetBottomDialog.DraggableLinearLayout mainLayout =
|
||||
SheetBottomDialog.createMainLayout(context, getDialogBackgroundColor());
|
||||
|
||||
// Create SpannableStringBuilder for formatted text.
|
||||
SpannableStringBuilder spannableTitle = new SpannableStringBuilder();
|
||||
@@ -298,16 +269,20 @@ public class VideoQualityDialogButton {
|
||||
LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
titleParams.setMargins(dip8, 0, 0, dip20);
|
||||
titleParams.setMargins(dip12, dip16, 0, dip16);
|
||||
titleView.setLayoutParams(titleParams);
|
||||
mainLayout.addView(titleView);
|
||||
|
||||
// Create ListView for quality selection.
|
||||
ListView listView = new ListView(context);
|
||||
CustomQualityAdapter adapter = new CustomQualityAdapter(context, qualityLabels);
|
||||
adapter.setSelectedPosition(listViewSelectedIndex);
|
||||
listView.setAdapter(adapter);
|
||||
listView.setDivider(null);
|
||||
listView.setPadding(dip16, 0, 0, 0);
|
||||
|
||||
// Create dialog.
|
||||
SheetBottomDialog.SlideDialog dialog = SheetBottomDialog.createSlideDialog(context, mainLayout, fadeInDuration);
|
||||
currentDialog = new WeakReference<>(dialog);
|
||||
|
||||
listView.setOnItemClickListener((parent, view, which, id) -> {
|
||||
try {
|
||||
@@ -322,112 +297,41 @@ public class VideoQualityDialogButton {
|
||||
}
|
||||
});
|
||||
|
||||
LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
listViewParams.setMargins(0, 0, 0, dip5);
|
||||
listView.setLayoutParams(listViewParams);
|
||||
mainLayout.addView(listView);
|
||||
|
||||
LinearLayout wrapperLayout = new LinearLayout(context);
|
||||
wrapperLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
wrapperLayout.setPadding(dip8, 0, dip8, 0);
|
||||
wrapperLayout.addView(mainLayout);
|
||||
dialog.setContentView(wrapperLayout);
|
||||
|
||||
Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
WindowManager.LayoutParams params = window.getAttributes();
|
||||
params.gravity = Gravity.BOTTOM;
|
||||
params.y = dip6;
|
||||
int portraitWidth = context.getResources().getDisplayMetrics().widthPixels;
|
||||
if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
portraitWidth = Math.min(
|
||||
portraitWidth,
|
||||
context.getResources().getDisplayMetrics().heightPixels);
|
||||
// Limit height in landscape mode.
|
||||
params.height = Utils.percentageHeightToPixels(80);
|
||||
} else {
|
||||
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
}
|
||||
params.width = portraitWidth;
|
||||
window.setAttributes(params);
|
||||
window.setBackgroundDrawable(null);
|
||||
}
|
||||
|
||||
final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast");
|
||||
Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom");
|
||||
slideInABottomAnimation.setDuration(fadeDurationFast);
|
||||
mainLayout.startAnimation(slideInABottomAnimation);
|
||||
|
||||
// noinspection ClickableViewAccessibility
|
||||
mainLayout.setOnTouchListener(new View.OnTouchListener() {
|
||||
final float dismissThreshold = dipToPixels(100);
|
||||
float touchY;
|
||||
float translationY;
|
||||
|
||||
// Create observer for PlayerType changes.
|
||||
Function1<PlayerType, Unit> playerTypeObserver = new Function1<>() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
touchY = event.getRawY();
|
||||
translationY = mainLayout.getTranslationY();
|
||||
return true;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
final float deltaY = event.getRawY() - touchY;
|
||||
if (deltaY >= 0) {
|
||||
mainLayout.setTranslationY(translationY + deltaY);
|
||||
}
|
||||
return true;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (mainLayout.getTranslationY() > dismissThreshold) {
|
||||
//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 {
|
||||
TranslateAnimation slideBack = new TranslateAnimation(
|
||||
0, 0, mainLayout.getTranslationY(), 0);
|
||||
slideBack.setDuration(fadeDurationFast);
|
||||
mainLayout.startAnimation(slideBack);
|
||||
mainLayout.setTranslationY(0);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
public Unit invoke(PlayerType type) {
|
||||
SheetBottomDialog.SlideDialog current = currentDialog.get();
|
||||
if (current == null || !current.isShowing()) {
|
||||
// Should never happen.
|
||||
PlayerType.getOnChange().removeObserver(this);
|
||||
Logger.printException(() -> "Removing player type listener as dialog is null or closed");
|
||||
} else if (type == PlayerType.WATCH_WHILE_PICTURE_IN_PICTURE) {
|
||||
current.dismiss();
|
||||
Logger.printDebug(() -> "Playback speed dialog dismissed due to PiP mode");
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
};
|
||||
|
||||
// Add observer to dismiss dialog when entering PiP mode.
|
||||
PlayerType.getOnChange().addObserver(playerTypeObserver);
|
||||
|
||||
// Remove observer when dialog is dismissed.
|
||||
dialog.setOnDismissListener(d -> {
|
||||
PlayerType.getOnChange().removeObserver(playerTypeObserver);
|
||||
Logger.printDebug(() -> "PlayerType observer removed on dialog dismiss");
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
dialog.show(); // Show the dialog.
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "showVideoQualityDialog failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CustomQualityAdapter extends ArrayAdapter<String> {
|
||||
private static final int CUSTOM_LIST_ITEM_CHECKED_ID = Utils.getResourceIdentifier(
|
||||
"revanced_custom_list_item_checked", "layout");
|
||||
private static final int CHECK_ICON_ID = Utils.getResourceIdentifier(
|
||||
"revanced_check_icon", "id");
|
||||
private static final int CHECK_ICON_PLACEHOLDER_ID = Utils.getResourceIdentifier(
|
||||
"revanced_check_icon_placeholder", "id");
|
||||
private static final int ITEM_TEXT_ID = Utils.getResourceIdentifier(
|
||||
"revanced_item_text", "id");
|
||||
|
||||
private int selectedPosition = -1;
|
||||
|
||||
@@ -447,14 +351,14 @@ public class VideoQualityDialogButton {
|
||||
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(getContext()).inflate(
|
||||
CUSTOM_LIST_ITEM_CHECKED_ID,
|
||||
LAYOUT_REVANCED_CUSTOM_LIST_ITEM_CHECKED,
|
||||
parent,
|
||||
false
|
||||
);
|
||||
viewHolder = new ViewHolder();
|
||||
viewHolder.checkIcon = convertView.findViewById(CHECK_ICON_ID);
|
||||
viewHolder.placeholder = convertView.findViewById(CHECK_ICON_PLACEHOLDER_ID);
|
||||
viewHolder.textView = convertView.findViewById(ITEM_TEXT_ID);
|
||||
viewHolder.checkIcon = convertView.findViewById(ID_REVANCED_CHECK_ICON);
|
||||
viewHolder.placeholder = convertView.findViewById(ID_REVANCED_CHECK_ICON_PLACEHOLDER);
|
||||
viewHolder.textView = convertView.findViewById(ID_REVANCED_ITEM_TEXT);
|
||||
convertView.setTag(viewHolder);
|
||||
} else {
|
||||
viewHolder = (ViewHolder) convertView.getTag();
|
||||
|
||||
Reference in New Issue
Block a user