diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButtonController.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButtonController.java index 4ec6c35b7..616bc2f06 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButtonController.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButtonController.java @@ -1,23 +1,25 @@ package app.revanced.extension.youtube.sponsorblock.ui; -import static app.revanced.extension.shared.Utils.getResourceIdentifier; - import android.view.View; import android.widget.ImageView; -import java.lang.ref.WeakReference; +import androidx.annotation.Nullable; + import java.util.Objects; -import app.revanced.extension.youtube.patches.VideoInformation; -import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; -import app.revanced.extension.youtube.videoplayer.PlayerControlButton; +import app.revanced.extension.youtube.patches.VideoInformation; +import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.youtube.videoplayer.PlayerControlTopButton; -// Edit: This should be a subclass of PlayerControlButton -public class CreateSegmentButtonController { - private static WeakReference buttonReference = new WeakReference<>(null); - private static boolean isShowing; +public class CreateSegmentButtonController extends PlayerControlTopButton { + @Nullable + private static CreateSegmentButtonController instance; + + public static void hideControls() { + if (instance != null) instance.hide(); + } /** * injection point @@ -27,10 +29,7 @@ public class CreateSegmentButtonController { Logger.printDebug(() -> "initializing new segment button"); ImageView imageView = Objects.requireNonNull(Utils.getChildViewByResourceName( youtubeControlsLayout, "revanced_sb_create_segment_button")); - imageView.setVisibility(View.GONE); - imageView.setOnClickListener(v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility()); - - buttonReference = new WeakReference<>(imageView); + instance = new CreateSegmentButtonController(imageView); } catch (Exception ex) { Logger.printException(() -> "initialize failure", ex); } @@ -40,72 +39,22 @@ public class CreateSegmentButtonController { * injection point */ public static void changeVisibilityImmediate(boolean visible) { - if (visible) { - // Fix button flickering, by pushing this call to the back of - // the main thread and letting other layout code run first. - Utils.runOnMainThread(() -> setVisibility(true, false)); - } else { - setVisibility(false, false); - } + if (instance != null) instance.setVisibilityImmediate(visible); } /** * injection point */ public static void changeVisibility(boolean visible, boolean animated) { - // Ignore this call, otherwise with full screen thumbnails the buttons are visible while seeking. - if (visible && !animated) return; - - setVisibility(visible, animated); + if (instance != null) instance.setVisibility(visible, animated); } - private static void setVisibility(boolean visible, boolean animated) { - try { - if (isShowing == visible) return; - isShowing = visible; - - ImageView iView = buttonReference.get(); - if (iView == null) return; - - if (visible) { - iView.clearAnimation(); - if (!shouldBeShown()) { - return; - } - if (animated) { - iView.startAnimation(PlayerControlButton.getButtonFadeIn()); - } - iView.setVisibility(View.VISIBLE); - return; - } - - if (iView.getVisibility() == View.VISIBLE) { - iView.clearAnimation(); - if (animated) { - iView.startAnimation(PlayerControlButton.getButtonFadeOut()); - } - iView.setVisibility(View.GONE); - } - } catch (Exception ex) { - Logger.printException(() -> "changeVisibility failure", ex); - } + private CreateSegmentButtonController(ImageView imageView) { + super(imageView, v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility()); } - private static boolean shouldBeShown() { + protected boolean shouldBeShown() { return Settings.SB_ENABLED.get() && Settings.SB_CREATE_NEW_SEGMENT.get() && !VideoInformation.isAtEndOfVideo(); } - - public static void hide() { - if (!isShowing) { - return; - } - Utils.verifyOnMainThread(); - View v = buttonReference.get(); - if (v == null) { - return; - } - v.setVisibility(View.GONE); - isShowing = false; - } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/SponsorBlockViewController.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/SponsorBlockViewController.java index 78c48a779..297fb3acc 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/SponsorBlockViewController.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/SponsorBlockViewController.java @@ -19,6 +19,7 @@ import app.revanced.extension.shared.Utils; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment; +import app.revanced.extension.youtube.videoplayer.PlayerControlTopButton; import kotlin.Unit; public class SponsorBlockViewController { @@ -238,8 +239,8 @@ public class SponsorBlockViewController { // but if buttons are showing when the end of the video is reached then they need // to be forcefully hidden if (!Settings.AUTO_REPEAT.get()) { - CreateSegmentButtonController.hide(); - VotingButtonController.hide(); + CreateSegmentButtonController.hideControls(); + VotingButtonController.hideControls(); } } catch (Exception ex) { Logger.printException(() -> "endOfVideoReached failure", ex); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/VotingButtonController.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/VotingButtonController.java index bad5f2484..0240d1c70 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/VotingButtonController.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/VotingButtonController.java @@ -1,25 +1,27 @@ package app.revanced.extension.youtube.sponsorblock.ui; -import static app.revanced.extension.shared.Utils.getResourceIdentifier; - import android.view.View; import android.widget.ImageView; -import java.lang.ref.WeakReference; +import androidx.annotation.Nullable; + import java.util.Objects; +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController; import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils; -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.youtube.videoplayer.PlayerControlButton; +import app.revanced.extension.youtube.videoplayer.PlayerControlTopButton; -// Edit: This should be a subclass of PlayerControlButton -public class VotingButtonController { - private static WeakReference buttonReference = new WeakReference<>(null); - private static boolean isShowing; +public class VotingButtonController extends PlayerControlTopButton { + @Nullable + private static VotingButtonController instance; + + public static void hideControls() { + if (instance != null) instance.hide(); + } /** * injection point @@ -29,10 +31,7 @@ public class VotingButtonController { Logger.printDebug(() -> "initializing voting button"); ImageView imageView = Objects.requireNonNull(Utils.getChildViewByResourceName( youtubeControlsLayout, "revanced_sb_voting_button")); - imageView.setVisibility(View.GONE); - imageView.setOnClickListener(v -> SponsorBlockUtils.onVotingClicked(v.getContext())); - - buttonReference = new WeakReference<>(imageView); + instance = new VotingButtonController(imageView); } catch (Exception ex) { Logger.printException(() -> "initialize failure", ex); } @@ -42,75 +41,22 @@ public class VotingButtonController { * injection point */ public static void changeVisibilityImmediate(boolean visible) { - if (visible) { - // Fix button flickering, by pushing this call to the back of - // the main thread and letting other layout code run first. - Utils.runOnMainThread(() -> setVisibility(true, false)); - } else { - setVisibility(false, false); - } + if (instance != null) instance.setVisibilityImmediate(visible); } /** * injection point */ public static void changeVisibility(boolean visible, boolean animated) { - // Ignore this call, otherwise with full screen thumbnails the buttons are visible while seeking. - if (visible && !animated) return; - - setVisibility(visible, animated); + if (instance != null) instance.setVisibility(visible, animated); } - /** - * injection point - */ - private static void setVisibility(boolean visible, boolean animated) { - try { - if (isShowing == visible) return; - isShowing = visible; - - ImageView iView = buttonReference.get(); - if (iView == null) return; - - if (visible) { - iView.clearAnimation(); - if (!shouldBeShown()) { - return; - } - if (animated) { - iView.startAnimation(PlayerControlButton.getButtonFadeIn()); - } - iView.setVisibility(View.VISIBLE); - return; - } - - if (iView.getVisibility() == View.VISIBLE) { - iView.clearAnimation(); - if (animated) { - iView.startAnimation(PlayerControlButton.getButtonFadeOut()); - } - iView.setVisibility(View.GONE); - } - } catch (Exception ex) { - Logger.printException(() -> "changeVisibility failure", ex); - } + private VotingButtonController(ImageView imageView) { + super(imageView, v -> SponsorBlockUtils.onVotingClicked(v.getContext())); } - private static boolean shouldBeShown() { + protected boolean shouldBeShown() { return Settings.SB_ENABLED.get() && Settings.SB_VOTING_BUTTON.get() && SegmentPlaybackController.videoHasSegments() && !VideoInformation.isAtEndOfVideo(); } - - public static void hide() { - if (!isShowing) { - return; - } - Utils.verifyOnMainThread(); - View v = buttonReference.get(); - if (v == null) { - return; - } - v.setVisibility(View.GONE); - isShowing = false; - } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/CopyVideoUrlButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/CopyVideoUrlButton.java index 5b877cdc6..62d6ef1f1 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/CopyVideoUrlButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/CopyVideoUrlButton.java @@ -10,7 +10,7 @@ import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.shared.Logger; @SuppressWarnings("unused") -public class CopyVideoUrlButton extends PlayerControlButton { +public class CopyVideoUrlButton extends PlayerControlBottomButton { @Nullable private static CopyVideoUrlButton instance; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/CopyVideoUrlTimestampButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/CopyVideoUrlTimestampButton.java index ebb3f518a..269431d75 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/CopyVideoUrlTimestampButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/CopyVideoUrlTimestampButton.java @@ -10,7 +10,7 @@ import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.shared.Logger; @SuppressWarnings("unused") -public class CopyVideoUrlTimestampButton extends PlayerControlButton { +public class CopyVideoUrlTimestampButton extends PlayerControlBottomButton { @Nullable private static CopyVideoUrlTimestampButton instance; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/ExternalDownloadButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/ExternalDownloadButton.java index bfd6d30e5..112df8086 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/ExternalDownloadButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/ExternalDownloadButton.java @@ -11,7 +11,7 @@ import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.settings.Settings; @SuppressWarnings("unused") -public class ExternalDownloadButton extends PlayerControlButton { +public class ExternalDownloadButton extends PlayerControlBottomButton { @Nullable private static ExternalDownloadButton instance; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlaybackSpeedDialogButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlaybackSpeedDialogButton.java index 2954eac15..b0ace1bec 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlaybackSpeedDialogButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlaybackSpeedDialogButton.java @@ -10,7 +10,7 @@ import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.shared.Logger; @SuppressWarnings("unused") -public class PlaybackSpeedDialogButton extends PlayerControlButton { +public class PlaybackSpeedDialogButton extends PlayerControlBottomButton { @Nullable private static PlaybackSpeedDialogButton instance; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlBottomButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlBottomButton.java new file mode 100644 index 000000000..abf7c5f88 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlBottomButton.java @@ -0,0 +1,103 @@ +package app.revanced.extension.youtube.videoplayer; + +import static app.revanced.extension.youtube.videoplayer.PlayerControlTopButton.fadeOutDuration; + +import android.transition.Fade; +import android.transition.TransitionManager; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.Nullable; + +import java.lang.ref.WeakReference; +import java.util.Objects; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.BooleanSetting; + +public abstract class PlayerControlBottomButton { + private final WeakReference buttonRef; + private final BooleanSetting setting; + private boolean isVisible; + + protected PlayerControlBottomButton(ViewGroup bottomControlsViewGroup, String imageViewButtonId, + BooleanSetting booleanSetting, View.OnClickListener onClickListener, + @Nullable View.OnLongClickListener longClickListener) { + Logger.printDebug(() -> "Initializing button: " + imageViewButtonId); + + ImageView imageView = Objects.requireNonNull(bottomControlsViewGroup.findViewById( + Utils.getResourceIdentifier(imageViewButtonId, "id") + )); + imageView.setVisibility(View.GONE); + + imageView.setOnClickListener(onClickListener); + if (longClickListener != null) { + imageView.setOnLongClickListener(longClickListener); + } + + setting = booleanSetting; + buttonRef = new WeakReference<>(imageView); + } + + protected void setVisibilityImmediate(boolean visible) { + private_setVisibility(visible, false); + } + + protected void setVisibility(boolean visible, boolean animated) { + // Ignore this call, otherwise with full screen thumbnails the buttons are visible while seeking. + if (visible && !animated) return; + + private_setVisibility(visible, animated); + } + + private void private_setVisibility(boolean visible, boolean animated) { + try { + // If the visibility state hasn't changed, return early. + if (isVisible == visible) return; + isVisible = visible; + + ImageView iView = buttonRef.get(); + if (iView == null) { + return; + } + + ViewGroup parent = (ViewGroup) iView.getParent(); + if (parent == null) { + return; + } + + // Apply transition if animation is enabled. + if (animated) { + Fade fade = visible + ? PlayerControlTopButton.fadeInTransition + : PlayerControlTopButton.fadeOutTransition; + TransitionManager.beginDelayedTransition(parent, fade); + } + + // If the view should be visible and the setting allows it. + if (visible && setting.get()) { + // Set the view to VISIBLE. + iView.setVisibility(View.VISIBLE); + } else if (iView.getVisibility() == View.VISIBLE) { + // First, set visibility to INVISIBLE for animation. + iView.setVisibility(View.INVISIBLE); + + if (animated) { + // Set the view to GONE after the fade animation ends. + Utils.runOnMainThreadDelayed(() -> { + if (!isVisible) { + iView.setVisibility(View.GONE); + } + }, fadeOutDuration); + } else { + // If no animation, immediately set the view to GONE. + iView.setVisibility(View.GONE); + } + } + } catch (Exception ex) { + Logger.printException(() -> "private_setVisibility failure", ex); + } + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java deleted file mode 100644 index 71e176c1f..000000000 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java +++ /dev/null @@ -1,117 +0,0 @@ -package app.revanced.extension.youtube.videoplayer; - -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.lang.ref.WeakReference; -import java.util.Objects; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.BooleanSetting; - -public abstract class PlayerControlButton { - private static final Animation fadeIn; - private static final Animation fadeOut; - private static final Animation fadeOutImmediate; - - private final WeakReference buttonRef; - protected final BooleanSetting setting; - protected boolean isVisible; - - static { - // TODO: check if these durations are correct. - fadeIn = Utils.getResourceAnimation("fade_in"); - fadeIn.setDuration(Utils.getResourceInteger("fade_duration_fast")); - - fadeOut = Utils.getResourceAnimation("fade_out"); - fadeOut.setDuration(Utils.getResourceInteger("fade_duration_scheduled")); - - fadeOutImmediate = Utils.getResourceAnimation("abc_fade_out"); - fadeOutImmediate.setDuration(Utils.getResourceInteger("fade_duration_fast")); - } - - @NonNull - public static Animation getButtonFadeIn() { - return fadeIn; - } - - @NonNull - public static Animation getButtonFadeOut() { - return fadeOut; - } - - @NonNull - public static Animation getButtonFadeOutImmediately() { - return fadeOutImmediate; - } - - public PlayerControlButton(@NonNull ViewGroup bottomControlsViewGroup, @NonNull String imageViewButtonId, - @NonNull BooleanSetting booleanSetting, @NonNull View.OnClickListener onClickListener, - @Nullable View.OnLongClickListener longClickListener) { - Logger.printDebug(() -> "Initializing button: " + imageViewButtonId); - - ImageView imageView = Objects.requireNonNull(bottomControlsViewGroup.findViewById( - Utils.getResourceIdentifier(imageViewButtonId, "id") - )); - imageView.setVisibility(View.GONE); - - imageView.setOnClickListener(onClickListener); - if (longClickListener != null) { - imageView.setOnLongClickListener(longClickListener); - } - - setting = booleanSetting; - buttonRef = new WeakReference<>(imageView); - } - - public void setVisibilityImmediate(boolean visible) { - if (visible) { - // Fix button flickering, by pushing this call to the back of - // the main thread and letting other layout code run first. - Utils.runOnMainThread(() -> private_setVisibility(true, false)); - } else { - private_setVisibility(false, false); - } - } - - public void setVisibility(boolean visible, boolean animated) { - // Ignore this call, otherwise with full screen thumbnails the buttons are visible while seeking. - if (visible && !animated) return; - - private_setVisibility(visible, animated); - } - - private void private_setVisibility(boolean visible, boolean animated) { - try { - if (isVisible == visible) return; - isVisible = visible; - - ImageView iView = buttonRef.get(); - if (iView == null) { - return; - } - - if (visible && setting.get()) { - iView.clearAnimation(); - if (animated) { - iView.startAnimation(PlayerControlButton.getButtonFadeIn()); - } - iView.setVisibility(View.VISIBLE); - } else if (iView.getVisibility() == View.VISIBLE) { - iView.clearAnimation(); - if (animated) { - iView.startAnimation(PlayerControlButton.getButtonFadeOut()); - } - iView.setVisibility(View.GONE); - } - } catch (Exception ex) { - Logger.printException(() -> "setVisibility failure", ex); - } - } -} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlTopButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlTopButton.java new file mode 100644 index 000000000..fed336705 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlTopButton.java @@ -0,0 +1,110 @@ +package app.revanced.extension.youtube.videoplayer; + +import android.transition.Fade; +import android.view.View; +import android.view.animation.Animation; +import android.widget.ImageView; + +import java.lang.ref.WeakReference; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; + +// Ideally this should be refactored into PlayerControlBottomButton, +// but the show/hide logic is not the same so keeping this as two classes might be simpler. +public abstract class PlayerControlTopButton { + static final int fadeInDuration; + static final int fadeOutDuration; + + private static final Animation fadeInAnimation; + private static final Animation fadeOutAnimation; + + static final Fade fadeInTransition; + static final Fade fadeOutTransition; + + private final WeakReference buttonReference; + private boolean isShowing; + + static { + fadeInDuration = Utils.getResourceInteger("fade_duration_fast"); + fadeOutDuration = Utils.getResourceInteger("fade_duration_scheduled"); + + fadeInAnimation = Utils.getResourceAnimation("fade_in"); + fadeInAnimation.setDuration(fadeInDuration); + + fadeOutAnimation = Utils.getResourceAnimation("fade_out"); + fadeOutAnimation.setDuration(fadeOutDuration); + + fadeInTransition = new Fade(); + fadeInTransition.setDuration(fadeInDuration); + + fadeOutTransition = new Fade(); + fadeOutTransition.setDuration(fadeOutDuration); + } + + protected PlayerControlTopButton(ImageView imageView, View.OnClickListener onClickListener) { + imageView.setVisibility(View.GONE); + imageView.setOnClickListener(onClickListener); + + buttonReference = new WeakReference<>(imageView); + } + + protected void setVisibilityImmediate(boolean visible) { + private_setVisibility(visible, false); + } + + protected void setVisibility(boolean visible, boolean animated) { + // Ignore this call, otherwise with full screen thumbnails the buttons are visible while seeking. + if (visible && !animated) return; + + private_setVisibility(visible, animated); + } + + private void private_setVisibility(boolean visible, boolean animated) { + try { + if (isShowing == visible) return; + isShowing = visible; + + ImageView iView = buttonReference.get(); + if (iView == null) return; + + if (visible) { + iView.clearAnimation(); + if (!shouldBeShown()) { + return; + } + if (animated) { + iView.startAnimation(fadeInAnimation); + } + iView.setVisibility(View.VISIBLE); + return; + } + + if (iView.getVisibility() == View.VISIBLE) { + iView.clearAnimation(); + if (animated) { + iView.startAnimation(fadeOutAnimation); + } + iView.setVisibility(View.GONE); + } + } catch (Exception ex) { + Logger.printException(() -> "private_setVisibility failure", ex); + } + } + + protected abstract boolean shouldBeShown(); + + public void hide() { + if (!isShowing) { + return; + } + + Utils.verifyOnMainThread(); + View v = buttonReference.get(); + if (v == null) { + return; + } + v.setVisibility(View.GONE); + isShowing = false; + } +}