diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java index 5287033109..84576bd5b8 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java @@ -121,8 +121,7 @@ private static void createToolbar(Activity activity, String toolbarTitleResource // This is required to fix submenu title alignment issue with Android ASOP 15+ ViewGroup toolBarParent = activity.findViewById( getResourceIdentifier("revanced_toolbar_parent", "id")); - ViewGroup dummyToolbar = toolBarParent.findViewById(getResourceIdentifier( - "revanced_toolbar", "id")); + ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent,"revanced_toolbar"); toolbarLayoutParams = dummyToolbar.getLayoutParams(); toolBarParent.removeView(dummyToolbar); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt index 5964e5fe69..a08a197309 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt @@ -21,9 +21,6 @@ enum class PlayerType { /** * A regular video is minimized. - * - * When spoofing to 16.x YouTube and watching a short with a regular video in the background, - * the type can be this (and not [HIDDEN]). */ WATCH_WHILE_MINIMIZED, WATCH_WHILE_MAXIMIZED, @@ -56,8 +53,7 @@ enum class PlayerType { val newType = nameToPlayerType[enumName] if (newType == null) { Logger.printException { "Unknown PlayerType encountered: $enumName" } - } else if (current != newType) { - Logger.printDebug { "PlayerType changed to: $newType" } + } else { current = newType } } @@ -68,9 +64,13 @@ enum class PlayerType { @JvmStatic var current get() = currentPlayerType - private set(value) { - currentPlayerType = value - onChange(currentPlayerType) + private set(type) { + if (currentPlayerType != type) { + Logger.printDebug { "Changed to: $type" } + + currentPlayerType = type + onChange(type) + } } @Volatile // Read/write from different threads. diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButton.java new file mode 100644 index 0000000000..b764ef24a2 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButton.java @@ -0,0 +1,56 @@ +package app.revanced.extension.youtube.sponsorblock.ui; + +import android.view.View; + +import androidx.annotation.Nullable; + +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.videoplayer.PlayerControlButton; + +public class CreateSegmentButton { + @Nullable + private static PlayerControlButton instance; + + public static void hideControls() { + if (instance != null) instance.hide(); + } + + /** + * injection point + */ + public static void initialize(View controlsView) { + try { + instance = new PlayerControlButton( + controlsView, + "revanced_sb_create_segment_button", + null, + CreateSegmentButton::shouldBeShown, + v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility(), + null + ); + } catch (Exception ex) { + Logger.printException(() -> "initialize failure", ex); + } + } + + /** + * Injection point + */ + public static void setVisibilityImmediate(boolean visible) { + if (instance != null) instance.setVisibilityImmediate(visible); + } + + /** + * Injection point + */ + public static void setVisibility(boolean visible, boolean animated) { + if (instance != null) instance.setVisibility(visible, animated); + } + + private static boolean shouldBeShown() { + return Settings.SB_ENABLED.get() && Settings.SB_CREATE_NEW_SEGMENT.get() + && !VideoInformation.isAtEndOfVideo(); + } +} 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 deleted file mode 100644 index 616bc2f062..0000000000 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButtonController.java +++ /dev/null @@ -1,60 +0,0 @@ -package app.revanced.extension.youtube.sponsorblock.ui; - -import android.view.View; -import android.widget.ImageView; - -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.videoplayer.PlayerControlTopButton; - -public class CreateSegmentButtonController extends PlayerControlTopButton { - @Nullable - private static CreateSegmentButtonController instance; - - public static void hideControls() { - if (instance != null) instance.hide(); - } - - /** - * injection point - */ - public static void initialize(View youtubeControlsLayout) { - try { - Logger.printDebug(() -> "initializing new segment button"); - ImageView imageView = Objects.requireNonNull(Utils.getChildViewByResourceName( - youtubeControlsLayout, "revanced_sb_create_segment_button")); - instance = new CreateSegmentButtonController(imageView); - } catch (Exception ex) { - Logger.printException(() -> "initialize failure", ex); - } - } - - /** - * injection point - */ - public static void changeVisibilityImmediate(boolean visible) { - if (instance != null) instance.setVisibilityImmediate(visible); - } - - /** - * injection point - */ - public static void changeVisibility(boolean visible, boolean animated) { - if (instance != null) instance.setVisibility(visible, animated); - } - - private CreateSegmentButtonController(ImageView imageView) { - super(imageView, v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility()); - } - - protected boolean shouldBeShown() { - return Settings.SB_ENABLED.get() && Settings.SB_CREATE_NEW_SEGMENT.get() - && !VideoInformation.isAtEndOfVideo(); - } -} 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 297fb3acc7..a16ffdd321 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,7 +19,6 @@ 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 { @@ -239,8 +238,8 @@ public static void endOfVideoReached() { // 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.hideControls(); - VotingButtonController.hideControls(); + CreateSegmentButton.hideControls(); + VotingButton.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/VotingButton.java similarity index 51% rename from extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/VotingButtonController.java rename to extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/VotingButton.java index 0240d1c70b..87d46c464f 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/VotingButton.java @@ -1,23 +1,19 @@ package app.revanced.extension.youtube.sponsorblock.ui; import android.view.View; -import android.widget.ImageView; 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.youtube.videoplayer.PlayerControlTopButton; +import app.revanced.extension.youtube.videoplayer.PlayerControlButton; -public class VotingButtonController extends PlayerControlTopButton { +public class VotingButton { @Nullable - private static VotingButtonController instance; + private static PlayerControlButton instance; public static void hideControls() { if (instance != null) instance.hide(); @@ -26,36 +22,36 @@ public static void hideControls() { /** * injection point */ - public static void initialize(View youtubeControlsLayout) { + public static void initialize(View controlsView) { try { - Logger.printDebug(() -> "initializing voting button"); - ImageView imageView = Objects.requireNonNull(Utils.getChildViewByResourceName( - youtubeControlsLayout, "revanced_sb_voting_button")); - instance = new VotingButtonController(imageView); + instance = new PlayerControlButton( + controlsView, + "revanced_sb_voting_button", + null, + VotingButton::shouldBeShown, + v -> SponsorBlockUtils.onVotingClicked(v.getContext()), + null + ); } catch (Exception ex) { Logger.printException(() -> "initialize failure", ex); } } /** - * injection point + * Injection point */ - public static void changeVisibilityImmediate(boolean visible) { + public static void setVisibilityImmediate(boolean visible) { if (instance != null) instance.setVisibilityImmediate(visible); } /** - * injection point + * Injection point */ - public static void changeVisibility(boolean visible, boolean animated) { + public static void setVisibility(boolean visible, boolean animated) { if (instance != null) instance.setVisibility(visible, animated); } - private VotingButtonController(ImageView imageView) { - super(imageView, v -> SponsorBlockUtils.onVotingClicked(v.getContext())); - } - - protected boolean shouldBeShown() { + private static boolean shouldBeShown() { return Settings.SB_ENABLED.get() && Settings.SB_VOTING_BUTTON.get() && SegmentPlaybackController.videoHasSegments() && !VideoInformation.isAtEndOfVideo(); } 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 62d6ef1f1d..be66cd70a7 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 @@ -1,38 +1,35 @@ package app.revanced.extension.youtube.videoplayer; import android.view.View; -import android.view.ViewGroup; import androidx.annotation.Nullable; +import app.revanced.extension.shared.Logger; import app.revanced.extension.youtube.patches.CopyVideoUrlPatch; import app.revanced.extension.youtube.settings.Settings; -import app.revanced.extension.shared.Logger; +import app.revanced.extension.youtube.shared.PlayerType; @SuppressWarnings("unused") -public class CopyVideoUrlButton extends PlayerControlBottomButton { +public class CopyVideoUrlButton { @Nullable - private static CopyVideoUrlButton instance; - - public CopyVideoUrlButton(ViewGroup viewGroup) { - super( - viewGroup, - "revanced_copy_video_url_button", - Settings.COPY_VIDEO_URL, - view -> CopyVideoUrlPatch.copyUrl(false), - view -> { - CopyVideoUrlPatch.copyUrl(true); - return true; - } - ); - } + private static PlayerControlButton instance; /** * Injection point. */ - public static void initializeButton(View view) { + public static void initializeButton(View controlsView) { try { - instance = new CopyVideoUrlButton((ViewGroup) view); + instance = new PlayerControlButton( + controlsView, + "revanced_copy_video_url_button", + "revanced_copy_video_url_button_placeholder", + Settings.COPY_VIDEO_URL::get, + view -> CopyVideoUrlPatch.copyUrl(false), + view -> { + CopyVideoUrlPatch.copyUrl(true); + return true; + } + ); } catch (Exception ex) { Logger.printException(() -> "initializeButton failure", ex); } @@ -41,14 +38,14 @@ public static void initializeButton(View view) { /** * injection point */ - public static void changeVisibilityImmediate(boolean visible) { + public static void setVisibilityImmediate(boolean visible) { if (instance != null) instance.setVisibilityImmediate(visible); } /** * injection point */ - public static void changeVisibility(boolean visible, boolean animated) { + public static void setVisibility(boolean visible, boolean animated) { if (instance != null) instance.setVisibility(visible, animated); } } \ No newline at end of file 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 269431d755..f7f246e1cc 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 @@ -1,38 +1,35 @@ package app.revanced.extension.youtube.videoplayer; import android.view.View; -import android.view.ViewGroup; import androidx.annotation.Nullable; +import app.revanced.extension.shared.Logger; import app.revanced.extension.youtube.patches.CopyVideoUrlPatch; import app.revanced.extension.youtube.settings.Settings; -import app.revanced.extension.shared.Logger; +import app.revanced.extension.youtube.shared.PlayerType; @SuppressWarnings("unused") -public class CopyVideoUrlTimestampButton extends PlayerControlBottomButton { +public class CopyVideoUrlTimestampButton { @Nullable - private static CopyVideoUrlTimestampButton instance; - - public CopyVideoUrlTimestampButton(ViewGroup bottomControlsViewGroup) { - super( - bottomControlsViewGroup, - "revanced_copy_video_url_timestamp_button", - Settings.COPY_VIDEO_URL_TIMESTAMP, - view -> CopyVideoUrlPatch.copyUrl(true), - view -> { - CopyVideoUrlPatch.copyUrl(false); - return true; - } - ); - } + private static PlayerControlButton instance; /** * Injection point. */ - public static void initializeButton(View bottomControlsViewGroup) { + public static void initializeButton(View controlsView) { try { - instance = new CopyVideoUrlTimestampButton((ViewGroup) bottomControlsViewGroup); + instance = new PlayerControlButton( + controlsView, + "revanced_copy_video_url_timestamp_button", + "revanced_copy_video_url_timestamp_button_placeholder", + Settings.COPY_VIDEO_URL_TIMESTAMP::get, + view -> CopyVideoUrlPatch.copyUrl(true), + view -> { + CopyVideoUrlPatch.copyUrl(false); + return true; + } + ); } catch (Exception ex) { Logger.printException(() -> "initializeButton failure", ex); } @@ -41,14 +38,14 @@ public static void initializeButton(View bottomControlsViewGroup) { /** * injection point */ - public static void changeVisibilityImmediate(boolean visible) { + public static void setVisibilityImmediate(boolean visible) { if (instance != null) instance.setVisibilityImmediate(visible); } /** * injection point */ - public static void changeVisibility(boolean visible, boolean animated) { + public static void setVisibility(boolean visible, boolean animated) { if (instance != null) instance.setVisibility(visible, animated); } } \ No newline at end of file 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 112df8086e..1ba5b2dccd 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 @@ -1,7 +1,6 @@ package app.revanced.extension.youtube.videoplayer; import android.view.View; -import android.view.ViewGroup; import androidx.annotation.Nullable; @@ -11,26 +10,23 @@ import app.revanced.extension.youtube.settings.Settings; @SuppressWarnings("unused") -public class ExternalDownloadButton extends PlayerControlBottomButton { +public class ExternalDownloadButton { @Nullable - private static ExternalDownloadButton instance; - - public ExternalDownloadButton(ViewGroup viewGroup) { - super( - viewGroup, - "revanced_external_download_button", - Settings.EXTERNAL_DOWNLOADER, - ExternalDownloadButton::onDownloadClick, - null - ); - } + private static PlayerControlButton instance; /** * Injection point. */ - public static void initializeButton(View view) { + public static void initializeButton(View controlsView) { try { - instance = new ExternalDownloadButton((ViewGroup) view); + instance = new PlayerControlButton( + controlsView, + "revanced_external_download_button", + "revanced_external_download_button_placeholder", + Settings.EXTERNAL_DOWNLOADER::get, + ExternalDownloadButton::onDownloadClick, + null + ); } catch (Exception ex) { Logger.printException(() -> "initializeButton failure", ex); } @@ -39,14 +35,14 @@ public static void initializeButton(View view) { /** * injection point */ - public static void changeVisibilityImmediate(boolean visible) { + public static void setVisibilityImmediate(boolean visible) { if (instance != null) instance.setVisibilityImmediate(visible); } /** - * injection point + * Injection point */ - public static void changeVisibility(boolean visible, boolean animated) { + public static void setVisibility(boolean visible, boolean animated) { if (instance != null) instance.setVisibility(visible, animated); } 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 b0ace1bec3..3ac3870613 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 @@ -1,35 +1,31 @@ package app.revanced.extension.youtube.videoplayer; import android.view.View; -import android.view.ViewGroup; import androidx.annotation.Nullable; +import app.revanced.extension.shared.Logger; import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch; import app.revanced.extension.youtube.settings.Settings; -import app.revanced.extension.shared.Logger; @SuppressWarnings("unused") -public class PlaybackSpeedDialogButton extends PlayerControlBottomButton { +public class PlaybackSpeedDialogButton { @Nullable - private static PlaybackSpeedDialogButton instance; - - public PlaybackSpeedDialogButton(ViewGroup viewGroup) { - super( - viewGroup, - "revanced_playback_speed_dialog_button", - Settings.PLAYBACK_SPEED_DIALOG_BUTTON, - view -> CustomPlaybackSpeedPatch.showOldPlaybackSpeedMenu(), - null - ); - } + private static PlayerControlButton instance; /** * Injection point. */ - public static void initializeButton(View view) { + public static void initializeButton(View controlsView) { try { - instance = new PlaybackSpeedDialogButton((ViewGroup) view); + instance = new PlayerControlButton( + controlsView, + "revanced_playback_speed_dialog_button", + "revanced_playback_speed_dialog_button_placeholder", + Settings.PLAYBACK_SPEED_DIALOG_BUTTON::get, + view -> CustomPlaybackSpeedPatch.showOldPlaybackSpeedMenu(), + null + ); } catch (Exception ex) { Logger.printException(() -> "initializeButton failure", ex); } @@ -38,14 +34,14 @@ public static void initializeButton(View view) { /** * injection point */ - public static void changeVisibilityImmediate(boolean visible) { + public static void setVisibilityImmediate(boolean visible) { if (instance != null) instance.setVisibilityImmediate(visible); } /** * injection point */ - public static void changeVisibility(boolean visible, boolean animated) { + public static void setVisibility(boolean visible, boolean animated) { if (instance != null) instance.setVisibility(visible, animated); } } \ No newline at end of file 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 deleted file mode 100644 index abf7c5f886..0000000000 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlBottomButton.java +++ /dev/null @@ -1,103 +0,0 @@ -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 new file mode 100644 index 0000000000..d2b47b888f --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlButton.java @@ -0,0 +1,184 @@ +package app.revanced.extension.youtube.videoplayer; + +import android.view.View; +import android.view.animation.Animation; +import android.widget.ImageView; + +import androidx.annotation.Nullable; + +import java.lang.ref.WeakReference; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.youtube.shared.PlayerType; +import kotlin.Unit; + +public class PlayerControlButton { + public interface PlayerControlButtonVisibility { + /** + * @return If the button should be shown when the player overlay is visible. + */ + boolean shouldBeShown(); + } + + private static final int fadeInDuration; + private static final int fadeOutDuration; + + private static final Animation fadeInAnimation; + private static final Animation fadeOutAnimation; + private static final Animation fadeOutImmediate; + + 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); + + // Animation for the fast fade out after tapping the overlay. + // Currently not used but should be. + fadeOutImmediate = Utils.getResourceAnimation("abc_fade_out"); + fadeOutImmediate.setDuration(Utils.getResourceInteger("fade_duration_fast")); + } + + private final WeakReference buttonRef; + /** + * Empty view with the same layout size as the button. Used to fill empty space while the + * fade out animation runs. Without this the chapter titles overlapping the button when fading out. + */ + private final WeakReference placeHolderRef; + private final PlayerControlButtonVisibility visibilityCheck; + private boolean isVisible; + + public PlayerControlButton(View controlsViewGroup, + String imageViewButtonId, + @Nullable String placeholderId, + PlayerControlButtonVisibility buttonVisibility, + View.OnClickListener onClickListener, + @Nullable View.OnLongClickListener longClickListener) { + ImageView imageView = Utils.getChildViewByResourceName(controlsViewGroup, imageViewButtonId); + imageView.setVisibility(View.GONE); + + View tempPlaceholder = null; + if (placeholderId != null) { + tempPlaceholder = Utils.getChildViewByResourceName(controlsViewGroup, placeholderId); + tempPlaceholder.setVisibility(View.GONE); + } + placeHolderRef = new WeakReference<>(tempPlaceholder); + + imageView.setOnClickListener(onClickListener); + if (longClickListener != null) { + imageView.setOnLongClickListener(longClickListener); + } + + visibilityCheck = buttonVisibility; + buttonRef = new WeakReference<>(imageView); + isVisible = false; + + // Update the visibility after the player type changes. + // This ensures that button animations are cleared and their states are updated correctly + // when switching between states like minimized, maximized, or fullscreen, preventing + // "stuck" animations or incorrect visibility. Without this fix the issue is most noticable + // when maximizing type 3 miniplayer. + PlayerType.getOnChange().addObserver((PlayerType type) -> { + playerTypeChanged(type); + return Unit.INSTANCE; + }); + } + + public void setVisibilityImmediate(boolean visible) { + private_setVisibility(visible, 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; + + View button = buttonRef.get(); + if (button == null) return; + + View placeholder = placeHolderRef.get(); + final boolean shouldBeShown = visibilityCheck.shouldBeShown(); + + if (visible && shouldBeShown) { + button.clearAnimation(); + if (animated) { + button.startAnimation(PlayerControlButton.fadeInAnimation); + } + button.setVisibility(View.VISIBLE); + + if (placeholder != null) { + placeholder.setVisibility(View.GONE); + } + } else { + if (button.getVisibility() == View.VISIBLE) { + button.clearAnimation(); + if (animated) { + button.startAnimation(PlayerControlButton.fadeOutAnimation); + } + button.setVisibility(View.GONE); + } + + if (placeholder != null) { + placeholder.setVisibility(shouldBeShown + ? View.VISIBLE + : View.GONE); + } + } + } catch (Exception ex) { + Logger.printException(() -> "private_setVisibility failure", ex); + } + } + + /** + * Synchronizes the button state after the player state changes. + */ + private void playerTypeChanged(PlayerType newType) { + if (newType != PlayerType.WATCH_WHILE_MINIMIZED && !newType.isMaximizedOrFullscreen()) { + return; + } + + View button = buttonRef.get(); + if (button == null) return; + + button.clearAnimation(); + View placeholder = placeHolderRef.get(); + + if (visibilityCheck.shouldBeShown()) { + if (isVisible) { + button.setVisibility(View.VISIBLE); + if (placeholder != null) placeholder.setVisibility(View.GONE); + } else { + button.setVisibility(View.GONE); + if (placeholder != null) placeholder.setVisibility(View.VISIBLE); + } + } else { + button.setVisibility(View.GONE); + if (placeholder != null) placeholder.setVisibility(View.GONE); + } + } + + public void hide() { + if (!isVisible) return; + + Utils.verifyOnMainThread(); + View view = buttonRef.get(); + if (view == null) return; + view.setVisibility(View.GONE); + + view = placeHolderRef.get(); + if (view != null) view.setVisibility(View.GONE); + isVisible = false; + } +} \ No newline at end of file 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 deleted file mode 100644 index fed3367056..0000000000 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/PlayerControlTopButton.java +++ /dev/null @@ -1,110 +0,0 @@ -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; - } -} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt index 78edfff91f..3c9571f47e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt @@ -82,9 +82,9 @@ private val sponsorBlockResourcePatch = resourcePatch { private const val EXTENSION_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/sponsorblock/SegmentPlaybackController;" private const val EXTENSION_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR = - "Lapp/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButtonController;" + "Lapp/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButton;" private const val EXTENSION_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR = - "Lapp/revanced/extension/youtube/sponsorblock/ui/VotingButtonController;" + "Lapp/revanced/extension/youtube/sponsorblock/ui/VotingButton;" private const val EXTENSION_SPONSORBLOCK_VIEW_CONTROLLER_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/sponsorblock/ui/SponsorBlockViewController;" diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt index 6145048cef..7db8de5b2b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt @@ -77,12 +77,9 @@ val playerControlsResourcePatch = resourcePatch { ).item(0) val bottomTargetDocumentChildNodes = bottomTargetDocument.childNodes - var bottomInsertBeforeNode: Node = bottomTargetDocumentChildNodes.findElementByAttributeValue( + var bottomInsertBeforeNode: Node = bottomTargetDocumentChildNodes.findElementByAttributeValueOrThrow( "android:inflatedId", bottomLastLeftOf, - ) ?: bottomTargetDocumentChildNodes.findElementByAttributeValueOrThrow( - "android:id", // Older targets use non-inflated id. - bottomLastLeftOf, ) addTopControl = { resourceDirectoryName -> @@ -123,7 +120,7 @@ val playerControlsResourcePatch = resourcePatch { ).item(0).childNodes // Copy the patch layout xml into the target layout file. - for (index in 1 until sourceElements.length) { + for (index in sourceElements.length - 1 downTo 1) { val element = sourceElements.item(index).cloneNode(true) // If the element has no attributes there's no point adding it to the destination. @@ -189,7 +186,7 @@ fun initializeBottomControl(descriptor: String) { fun injectVisibilityCheckCall(descriptor: String) { visibilityMethod.addInstruction( visibilityInsertIndex++, - "invoke-static { p1 , p2 }, $descriptor->changeVisibility(ZZ)V", + "invoke-static { p1 , p2 }, $descriptor->setVisibility(ZZ)V", ) if (!visibilityImmediateCallbacksExistModified) { @@ -199,7 +196,7 @@ fun injectVisibilityCheckCall(descriptor: String) { visibilityImmediateMethod.addInstruction( visibilityImmediateInsertIndex++, - "invoke-static { p0 }, $descriptor->changeVisibilityImmediate(Z)V", + "invoke-static { p0 }, $descriptor->setVisibilityImmediate(Z)V", ) } diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 534f78b07c..10d657b808 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -448,10 +448,10 @@ This feature is only available for older devices" URL copied to clipboard URL with timestamp copied Show copy video URL button - Button is shown. Tap to copy video URL. Tap and hold to copy video URL with timestamp + Button is shown. Tap to copy video URL. Tap and hold to copy with timestamp Button is not shown Show copy timestamp URL button - Button is shown. Tap to copy video URL with timestamp. Tap and hold to copy video without timestamp + Button is shown. Tap to copy video URL with timestamp. Tap and hold to copy without timestamp Button is not shown diff --git a/patches/src/main/resources/copyvideourl/host/layout/youtube_controls_bottom_ui_container.xml b/patches/src/main/resources/copyvideourl/host/layout/youtube_controls_bottom_ui_container.xml index 531be4cf5f..ef5fcf9cbd 100644 --- a/patches/src/main/resources/copyvideourl/host/layout/youtube_controls_bottom_ui_container.xml +++ b/patches/src/main/resources/copyvideourl/host/layout/youtube_controls_bottom_ui_container.xml @@ -13,12 +13,19 @@ android:layout_height="60.0dip" android:paddingTop="6.0dp" android:paddingBottom="0dp" - android:longClickable="false" android:scaleType="center" android:src="@drawable/revanced_yt_copy_timestamp" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" /> + + + + diff --git a/patches/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml b/patches/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml index a243eea714..eb9c2d7792 100644 --- a/patches/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml +++ b/patches/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml @@ -18,4 +18,12 @@ android:src="@drawable/revanced_yt_download_button" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" /> + + diff --git a/patches/src/main/resources/speedbutton/host/layout/youtube_controls_bottom_ui_container.xml b/patches/src/main/resources/speedbutton/host/layout/youtube_controls_bottom_ui_container.xml index 3f51307493..315251b0c0 100644 --- a/patches/src/main/resources/speedbutton/host/layout/youtube_controls_bottom_ui_container.xml +++ b/patches/src/main/resources/speedbutton/host/layout/youtube_controls_bottom_ui_container.xml @@ -18,4 +18,12 @@ android:src="@drawable/revanced_playback_speed_dialog_button" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" /> + +