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

Commit 669cb29

Browse files
authored
fix: swipe-controls with active engagement panel (#82)
1 parent 2c2bdf6 commit 669cb29

9 files changed

Lines changed: 302 additions & 100 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package app.revanced.integrations.patches;
2+
3+
import android.view.ViewGroup;
4+
5+
import androidx.annotation.Nullable;
6+
7+
import app.revanced.integrations.shared.PlayerOverlays;
8+
9+
/**
10+
* Hook receiver class for 'player-overlays-hook' patch
11+
*
12+
* @usedBy app.revanced.patches.youtube.misc.playeroverlay.patch.PlayerOverlaysHookPatch
13+
* @smali Lapp/revanced/integrations/patches/PlayerOverlaysHookPatch;
14+
*/
15+
@SuppressWarnings("unused")
16+
public class PlayerOverlaysHookPatch {
17+
/**
18+
* Hook into YouTubePlayerOverlaysLayout.onFinishInflate() method
19+
*
20+
* @param thisRef reference to the view
21+
* @smali YouTubePlayerOverlaysLayout_onFinishInflateHook(Ljava / lang / Object ;)V
22+
*/
23+
public static void YouTubePlayerOverlaysLayout_onFinishInflateHook(@Nullable Object thisRef) {
24+
if (thisRef == null) return;
25+
if (thisRef instanceof ViewGroup) {
26+
PlayerOverlays.attach((ViewGroup) thisRef);
27+
}
28+
}
29+
}

app/src/main/java/app/revanced/integrations/patches/PlayerTypeHookPatch.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import androidx.annotation.Nullable;
44

55
import app.revanced.integrations.utils.LogHelper;
6-
import app.revanced.integrations.utils.PlayerType;
6+
import app.revanced.integrations.shared.PlayerType;
77

88
/**
99
* Hook receiver class for 'player-type-hook' patch
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package app.revanced.integrations.shared
2+
3+
import android.view.View
4+
import android.view.ViewGroup
5+
import app.revanced.integrations.swipecontrols.misc.Rectangle
6+
import app.revanced.integrations.utils.Event
7+
8+
/**
9+
* hooking class for player overlays
10+
*/
11+
@Suppress("MemberVisibilityCanBePrivate")
12+
object PlayerOverlays {
13+
14+
/**
15+
* called when the overlays finished inflating
16+
*/
17+
val onInflate = Event<ViewGroup>()
18+
19+
/**
20+
* called when new children are added or removed from the overlay
21+
*/
22+
val onChildrenChange = Event<ChildrenChangeEventArgs>()
23+
24+
/**
25+
* called when the overlay layout changes
26+
*/
27+
val onLayoutChange = Event<LayoutChangeEventArgs>()
28+
29+
/**
30+
* start listening for events on the provided view group
31+
*
32+
* @param overlaysLayout the overlays view group
33+
*/
34+
@JvmStatic
35+
fun attach(overlaysLayout: ViewGroup) {
36+
onInflate.invoke(overlaysLayout)
37+
overlaysLayout.setOnHierarchyChangeListener(object :
38+
ViewGroup.OnHierarchyChangeListener {
39+
override fun onChildViewAdded(parent: View?, child: View?) {
40+
if (parent is ViewGroup && child is View) {
41+
onChildrenChange(
42+
ChildrenChangeEventArgs(
43+
parent,
44+
child,
45+
false
46+
)
47+
)
48+
}
49+
}
50+
51+
override fun onChildViewRemoved(parent: View?, child: View?) {
52+
if (parent is ViewGroup && child is View) {
53+
onChildrenChange(
54+
ChildrenChangeEventArgs(
55+
parent,
56+
child,
57+
true
58+
)
59+
)
60+
}
61+
}
62+
})
63+
overlaysLayout.addOnLayoutChangeListener { view, newLeft, newTop, newRight, newBottom, oldLeft, oldTop, oldRight, oldBottom ->
64+
if (view is ViewGroup) {
65+
onLayoutChange(
66+
LayoutChangeEventArgs(
67+
view,
68+
Rectangle(
69+
oldLeft,
70+
oldTop,
71+
oldRight - oldLeft,
72+
oldBottom - oldTop
73+
),
74+
Rectangle(
75+
newLeft,
76+
newTop,
77+
newRight - newLeft,
78+
newBottom - newTop
79+
)
80+
)
81+
)
82+
}
83+
}
84+
}
85+
}
86+
87+
data class ChildrenChangeEventArgs(
88+
val overlaysLayout: ViewGroup,
89+
val childView: View,
90+
val wasChildRemoved: Boolean
91+
)
92+
93+
data class LayoutChangeEventArgs(
94+
val overlaysLayout: ViewGroup,
95+
val oldRect: Rectangle,
96+
val newRect: Rectangle
97+
)

app/src/main/java/app/revanced/integrations/utils/PlayerType.kt renamed to app/src/main/java/app/revanced/integrations/shared/PlayerType.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
package app.revanced.integrations.utils
1+
package app.revanced.integrations.shared
2+
3+
import app.revanced.integrations.utils.Event
24

35
/**
46
* WatchWhile player type

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package app.revanced.integrations.swipecontrols
33
import android.content.Context
44
import android.graphics.Color
55
import app.revanced.integrations.settings.SettingsEnum
6-
import app.revanced.integrations.utils.PlayerType
6+
import app.revanced.integrations.shared.PlayerType
77

88
/**
99
* provider for configuration for volume and brightness swipe controls
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package app.revanced.integrations.swipecontrols.controller
2+
3+
import android.content.Context
4+
import android.util.TypedValue
5+
import android.view.View
6+
import app.revanced.integrations.shared.LayoutChangeEventArgs
7+
import app.revanced.integrations.shared.PlayerOverlays
8+
import app.revanced.integrations.swipecontrols.misc.Rectangle
9+
import app.revanced.integrations.swipecontrols.misc.applyDimension
10+
import app.revanced.integrations.utils.ReVancedUtils
11+
12+
/**
13+
* Y- Axis:
14+
* -------- 0
15+
* ^
16+
* dead | 40dp
17+
* v
18+
* -------- yDeadTop
19+
* ^
20+
* swipe |
21+
* v
22+
* -------- yDeadBtm
23+
* ^
24+
* dead | 80dp
25+
* v
26+
* -------- screenHeight
27+
*
28+
* X- Axis:
29+
* 0 xBrigStart xBrigEnd xVolStart xVolEnd screenWidth
30+
* | | | | | |
31+
* | 20dp | 3/8 | 2/8 | 3/8 | 20dp |
32+
* | <------> | <------> | <------> | <------> | <------> |
33+
* | dead | brightness | dead | volume | dead |
34+
* | <--------------------------------> |
35+
* 1/1
36+
*/
37+
@Suppress("PrivatePropertyName")
38+
class SwipeZonesController(
39+
context: Context,
40+
private val fallbackScreenRect: () -> Rectangle
41+
) {
42+
/**
43+
* 20dp, in pixels
44+
*/
45+
private val _20dp = 20.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP)
46+
47+
/**
48+
* 40dp, in pixels
49+
*/
50+
private val _40dp = 40.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP)
51+
52+
/**
53+
* 80dp, in pixels
54+
*/
55+
private val _80dp = 80.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP)
56+
57+
/**
58+
* id for R.id.engagement_panel
59+
*/
60+
private val engagementPanelId =
61+
ReVancedUtils.getResourceIdByName(context, "id", "engagement_panel")
62+
63+
/**
64+
* current bounding rectangle of the player overlays
65+
*/
66+
private var playerRect: Rectangle? = null
67+
68+
/**
69+
* current bounding rectangle of the engagement_panel
70+
*/
71+
private var engagementPanelRect = Rectangle(0, 0, 0, 0)
72+
73+
/**
74+
* listener for player overlays layout change
75+
*/
76+
private fun onOverlaysLayoutChanged(args: LayoutChangeEventArgs) {
77+
// update engagement panel bounds
78+
val engagementPanel = args.overlaysLayout.findViewById<View>(engagementPanelId)
79+
engagementPanelRect =
80+
if (engagementPanel == null || engagementPanel.visibility != View.VISIBLE) {
81+
Rectangle(0, 0, 0, 0)
82+
} else {
83+
Rectangle(
84+
engagementPanel.x.toInt(),
85+
engagementPanel.y.toInt(),
86+
engagementPanel.width,
87+
engagementPanel.height
88+
)
89+
}
90+
91+
// update player bounds
92+
playerRect = args.newRect
93+
}
94+
95+
init {
96+
PlayerOverlays.onLayoutChange += this::onOverlaysLayoutChanged
97+
}
98+
99+
/**
100+
* rectangle of the area that is effectively usable for swipe controls
101+
*/
102+
private val effectiveSwipeRect: Rectangle
103+
get() {
104+
val p = if (playerRect != null) playerRect!! else fallbackScreenRect()
105+
return Rectangle(
106+
p.x + _20dp,
107+
p.y + _40dp,
108+
p.width - engagementPanelRect.width - _20dp,
109+
p.height - _20dp - _80dp
110+
)
111+
}
112+
113+
/**
114+
* the rectangle of the volume control zone
115+
*/
116+
val volume: Rectangle
117+
get() {
118+
val zoneWidth = (effectiveSwipeRect.width * 3) / 8
119+
return Rectangle(
120+
effectiveSwipeRect.right - zoneWidth,
121+
effectiveSwipeRect.top,
122+
zoneWidth,
123+
effectiveSwipeRect.height
124+
)
125+
}
126+
127+
/**
128+
* the rectangle of the screen brightness control zone
129+
*/
130+
val brightness: Rectangle
131+
get() {
132+
val zoneWidth = (effectiveSwipeRect.width * 3) / 8
133+
return Rectangle(
134+
effectiveSwipeRect.left,
135+
effectiveSwipeRect.top,
136+
zoneWidth,
137+
effectiveSwipeRect.height
138+
)
139+
}
140+
}

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import android.content.Context
44
import android.util.TypedValue
55
import android.view.GestureDetector
66
import android.view.MotionEvent
7-
import app.revanced.integrations.swipecontrols.views.SwipeControlsHostLayout
87
import app.revanced.integrations.swipecontrols.misc.ScrollDistanceHelper
98
import app.revanced.integrations.swipecontrols.misc.applyDimension
109
import app.revanced.integrations.swipecontrols.misc.contains
1110
import app.revanced.integrations.swipecontrols.misc.toPoint
11+
import app.revanced.integrations.swipecontrols.views.SwipeControlsHostLayout
1212
import app.revanced.integrations.utils.LogHelper
1313
import kotlin.math.abs
1414
import kotlin.math.pow
@@ -98,7 +98,24 @@ open class SwipeGestureController(
9898
onUp(motionEvent)
9999
}
100100

