Skip to content
This repository was archived by the owner on Oct 26, 2024. It is now read-only.

Commit 6d8c0a0

Browse files
authored
feat: disable swipe-controls when player controls are visible (#123)
1 parent 2b76337 commit 6d8c0a0

10 files changed

Lines changed: 640 additions & 269 deletions
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package app.revanced.integrations.shared
2+
3+
import android.app.Activity
4+
import android.view.View
5+
import android.view.ViewGroup
6+
import app.revanced.integrations.utils.ReVancedUtils
7+
import java.lang.ref.WeakReference
8+
9+
/**
10+
* default implementation of [PlayerControlsVisibilityObserver]
11+
*
12+
* @param activity activity that contains the controls_layout view
13+
*/
14+
class PlayerControlsVisibilityObserverImpl(
15+
private val activity: Activity
16+
) : PlayerControlsVisibilityObserver {
17+
18+
/**
19+
* id of the direct parent of controls_layout, R.id.youtube_controls_overlay
20+
*/
21+
private val controlsLayoutParentId =
22+
ReVancedUtils.getResourceIdByName(activity, "id", "youtube_controls_overlay")
23+
24+
/**
25+
* id of R.id.controls_layout
26+
*/
27+
private val controlsLayoutId =
28+
ReVancedUtils.getResourceIdByName(activity, "id", "controls_layout")
29+
30+
/**
31+
* reference to the controls layout view
32+
*/
33+
private var controlsLayoutView = WeakReference<View>(null)
34+
35+
/**
36+
* is the [controlsLayoutView] set to a valid reference of a view?
37+
*/
38+
private val isAttached: Boolean
39+
get() {
40+
val view = controlsLayoutView.get()
41+
return view != null && view.parent != null
42+
}
43+
44+
/**
45+
* find and attach the controls_layout view if needed
46+
*/
47+
private fun maybeAttach() {
48+
if (isAttached) return
49+
50+
// find parent, then controls_layout view
51+
// this is needed because there may be two views where id=R.id.controls_layout
52+
// because why should google confine themselves to their own guidelines...
53+
activity.findViewById<ViewGroup>(controlsLayoutParentId)?.let { parent ->
54+
parent.findViewById<View>(controlsLayoutId)?.let {
55+
controlsLayoutView = WeakReference(it)
56+
}
57+
}
58+
}
59+
60+
override val playerControlsVisibility: Int
61+
get() {
62+
maybeAttach()
63+
return controlsLayoutView.get()?.visibility ?: View.GONE
64+
}
65+
66+
override val arePlayerControlsVisible: Boolean
67+
get() = playerControlsVisibility == View.VISIBLE
68+
}
69+
70+
/**
71+
* provides the visibility status of the fullscreen player controls_layout view.
72+
* this can be used for detecting when the player controls are shown
73+
*/
74+
interface PlayerControlsVisibilityObserver {
75+
/**
76+
* current visibility int of the controls_layout view
77+
*/
78+
val playerControlsVisibility: Int
79+
80+
/**
81+
* is the value of [playerControlsVisibility] equal to [View.VISIBLE]?
82+
*/
83+
val arePlayerControlsVisible: Boolean
84+
}

app/src/main/java/app/revanced/integrations/swipecontrols/SwipeControlsHostActivity.kt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import app.revanced.integrations.swipecontrols.controller.AudioVolumeController
1010
import app.revanced.integrations.swipecontrols.controller.ScreenBrightnessController
1111
import app.revanced.integrations.swipecontrols.controller.SwipeZonesController
1212
import app.revanced.integrations.swipecontrols.controller.VolumeKeysController
13-
import app.revanced.integrations.swipecontrols.controller.gesture.NoPtSSwipeGestureController
14-
import app.revanced.integrations.swipecontrols.controller.gesture.SwipeGestureController
13+
import app.revanced.integrations.swipecontrols.controller.gesture.ClassicSwipeController
14+
import app.revanced.integrations.swipecontrols.controller.gesture.PressToSwipeController
15+
import app.revanced.integrations.swipecontrols.controller.gesture.core.GestureController
1516
import app.revanced.integrations.swipecontrols.misc.Rectangle
1617
import app.revanced.integrations.swipecontrols.views.SwipeControlsOverlayLayout
1718
import app.revanced.integrations.utils.LogHelper
@@ -52,7 +53,7 @@ class SwipeControlsHostActivity : Activity() {
5253
/**
5354
* main gesture controller
5455
*/
55-
private lateinit var gesture: SwipeGestureController
56+
private lateinit var gesture: GestureController
5657

5758
/**
5859
* main volume keys controller
@@ -71,13 +72,12 @@ class SwipeControlsHostActivity : Activity() {
7172
// create controllers
7273
LogHelper.info(this.javaClass, "initializing swipe controls controllers")
7374
config = SwipeControlsConfigurationProvider(this)
74-
gesture = createGestureController()
7575
keys = VolumeKeysController(this)
7676
audio = createAudioController()
7777
screen = createScreenController()
7878

7979
// create overlay
80-
SwipeControlsOverlayLayout(this).let {
80+
SwipeControlsOverlayLayout(this, config).let {
8181
overlay = it
8282
contentRoot.addView(it)
8383
}
@@ -92,6 +92,9 @@ class SwipeControlsHostActivity : Activity() {
9292
)
9393
}
9494

95+
// create the gesture controller
96+
gesture = createGestureController()
97+
9598
// listen for changes in the player type
9699
PlayerType.onChange += this::onPlayerTypeChanged
97100

@@ -109,13 +112,13 @@ class SwipeControlsHostActivity : Activity() {
109112
}
110113

111114
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
112-
return if ((ev != null) && gesture.onTouchEvent(ev)) true else {
115+
return if ((ev != null) && gesture.submitTouchEvent(ev)) true else {
113116
super.dispatchTouchEvent(ev)
114117
}
115118
}
116119

117120
override fun dispatchKeyEvent(ev: KeyEvent?): Boolean {
118-
return if((ev != null) && keys.onKeyEvent(ev)) true else {
121+
return if ((ev != null) && keys.onKeyEvent(ev)) true else {
119122
super.dispatchKeyEvent(ev)
120123
}
121124
}
@@ -163,8 +166,8 @@ class SwipeControlsHostActivity : Activity() {
163166
*/
164167
private fun createGestureController() =
165168
if (config.shouldEnablePressToSwipe)
166-
SwipeGestureController(this)
167-
else NoPtSSwipeGestureController(this)
169+
PressToSwipeController(this)
170+
else ClassicSwipeController(this)
168171

169172
companion object {
170173
/**
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package app.revanced.integrations.swipecontrols.controller.gesture
2+
3+
import android.view.MotionEvent
4+
import app.revanced.integrations.shared.PlayerControlsVisibilityObserver
5+
import app.revanced.integrations.shared.PlayerControlsVisibilityObserverImpl
6+
import app.revanced.integrations.swipecontrols.SwipeControlsHostActivity
7+
import app.revanced.integrations.swipecontrols.controller.gesture.core.BaseGestureController
8+
import app.revanced.integrations.swipecontrols.controller.gesture.core.SwipeDetector
9+
import app.revanced.integrations.swipecontrols.misc.contains
10+
import app.revanced.integrations.swipecontrols.misc.toPoint
11+
12+
/**
13+
* provides the classic swipe controls experience, as it was with 'XFenster'
14+
*
15+
* @param controller reference to the main swipe controller
16+
*/
17+
class ClassicSwipeController(
18+
private val controller: SwipeControlsHostActivity
19+
) : BaseGestureController(controller),
20+
PlayerControlsVisibilityObserver by PlayerControlsVisibilityObserverImpl(controller) {
21+
/**
22+
* the last event captured in [onDown]
23+
*/
24+
private var lastOnDownEvent: MotionEvent? = null
25+
26+
override val shouldForceInterceptEvents: Boolean
27+
get() = currentSwipe == SwipeDetector.SwipeDirection.VERTICAL
28+
29+
override fun isInSwipeZone(motionEvent: MotionEvent): Boolean {
30+
val inVolumeZone = if (controller.config.enableVolumeControls)
31+
(motionEvent.toPoint() in controller.zones.volume) else false
32+
val inBrightnessZone = if (controller.config.enableBrightnessControl)
33+
(motionEvent.toPoint() in controller.zones.brightness) else false
34+
35+
return inVolumeZone || inBrightnessZone
36+
}
37+
38+
override fun shouldDropMotion(motionEvent: MotionEvent): Boolean {
39+
// ignore gestures with more than one pointer
40+
// when such a gesture is detected, dispatch the first event of the gesture to downstream
41+
if (motionEvent.pointerCount > 1) {
42+
lastOnDownEvent?.let {
43+
controller.dispatchDownstreamTouchEvent(it)
44+
it.recycle()
45+
}
46+
lastOnDownEvent = null
47+
return true
48+
}
49+
50+
// ignore gestures when player controls are visible
51+
return arePlayerControlsVisible
52+
}
53+
54+
override fun onDown(motionEvent: MotionEvent): Boolean {
55+
// save the event for later
56+
lastOnDownEvent?.recycle()
57+
lastOnDownEvent = MotionEvent.obtain(motionEvent)
58+
59+
// must be inside swipe zone
60+
return isInSwipeZone(motionEvent)
61+
}
62+
63+
override fun onSingleTapUp(motionEvent: MotionEvent): Boolean {
64+
MotionEvent.obtain(motionEvent).let {
65+
it.action = MotionEvent.ACTION_DOWN
66+
controller.dispatchDownstreamTouchEvent(it)
67+
it.recycle()
68+
}
69+
70+
return false
71+
}
72+
73+
override fun onDoubleTapEvent(motionEvent: MotionEvent?): Boolean {
74+
MotionEvent.obtain(motionEvent).let {
75+
controller.dispatchDownstreamTouchEvent(it)
76+
it.recycle()
77+
}
78+
79+
return super.onDoubleTapEvent(motionEvent)
80+
}
81+
82+
override fun onLongPress(motionEvent: MotionEvent?) {
83+
MotionEvent.obtain(motionEvent).let {
84+
controller.dispatchDownstreamTouchEvent(it)
85+
it.recycle()
86+
}
87+
88+
super.onLongPress(motionEvent)
89+
}
90+
91+
override fun onSwipe(
92+
from: MotionEvent,
93+
to: MotionEvent,
94+
distanceX: Double,
95+
distanceY: Double
96+
): Boolean {
97+
// cancel if not vertical
98+
if (currentSwipe != SwipeDetector.SwipeDirection.VERTICAL) return false
99+
return when (from.toPoint()) {
100+
in controller.zones.volume -> {
101+
scrollVolume(distanceY)
102+
true
103+
}
104+
in controller.zones.brightness -> {
105+
scrollBrightness(distanceY)
106+
true
107+
}
108+
else -> false
109+
}
110+
}
111+
}

app/src/main/java/app/revanced/integrations/swipecontrols/controller/gesture/NoPtSSwipeGestureController.kt

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package app.revanced.integrations.swipecontrols.controller.gesture
2+
3+
import android.view.MotionEvent
4+
import app.revanced.integrations.swipecontrols.SwipeControlsHostActivity
5+
import app.revanced.integrations.swipecontrols.controller.gesture.core.BaseGestureController
6+
import app.revanced.integrations.swipecontrols.controller.gesture.core.SwipeDetector
7+
import app.revanced.integrations.swipecontrols.misc.contains
8+
import app.revanced.integrations.swipecontrols.misc.toPoint
9+
10+
/**
11+
* provides the press-to-swipe (PtS) swipe controls experience
12+
*
13+
* @param controller reference to the main swipe controller
14+
*/
15+
class PressToSwipeController(
16+
private val controller: SwipeControlsHostActivity
17+
) : BaseGestureController(controller) {
18+
/**
19+
* monitors if the user is currently in a swipe session.
20+
*/
21+
private var isInSwipeSession = false
22+
23+
override val shouldForceInterceptEvents: Boolean
24+
get() = currentSwipe == SwipeDetector.SwipeDirection.VERTICAL && isInSwipeSession
25+
26+
override fun shouldDropMotion(motionEvent: MotionEvent): Boolean = false
27+
28+
override fun isInSwipeZone(motionEvent: MotionEvent): Boolean {
29+
val inVolumeZone = if (controller.config.enableVolumeControls)
30+
(motionEvent.toPoint() in controller.zones.volume) else false
31+
val inBrightnessZone = if (controller.config.enableBrightnessControl)
32+
(motionEvent.toPoint() in controller.zones.brightness) else false
33+
34+
return inVolumeZone || inBrightnessZone
35+
}
36+
37+
override fun onUp(motionEvent: MotionEvent) {
38+
super.onUp(motionEvent)
39+
isInSwipeSession = false
40+
}
41+
42+
override fun onLongPress(motionEvent: MotionEvent) {
43+
// enter swipe session with feedback
44+
isInSwipeSession = true
45+
controller.overlay.onEnterSwipeSession()
46+
47+
// send GestureDetector a ACTION_CANCEL event so it will handle further events
48+
motionEvent.action = MotionEvent.ACTION_CANCEL
49+
detector.onTouchEvent(motionEvent)
50+
}
51+
52+
override fun onSwipe(
53+
from: MotionEvent,
54+
to: MotionEvent,
55+
distanceX: Double,
56+
distanceY: Double
57+
): Boolean {
58+
// cancel if not in swipe session or vertical
59+
if (!isInSwipeSession || currentSwipe != SwipeDetector.SwipeDirection.VERTICAL) return false
60+
return when (from.toPoint()) {
61+
in controller.zones.volume -> {
62+
scrollVolume(distanceY)
63+
true
64+
}
65+
in controller.zones.brightness -> {
66+
scrollBrightness(distanceY)
67+
true
68+
}
69+
else -> false
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)