diff --git a/app/src/main/java/app/revanced/integrations/patches/PlayerOverlaysHookPatch.java b/app/src/main/java/app/revanced/integrations/patches/PlayerOverlaysHookPatch.java new file mode 100644 index 00000000..95b30835 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/patches/PlayerOverlaysHookPatch.java @@ -0,0 +1,29 @@ +package app.revanced.integrations.patches; + +import android.view.ViewGroup; + +import androidx.annotation.Nullable; + +import app.revanced.integrations.shared.PlayerOverlays; + +/** + * Hook receiver class for 'player-overlays-hook' patch + * + * @usedBy app.revanced.patches.youtube.misc.playeroverlay.patch.PlayerOverlaysHookPatch + * @smali Lapp/revanced/integrations/patches/PlayerOverlaysHookPatch; + */ +@SuppressWarnings("unused") +public class PlayerOverlaysHookPatch { + /** + * Hook into YouTubePlayerOverlaysLayout.onFinishInflate() method + * + * @param thisRef reference to the view + * @smali YouTubePlayerOverlaysLayout_onFinishInflateHook(Ljava / lang / Object ;)V + */ + public static void YouTubePlayerOverlaysLayout_onFinishInflateHook(@Nullable Object thisRef) { + if (thisRef == null) return; + if (thisRef instanceof ViewGroup) { + PlayerOverlays.attach((ViewGroup) thisRef); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/patches/PlayerTypeHookPatch.java b/app/src/main/java/app/revanced/integrations/patches/PlayerTypeHookPatch.java index c91390e2..18419769 100644 --- a/app/src/main/java/app/revanced/integrations/patches/PlayerTypeHookPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/PlayerTypeHookPatch.java @@ -3,7 +3,7 @@ package app.revanced.integrations.patches; import androidx.annotation.Nullable; import app.revanced.integrations.utils.LogHelper; -import app.revanced.integrations.utils.PlayerType; +import app.revanced.integrations.shared.PlayerType; /** * Hook receiver class for 'player-type-hook' patch diff --git a/app/src/main/java/app/revanced/integrations/shared/PlayerOverlays.kt b/app/src/main/java/app/revanced/integrations/shared/PlayerOverlays.kt new file mode 100644 index 00000000..9c4c607b --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/shared/PlayerOverlays.kt @@ -0,0 +1,97 @@ +package app.revanced.integrations.shared + +import android.view.View +import android.view.ViewGroup +import app.revanced.integrations.swipecontrols.misc.Rectangle +import app.revanced.integrations.utils.Event + +/** + * hooking class for player overlays + */ +@Suppress("MemberVisibilityCanBePrivate") +object PlayerOverlays { + + /** + * called when the overlays finished inflating + */ + val onInflate = Event() + + /** + * called when new children are added or removed from the overlay + */ + val onChildrenChange = Event() + + /** + * called when the overlay layout changes + */ + val onLayoutChange = Event() + + /** + * start listening for events on the provided view group + * + * @param overlaysLayout the overlays view group + */ + @JvmStatic + fun attach(overlaysLayout: ViewGroup) { + onInflate.invoke(overlaysLayout) + overlaysLayout.setOnHierarchyChangeListener(object : + ViewGroup.OnHierarchyChangeListener { + override fun onChildViewAdded(parent: View?, child: View?) { + if (parent is ViewGroup && child is View) { + onChildrenChange( + ChildrenChangeEventArgs( + parent, + child, + false + ) + ) + } + } + + override fun onChildViewRemoved(parent: View?, child: View?) { + if (parent is ViewGroup && child is View) { + onChildrenChange( + ChildrenChangeEventArgs( + parent, + child, + true + ) + ) + } + } + }) + overlaysLayout.addOnLayoutChangeListener { view, newLeft, newTop, newRight, newBottom, oldLeft, oldTop, oldRight, oldBottom -> + if (view is ViewGroup) { + onLayoutChange( + LayoutChangeEventArgs( + view, + Rectangle( + oldLeft, + oldTop, + oldRight - oldLeft, + oldBottom - oldTop + ), + Rectangle( + newLeft, + newTop, + newRight - newLeft, + newBottom - newTop + ) + ) + ) + } + } + } +} + +data class ChildrenChangeEventArgs( + val overlaysLayout: ViewGroup, + val childView: View, + val wasChildRemoved: Boolean +) + +data class LayoutChangeEventArgs( + val overlaysLayout: ViewGroup, + val oldRect: Rectangle, + val newRect: Rectangle +) diff --git a/app/src/main/java/app/revanced/integrations/utils/PlayerType.kt b/app/src/main/java/app/revanced/integrations/shared/PlayerType.kt similarity index 93% rename from app/src/main/java/app/revanced/integrations/utils/PlayerType.kt rename to app/src/main/java/app/revanced/integrations/shared/PlayerType.kt index c82e62fc..2ea15366 100644 --- a/app/src/main/java/app/revanced/integrations/utils/PlayerType.kt +++ b/app/src/main/java/app/revanced/integrations/shared/PlayerType.kt @@ -1,4 +1,6 @@ -package app.revanced.integrations.utils +package app.revanced.integrations.shared + +import app.revanced.integrations.utils.Event /** * WatchWhile player type diff --git a/app/src/main/java/app/revanced/integrations/swipecontrols/SwipeControlsConfigurationProvider.kt b/app/src/main/java/app/revanced/integrations/swipecontrols/SwipeControlsConfigurationProvider.kt index 0f666262..650eac1e 100644 --- a/app/src/main/java/app/revanced/integrations/swipecontrols/SwipeControlsConfigurationProvider.kt +++ b/app/src/main/java/app/revanced/integrations/swipecontrols/SwipeControlsConfigurationProvider.kt @@ -3,7 +3,7 @@ package app.revanced.integrations.swipecontrols import android.content.Context import android.graphics.Color import app.revanced.integrations.settings.SettingsEnum -import app.revanced.integrations.utils.PlayerType +import app.revanced.integrations.shared.PlayerType /** * provider for configuration for volume and brightness swipe controls diff --git a/app/src/main/java/app/revanced/integrations/swipecontrols/controller/SwipeZonesController.kt b/app/src/main/java/app/revanced/integrations/swipecontrols/controller/SwipeZonesController.kt new file mode 100644 index 00000000..ae6d8234 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/swipecontrols/controller/SwipeZonesController.kt @@ -0,0 +1,140 @@ +package app.revanced.integrations.swipecontrols.controller + +import android.content.Context +import android.util.TypedValue +import android.view.View +import app.revanced.integrations.shared.LayoutChangeEventArgs +import app.revanced.integrations.shared.PlayerOverlays +import app.revanced.integrations.swipecontrols.misc.Rectangle +import app.revanced.integrations.swipecontrols.misc.applyDimension +import app.revanced.integrations.utils.ReVancedUtils + +/** + * Y- Axis: + * -------- 0 + * ^ + * dead | 40dp + * v + * -------- yDeadTop + * ^ + * swipe | + * v + * -------- yDeadBtm + * ^ + * dead | 80dp + * v + * -------- screenHeight + * + * X- Axis: + * 0 xBrigStart xBrigEnd xVolStart xVolEnd screenWidth + * | | | | | | + * | 20dp | 3/8 | 2/8 | 3/8 | 20dp | + * | <------> | <------> | <------> | <------> | <------> | + * | dead | brightness | dead | volume | dead | + * | <--------------------------------> | + * 1/1 + */ +@Suppress("PrivatePropertyName") +class SwipeZonesController( + context: Context, + private val fallbackScreenRect: () -> Rectangle +) { + /** + * 20dp, in pixels + */ + private val _20dp = 20.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) + + /** + * 40dp, in pixels + */ + private val _40dp = 40.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) + + /** + * 80dp, in pixels + */ + private val _80dp = 80.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) + + /** + * id for R.id.engagement_panel + */ + private val engagementPanelId = + ReVancedUtils.getResourceIdByName(context, "id", "engagement_panel") + + /** + * current bounding rectangle of the player overlays + */ + private var playerRect: Rectangle? = null + + /** + * current bounding rectangle of the engagement_panel + */ + private var engagementPanelRect = Rectangle(0, 0, 0, 0) + + /** + * listener for player overlays layout change + */ + private fun onOverlaysLayoutChanged(args: LayoutChangeEventArgs) { + // update engagement panel bounds + val engagementPanel = args.overlaysLayout.findViewById(engagementPanelId) + engagementPanelRect = + if (engagementPanel == null || engagementPanel.visibility != View.VISIBLE) { + Rectangle(0, 0, 0, 0) + } else { + Rectangle( + engagementPanel.x.toInt(), + engagementPanel.y.toInt(), + engagementPanel.width, + engagementPanel.height + ) + } + + // update player bounds + playerRect = args.newRect + } + + init { + PlayerOverlays.onLayoutChange += this::onOverlaysLayoutChanged + } + + /** + * rectangle of the area that is effectively usable for swipe controls + */ + private val effectiveSwipeRect: Rectangle + get() { + val p = if (playerRect != null) playerRect!! else fallbackScreenRect() + return Rectangle( + p.x + _20dp, + p.y + _40dp, + p.width - engagementPanelRect.width - _20dp, + p.height - _20dp - _80dp + ) + } + + /** + * the rectangle of the volume control zone + */ + val volume: Rectangle + get() { + val zoneWidth = (effectiveSwipeRect.width * 3) / 8 + return Rectangle( + effectiveSwipeRect.right - zoneWidth, + effectiveSwipeRect.top, + zoneWidth, + effectiveSwipeRect.height + ) + } + + /** + * the rectangle of the screen brightness control zone + */ + val brightness: Rectangle + get() { + val zoneWidth = (effectiveSwipeRect.width * 3) / 8 + return Rectangle( + effectiveSwipeRect.left, + effectiveSwipeRect.top, + zoneWidth, + effectiveSwipeRect.height + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/swipecontrols/controller/gesture/SwipeGestureController.kt b/app/src/main/java/app/revanced/integrations/swipecontrols/controller/gesture/SwipeGestureController.kt index e3fac02d..0a7fad36 100644 --- a/app/src/main/java/app/revanced/integrations/swipecontrols/controller/gesture/SwipeGestureController.kt +++ b/app/src/main/java/app/revanced/integrations/swipecontrols/controller/gesture/SwipeGestureController.kt @@ -4,11 +4,11 @@ import android.content.Context import android.util.TypedValue import android.view.GestureDetector import android.view.MotionEvent -import app.revanced.integrations.swipecontrols.views.SwipeControlsHostLayout import app.revanced.integrations.swipecontrols.misc.ScrollDistanceHelper import app.revanced.integrations.swipecontrols.misc.applyDimension import app.revanced.integrations.swipecontrols.misc.contains import app.revanced.integrations.swipecontrols.misc.toPoint +import app.revanced.integrations.swipecontrols.views.SwipeControlsHostLayout import app.revanced.integrations.utils.LogHelper import kotlin.math.abs import kotlin.math.pow @@ -98,7 +98,24 @@ open class SwipeGestureController( onUp(motionEvent) } - return detector.onTouchEvent(motionEvent) or shouldForceInterceptEvents + return if (shouldForceInterceptEvents || inSwipeZone(motionEvent)) { + detector.onTouchEvent(motionEvent) or shouldForceInterceptEvents + } else false + } + + /** + * check if provided motion event is in any active swipe zone? + * + * @param e the event to check + * @return is the event in any active swipe zone? + */ + open fun inSwipeZone(e: MotionEvent): Boolean { + val inVolumeZone = if (controller.config.enableVolumeControls) + (e.toPoint() in controller.zones.volume) else false + val inBrightnessZone = if (controller.config.enableBrightnessControl) + (e.toPoint() in controller.zones.brightness) else false + + return inVolumeZone || inBrightnessZone } /** @@ -183,8 +200,8 @@ open class SwipeGestureController( // then, process the event when (eFrom.toPoint()) { - in controller.volumeZone -> volumeScroller.add(disY.toDouble()) - in controller.brightnessZone -> brightnessScroller.add(disY.toDouble()) + in controller.zones.volume -> volumeScroller.add(disY.toDouble()) + in controller.zones.brightness -> brightnessScroller.add(disY.toDouble()) } return true } diff --git a/app/src/main/java/app/revanced/integrations/swipecontrols/misc/SwipeZonesHelper.kt b/app/src/main/java/app/revanced/integrations/swipecontrols/misc/SwipeZonesHelper.kt deleted file mode 100644 index e4ca1565..00000000 --- a/app/src/main/java/app/revanced/integrations/swipecontrols/misc/SwipeZonesHelper.kt +++ /dev/null @@ -1,73 +0,0 @@ -package app.revanced.integrations.swipecontrols.misc - -import android.content.Context -import android.util.TypedValue - -//TODO reimplement this, again with 1/3rd for the zone size -// because in shorts, the screen is way less wide than this code expects! -/** - * Y- Axis: - * -------- 0 - * ^ - * dead | 40dp - * v - * -------- yDeadTop - * ^ - * swipe | - * v - * -------- yDeadBtm - * ^ - * dead | 80dp - * v - * -------- screenHeight - * - * X- Axis: - * 0 xBrigStart xBrigEnd xVolStart xVolEnd screenWidth - * | | | | | | - * | 40dp | 200dp | | 200dp | 40dp | - * | <------> | <------> | <------> | <------> | <------> | - * | dead | brightness | dead | volume | dead | - */ -@Suppress("LocalVariableName") -object SwipeZonesHelper { - - /** - * get the zone for volume control - * - * @param context the current context - * @param screenRect the screen rectangle in the current orientation - * @return the rectangle for the control zone - */ - fun getVolumeControlZone(context: Context, screenRect: Rectangle): Rectangle { - val _40dp = 40.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) - val _80dp = 80.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) - val _200dp = 200.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) - - return Rectangle( - screenRect.right - _40dp - _200dp, - screenRect.top + _40dp, - _200dp, - screenRect.height - _40dp - _80dp - ) - } - - /** - * get the zone for brightness control - * - * @param context the current context - * @param screenRect the screen rectangle in the current orientation - * @return the rectangle for the control zone - */ - fun getBrightnessControlZone(context: Context, screenRect: Rectangle): Rectangle { - val _40dp = 40.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) - val _80dp = 80.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) - val _200dp = 200.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) - - return Rectangle( - screenRect.left + _40dp, - screenRect.top + _40dp, - _200dp, - screenRect.height - _40dp - _80dp - ) - } -} diff --git a/app/src/main/java/app/revanced/integrations/swipecontrols/views/SwipeControlsHostLayout.kt b/app/src/main/java/app/revanced/integrations/swipecontrols/views/SwipeControlsHostLayout.kt index 6f50aeee..ad0cd9f8 100644 --- a/app/src/main/java/app/revanced/integrations/swipecontrols/views/SwipeControlsHostLayout.kt +++ b/app/src/main/java/app/revanced/integrations/swipecontrols/views/SwipeControlsHostLayout.kt @@ -7,16 +7,16 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import app.revanced.integrations.shared.PlayerType import app.revanced.integrations.swipecontrols.SwipeControlsConfigurationProvider import app.revanced.integrations.swipecontrols.controller.AudioVolumeController import app.revanced.integrations.swipecontrols.controller.ScreenBrightnessController +import app.revanced.integrations.swipecontrols.controller.SwipeZonesController import app.revanced.integrations.swipecontrols.controller.gesture.NoPtSSwipeGestureController import app.revanced.integrations.swipecontrols.controller.gesture.SwipeGestureController import app.revanced.integrations.swipecontrols.misc.Rectangle import app.revanced.integrations.swipecontrols.misc.SwipeControlsOverlay -import app.revanced.integrations.swipecontrols.misc.SwipeZonesHelper import app.revanced.integrations.utils.LogHelper -import app.revanced.integrations.utils.PlayerType /** * The main controller for volume and brightness swipe controls @@ -64,6 +64,11 @@ class SwipeControlsHostLayout( */ val overlay: SwipeControlsOverlay + /** + * current instance of [SwipeZonesController] + */ + val zones: SwipeZonesController + /** * main gesture controller */ @@ -83,6 +88,9 @@ class SwipeControlsHostLayout( addView(it) } + // create swipe zone controller + zones = SwipeZonesController(context) { Rectangle(x.toInt(), y.toInt(), width, height) } + // listen for changes in the player type PlayerType.onChange += this::onPlayerTypeChanged } @@ -148,24 +156,6 @@ class SwipeControlsHostLayout( SwipeGestureController(hostActivity, this) else NoPtSSwipeGestureController(hostActivity, this) - /** - * the current screen rectangle - */ - private val screenRect: Rectangle - get() = Rectangle(x.toInt(), y.toInt(), width, height) - - /** - * the rectangle of the volume control zone - */ - val volumeZone: Rectangle - get() = SwipeZonesHelper.getVolumeControlZone(hostActivity, screenRect) - - /** - * the rectangle of the screen brightness control zone - */ - val brightnessZone: Rectangle - get() = SwipeZonesHelper.getBrightnessControlZone(hostActivity, screenRect) - interface TouchEventListener { /**