diff --git a/app/src/main/java/app/revanced/integrations/patches/HDRAutoBrightnessPatch.java b/app/src/main/java/app/revanced/integrations/patches/HDRAutoBrightnessPatch.java index ec74dc62..330b3cfd 100644 --- a/app/src/main/java/app/revanced/integrations/patches/HDRAutoBrightnessPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/HDRAutoBrightnessPatch.java @@ -3,7 +3,7 @@ package app.revanced.integrations.patches; import android.view.WindowManager; import app.revanced.integrations.settings.SettingsEnum; -import app.revanced.integrations.swipecontrols.views.SwipeControlsHostLayout; +import app.revanced.integrations.swipecontrols.SwipeControlsHostActivity; /** * Patch class for 'hdr-auto-brightness' patch @@ -27,7 +27,7 @@ public class HDRAutoBrightnessPatch { // override with brightness set by swipe-controls // only when swipe-controls is active and has overridden the brightness - final SwipeControlsHostLayout swipeControlsHost = SwipeControlsPatch.CURRENT_HOST.get(); + final SwipeControlsHostActivity swipeControlsHost = SwipeControlsHostActivity.getCurrentHost().get(); if (swipeControlsHost != null && swipeControlsHost.getScreen() != null && swipeControlsHost.getConfig().getEnableBrightnessControl() diff --git a/app/src/main/java/app/revanced/integrations/patches/SwipeControlsPatch.java b/app/src/main/java/app/revanced/integrations/patches/SwipeControlsPatch.java deleted file mode 100644 index f543b3c5..00000000 --- a/app/src/main/java/app/revanced/integrations/patches/SwipeControlsPatch.java +++ /dev/null @@ -1,42 +0,0 @@ -package app.revanced.integrations.patches; - -import android.app.Activity; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.lang.ref.WeakReference; - -import app.revanced.integrations.swipecontrols.views.SwipeControlsHostLayout; - -/** - * Hook receiver class for 'swipe-controls' patch - * - * @usedBy app.revanced.patches.youtube.interaction.swipecontrols.patch.SwipeControlsPatch - * @smali Lapp/revanced/integrations/patches/SwipeControlsPatch; - */ -@SuppressWarnings("unused") -public class SwipeControlsPatch { - - /** - * the currently active swipe controls host. - * the reference may be null! - */ - @NonNull - public static WeakReference CURRENT_HOST = new WeakReference<>(null); - - /** - * Hook into the main activity lifecycle - * (using onStart here, but really anything up until onResume should be fine) - * - * @param thisRef reference to the WatchWhileActivity instance - * @smali WatchWhileActivity_onStartHookEX(Ljava / lang / Object ;)V - */ - public static void WatchWhileActivity_onStartHookEX(@Nullable Object thisRef) { - if (thisRef == null) return; - if (thisRef instanceof Activity) { - SwipeControlsHostLayout swipeControlsHost = SwipeControlsHostLayout.attachTo((Activity) thisRef, false); - CURRENT_HOST = new WeakReference<>(swipeControlsHost); - } - } -} 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 453db1f3..5f9fc916 100644 --- a/app/src/main/java/app/revanced/integrations/swipecontrols/SwipeControlsConfigurationProvider.kt +++ b/app/src/main/java/app/revanced/integrations/swipecontrols/SwipeControlsConfigurationProvider.kt @@ -15,7 +15,7 @@ class SwipeControlsConfigurationProvider( ) { //region swipe enable /** - * should swipe controls be enabled? (global setting + * should swipe controls be enabled? (global setting) */ val enableSwipeControls: Boolean get() = isFullscreenVideo && (enableVolumeControls || enableBrightnessControl) @@ -39,6 +39,14 @@ class SwipeControlsConfigurationProvider( get() = PlayerType.current == PlayerType.WATCH_WHILE_FULLSCREEN //endregion +//region keys enable + /** + * should volume key controls be overwritten? (global setting) + */ + val overwriteVolumeKeyControls: Boolean + get() = isFullscreenVideo && enableVolumeControls +//endregioin + //region gesture adjustments /** * should press-to-swipe be enabled? diff --git a/app/src/main/java/app/revanced/integrations/swipecontrols/SwipeControlsHostActivity.kt b/app/src/main/java/app/revanced/integrations/swipecontrols/SwipeControlsHostActivity.kt new file mode 100644 index 00000000..3a3ec08d --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/swipecontrols/SwipeControlsHostActivity.kt @@ -0,0 +1,178 @@ +package app.revanced.integrations.swipecontrols + +import android.app.Activity +import android.os.Bundle +import android.view.KeyEvent +import android.view.MotionEvent +import android.view.ViewGroup +import app.revanced.integrations.shared.PlayerType +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.VolumeKeysController +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.views.SwipeControlsOverlayLayout +import app.revanced.integrations.utils.LogHelper +import java.lang.ref.WeakReference + +/** + * The main controller for volume and brightness swipe controls. + * note that the superclass is overwritten to the superclass of the WatchWhileActivity at patch time + * + * @smali Lapp/revanced/integrations/swipecontrols/SwipeControlsHostActivity; + */ +class SwipeControlsHostActivity : Activity() { + /** + * current instance of [AudioVolumeController] + */ + var audio: AudioVolumeController? = null + + /** + * current instance of [ScreenBrightnessController] + */ + var screen: ScreenBrightnessController? = null + + /** + * current instance of [SwipeControlsConfigurationProvider] + */ + lateinit var config: SwipeControlsConfigurationProvider + + /** + * current instance of [SwipeControlsOverlayLayout] + */ + lateinit var overlay: SwipeControlsOverlayLayout + + /** + * current instance of [SwipeZonesController] + */ + lateinit var zones: SwipeZonesController + + /** + * main gesture controller + */ + private lateinit var gesture: SwipeGestureController + + /** + * main volume keys controller + */ + private lateinit var keys: VolumeKeysController + + /** + * current content view with id [android.R.id.content] + */ + private val contentRoot + get() = window.decorView.findViewById(android.R.id.content) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // create controllers + LogHelper.info(this.javaClass, "initializing swipe controls controllers") + config = SwipeControlsConfigurationProvider(this) + gesture = createGestureController() + keys = VolumeKeysController(this) + audio = createAudioController() + screen = createScreenController() + + // create overlay + SwipeControlsOverlayLayout(this).let { + overlay = it + contentRoot.addView(it) + } + + // create swipe zone controller + zones = SwipeZonesController(this) { + Rectangle( + contentRoot.x.toInt(), + contentRoot.y.toInt(), + contentRoot.width, + contentRoot.height + ) + } + + // listen for changes in the player type + PlayerType.onChange += this::onPlayerTypeChanged + + // set current instance reference + currentHost = WeakReference(this) + } + + override fun onStart() { + super.onStart() + + // (re) attach overlay + LogHelper.info(this.javaClass, "attaching swipe controls overlay") + contentRoot.removeView(overlay) + contentRoot.addView(overlay) + } + + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + return if ((ev != null) && gesture.onTouchEvent(ev)) true else { + super.dispatchTouchEvent(ev) + } + } + + override fun dispatchKeyEvent(ev: KeyEvent?): Boolean { + return if((ev != null) && keys.onKeyEvent(ev)) true else { + super.dispatchKeyEvent(ev) + } + } + + /** + * dispatch a touch event to downstream views + * + * @param event the event to dispatch + * @return was the event consumed? + */ + fun dispatchDownstreamTouchEvent(event: MotionEvent) = + super.dispatchTouchEvent(event) + + /** + * called when the player type changes + * + * @param type the new player type + */ + private fun onPlayerTypeChanged(type: PlayerType) { + when (type) { + PlayerType.WATCH_WHILE_FULLSCREEN -> screen?.restore() + else -> { + screen?.save() + screen?.restoreDefaultBrightness() + } + } + } + + /** + * create the audio volume controller + */ + private fun createAudioController() = + if (config.enableVolumeControls) + AudioVolumeController(this) else null + + /** + * create the screen brightness controller instance + */ + private fun createScreenController() = + if (config.enableBrightnessControl) + ScreenBrightnessController(this) else null + + /** + * create the gesture controller based on settings + */ + private fun createGestureController() = + if (config.shouldEnablePressToSwipe) + SwipeGestureController(this) + else NoPtSSwipeGestureController(this) + + companion object { + /** + * the currently active swipe controls host. + * the reference may be null! + */ + @JvmStatic + var currentHost: WeakReference = WeakReference(null) + private set + } +} 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 index c0ef500f..f6436e80 100644 --- a/app/src/main/java/app/revanced/integrations/swipecontrols/controller/SwipeZonesController.kt +++ b/app/src/main/java/app/revanced/integrations/swipecontrols/controller/SwipeZonesController.kt @@ -1,6 +1,6 @@ package app.revanced.integrations.swipecontrols.controller -import android.content.Context +import android.app.Activity import android.util.TypedValue import android.view.ViewGroup import app.revanced.integrations.swipecontrols.misc.Rectangle @@ -35,29 +35,28 @@ import kotlin.math.min */ @Suppress("PrivatePropertyName") class SwipeZonesController( - context: Context, - private val parentView: ViewGroup, + private val host: Activity, private val fallbackScreenRect: () -> Rectangle ) { /** * 20dp, in pixels */ - private val _20dp = 20.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) + private val _20dp = 20.applyDimension(host, TypedValue.COMPLEX_UNIT_DIP) /** * 40dp, in pixels */ - private val _40dp = 40.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) + private val _40dp = 40.applyDimension(host, TypedValue.COMPLEX_UNIT_DIP) /** * 80dp, in pixels */ - private val _80dp = 80.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) + private val _80dp = 80.applyDimension(host, TypedValue.COMPLEX_UNIT_DIP) /** * id for R.id.player_view */ - private val playerViewId = ReVancedUtils.getResourceIdByName(context, "id", "player_view") + private val playerViewId = ReVancedUtils.getResourceIdByName(host, "id", "player_view") /** * current bounding rectangle of the player @@ -114,7 +113,7 @@ class SwipeZonesController( */ private fun maybeAttachPlayerBoundsListener() { if (playerRect != null) return - parentView.findViewById(playerViewId)?.let { + host.findViewById(playerViewId)?.let { onPlayerViewLayout(it) it.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> onPlayerViewLayout(it) diff --git a/app/src/main/java/app/revanced/integrations/swipecontrols/controller/VolumeKeysController.kt b/app/src/main/java/app/revanced/integrations/swipecontrols/controller/VolumeKeysController.kt new file mode 100644 index 00000000..be910510 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/swipecontrols/controller/VolumeKeysController.kt @@ -0,0 +1,51 @@ +package app.revanced.integrations.swipecontrols.controller + +import android.view.KeyEvent +import app.revanced.integrations.swipecontrols.SwipeControlsHostActivity + +/** + * controller for custom volume button behaviour + * + * @param controller main controller instance + */ +class VolumeKeysController( + private val controller: SwipeControlsHostActivity +) { + /** + * key event handler + * + * @param event the key event + * @return consume the event? + */ + fun onKeyEvent(event: KeyEvent): Boolean { + if(!controller.config.overwriteVolumeKeyControls) { + return false + } + + return when (event.keyCode) { + KeyEvent.KEYCODE_VOLUME_DOWN -> + handleVolumeKeyEvent(event, false) + KeyEvent.KEYCODE_VOLUME_UP -> + handleVolumeKeyEvent(event, true) + else -> false + } + } + + /** + * handle a volume up / down key event + * + * @param event the key event + * @param volumeUp was the key pressed the volume up key? + * @return consume the event? + */ + private fun handleVolumeKeyEvent(event: KeyEvent, volumeUp: Boolean): Boolean { + if (event.action == KeyEvent.ACTION_DOWN) { + controller.audio?.apply { + volume += if (volumeUp) 1 else -1 + controller.overlay.onVolumeChanged(volume, maxVolume) + } + } + + return true + } +} diff --git a/app/src/main/java/app/revanced/integrations/swipecontrols/controller/gesture/NoPtSSwipeGestureController.kt b/app/src/main/java/app/revanced/integrations/swipecontrols/controller/gesture/NoPtSSwipeGestureController.kt index 3791defc..9ba682ff 100644 --- a/app/src/main/java/app/revanced/integrations/swipecontrols/controller/gesture/NoPtSSwipeGestureController.kt +++ b/app/src/main/java/app/revanced/integrations/swipecontrols/controller/gesture/NoPtSSwipeGestureController.kt @@ -1,15 +1,14 @@ package app.revanced.integrations.swipecontrols.controller.gesture -import android.content.Context import android.view.MotionEvent -import app.revanced.integrations.swipecontrols.views.SwipeControlsHostLayout +import app.revanced.integrations.swipecontrols.SwipeControlsHostActivity /** * [SwipeGestureController], but with press-to-swipe disabled because a lot of people dislike the feature. * If you want to change something, try to do it in [SwipeGestureController] so that both configurations can benefit from it */ -class NoPtSSwipeGestureController(context: Context, controller: SwipeControlsHostLayout) : - SwipeGestureController(context, controller) { +class NoPtSSwipeGestureController(controller: SwipeControlsHostActivity) : + SwipeGestureController(controller) { /** * to disable press-to-swipe, we have to become press-to-swipe 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 0a7fad36..b298079b 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 @@ -1,14 +1,13 @@ package app.revanced.integrations.swipecontrols.controller.gesture -import android.content.Context import android.util.TypedValue import android.view.GestureDetector import android.view.MotionEvent +import app.revanced.integrations.swipecontrols.SwipeControlsHostActivity 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 @@ -17,21 +16,18 @@ import kotlin.math.pow * base gesture controller for volume and brightness swipe controls controls, with press-to-swipe enabled * for the controller without press-to-swipe, see [NoPtSSwipeGestureController] * - * @param context the context to create in * @param controller reference to main controller instance */ @Suppress("LeakingThis") open class SwipeGestureController( - context: Context, - private val controller: SwipeControlsHostLayout + private val controller: SwipeControlsHostActivity ) : - GestureDetector.SimpleOnGestureListener(), - SwipeControlsHostLayout.TouchEventListener { + GestureDetector.SimpleOnGestureListener() { /** * the main gesture detector that powers everything */ - protected open val detector = GestureDetector(context, this) + protected open val detector = GestureDetector(controller, this) /** * to enable swipe controls, users must first long- press. this flags monitors that long- press @@ -60,7 +56,7 @@ open class SwipeGestureController( */ protected open val volumeScroller = ScrollDistanceHelper( 10.applyDimension( - context, + controller, TypedValue.COMPLEX_UNIT_DIP ) ) { _, _, direction -> @@ -75,7 +71,7 @@ open class SwipeGestureController( */ protected open val brightnessScroller = ScrollDistanceHelper( 1.applyDimension( - context, + controller, TypedValue.COMPLEX_UNIT_DIP ) ) { _, _, direction -> @@ -90,7 +86,13 @@ open class SwipeGestureController( } } - override fun onTouchEvent(motionEvent: MotionEvent): Boolean { + /** + * touch event callback + * + * @param motionEvent the motion event that was received + * @return intercept the event? if true, child views will not receive the event + */ + fun onTouchEvent(motionEvent: MotionEvent): Boolean { if (!controller.config.enableSwipeControls) { return false } 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 deleted file mode 100644 index 9c0386ba..00000000 --- a/app/src/main/java/app/revanced/integrations/swipecontrols/views/SwipeControlsHostLayout.kt +++ /dev/null @@ -1,207 +0,0 @@ -package app.revanced.integrations.swipecontrols.views - -import android.annotation.SuppressLint -import android.app.Activity -import android.graphics.Color -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.utils.LogHelper - -/** - * The main controller for volume and brightness swipe controls - * - * @param hostActivity the activity that should host the controller - * @param debugTouchableZone show a overlay on all zones covered by this layout - */ -@SuppressLint("ViewConstructor") -class SwipeControlsHostLayout( - private val hostActivity: Activity, - private val mainContentChild: View, - debugTouchableZone: Boolean = false -) : FrameLayout(hostActivity) { - init { - isFocusable = false - isClickable = false - - if (debugTouchableZone) { - val zoneOverlay = View(context).apply { - layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - setBackgroundColor(Color.argb(50, 0, 255, 0)) - z = 9999f - } - addView(zoneOverlay) - } - } - - /** - * current instance of [AudioVolumeController] - */ - val audio: AudioVolumeController? - - /** - * current instance of [ScreenBrightnessController] - */ - val screen: ScreenBrightnessController? - - /** - * current instance of [SwipeControlsConfigurationProvider] - */ - val config: SwipeControlsConfigurationProvider - - /** - * current instance of [SwipeControlsOverlayLayout] - */ - val overlay: SwipeControlsOverlay - - /** - * current instance of [SwipeZonesController] - */ - val zones: SwipeZonesController - - /** - * main gesture controller - */ - private val gesture: SwipeGestureController - - init { - // create controllers - LogHelper.info(this.javaClass, "initializing swipe controls controllers") - config = SwipeControlsConfigurationProvider(hostActivity) - gesture = createGestureController() - audio = createAudioController() - screen = createScreenController() - - // create overlay - SwipeControlsOverlayLayout(hostActivity).let { - overlay = it - addView(it) - } - - // create swipe zone controller - zones = SwipeZonesController(context, this) { - Rectangle(x.toInt(), y.toInt(), width, height) - } - - // listen for changes in the player type - PlayerType.onChange += this::onPlayerTypeChanged - } - - override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { - return if (ev != null && gesture.onTouchEvent(ev)) true else { - super.dispatchTouchEvent(ev) - } - } - - override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) { - // main content is always at index 0, all other are inserted after - if (child == mainContentChild) { - super.addView(child, 0, params) - } else { - super.addView(child, childCount, params) - } - } - - /** - * called when the player type changes - * - * @param type the new player type - */ - private fun onPlayerTypeChanged(type: PlayerType) { - when (type) { - PlayerType.WATCH_WHILE_FULLSCREEN -> screen?.restore() - else -> { - screen?.save() - screen?.restoreDefaultBrightness() - } - } - } - - /** - * dispatch a touch event to downstream views - * - * @param event the event to dispatch - * @return was the event consumed? - */ - fun dispatchDownstreamTouchEvent(event: MotionEvent) = - super.dispatchTouchEvent(event) - - /** - * create the audio volume controller - */ - private fun createAudioController() = - if (config.enableVolumeControls) - AudioVolumeController(context) else null - - /** - * create the screen brightness controller instance - */ - private fun createScreenController() = - if (config.enableBrightnessControl) - ScreenBrightnessController(hostActivity) else null - - /** - * create the gesture controller based on settings - */ - private fun createGestureController() = - if (config.shouldEnablePressToSwipe) - SwipeGestureController(hostActivity, this) - else NoPtSSwipeGestureController(hostActivity, this) - - - interface TouchEventListener { - /** - * touch event callback - * - * @param motionEvent the motion event that was received - * @return intercept the event? if true, child views will not receive the event - */ - fun onTouchEvent(motionEvent: MotionEvent): Boolean - } - - companion object { - /** - * attach a [SwipeControlsHostLayout] to the activity - * - * @param debugTouchableZone show a overlay on all zones covered by this layout - * @return the attached instance - */ - @JvmStatic - fun Activity.attachTo(debugTouchableZone: Boolean = false): SwipeControlsHostLayout { - // get targets - val contentView: ViewGroup = window.decorView.findViewById(android.R.id.content)!! - var content = contentView.getChildAt(0) - - // detach previously attached swipe host first - if (content is SwipeControlsHostLayout) { - contentView.removeView(content) - content.removeAllViews() - content = content.mainContentChild - } - - // create swipe host - val swipeHost = SwipeControlsHostLayout(this, content, debugTouchableZone).apply { - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - } - - // insert the swipe host as parent to the actual content - contentView.removeView(content) - contentView.addView(swipeHost) - swipeHost.addView(content) - return swipeHost - } - } -}