101-
return detector.onTouchEvent(motionEvent) or shouldForceInterceptEvents
101+
return if (shouldForceInterceptEvents || inSwipeZone(motionEvent)) {
102+
detector.onTouchEvent(motionEvent) or shouldForceInterceptEvents
103+
} else false
104+
}
105+
106+
/**
107+
* check if provided motion event is in any active swipe zone?
108+
*
109+
* @param e the event to check
110+
* @return is the event in any active swipe zone?
111+
*/
112+
open fun inSwipeZone(e: MotionEvent): Boolean {
113+
val inVolumeZone = if (controller.config.enableVolumeControls)
114+
(e.toPoint() in controller.zones.volume) else false
115+
val inBrightnessZone = if (controller.config.enableBrightnessControl)
116+
(e.toPoint() in controller.zones.brightness) else false
117+
118+
return inVolumeZone || inBrightnessZone
102119
}
103120

104121
/**
@@ -183,8 +200,8 @@ open class SwipeGestureController(
183200

184201
// then, process the event
185202
when (eFrom.toPoint()) {
186-
in controller.volumeZone -> volumeScroller.add(disY.toDouble())
187-
in controller.brightnessZone -> brightnessScroller.add(disY.toDouble())
203+
in controller.zones.volume -> volumeScroller.add(disY.toDouble())
204+
in controller.zones.brightness -> brightnessScroller.add(disY.toDouble())
188205
}
189206
return true
190207
}

0 commit comments

Comments
 (0)