This repository has been archived by the owner on Oct 26, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 250
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: disable
swipe-controls
when player controls are visible (#123)
- Loading branch information
Showing
10 changed files
with
640 additions
and
269 deletions.
There are no files selected for viewing
84 changes: 84 additions & 0 deletions
84
app/src/main/java/app/revanced/integrations/shared/PlayerControlsVisibilityObserver.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package app.revanced.integrations.shared | ||
|
||
import android.app.Activity | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import app.revanced.integrations.utils.ReVancedUtils | ||
import java.lang.ref.WeakReference | ||
|
||
/** | ||
* default implementation of [PlayerControlsVisibilityObserver] | ||
* | ||
* @param activity activity that contains the controls_layout view | ||
*/ | ||
class PlayerControlsVisibilityObserverImpl( | ||
private val activity: Activity | ||
) : PlayerControlsVisibilityObserver { | ||
|
||
/** | ||
* id of the direct parent of controls_layout, R.id.youtube_controls_overlay | ||
*/ | ||
private val controlsLayoutParentId = | ||
ReVancedUtils.getResourceIdByName(activity, "id", "youtube_controls_overlay") | ||
|
||
/** | ||
* id of R.id.controls_layout | ||
*/ | ||
private val controlsLayoutId = | ||
ReVancedUtils.getResourceIdByName(activity, "id", "controls_layout") | ||
|
||
/** | ||
* reference to the controls layout view | ||
*/ | ||
private var controlsLayoutView = WeakReference<View>(null) | ||
|
||
/** | ||
* is the [controlsLayoutView] set to a valid reference of a view? | ||
*/ | ||
private val isAttached: Boolean | ||
get() { | ||
val view = controlsLayoutView.get() | ||
return view != null && view.parent != null | ||
} | ||
|
||
/** | ||
* find and attach the controls_layout view if needed | ||
*/ | ||
private fun maybeAttach() { | ||
if (isAttached) return | ||
|
||
// find parent, then controls_layout view | ||
// this is needed because there may be two views where id=R.id.controls_layout | ||
// because why should google confine themselves to their own guidelines... | ||
activity.findViewById<ViewGroup>(controlsLayoutParentId)?.let { parent -> | ||
parent.findViewById<View>(controlsLayoutId)?.let { | ||
controlsLayoutView = WeakReference(it) | ||
} | ||
} | ||
} | ||
|
||
override val playerControlsVisibility: Int | ||
get() { | ||
maybeAttach() | ||
return controlsLayoutView.get()?.visibility ?: View.GONE | ||
} | ||
|
||
override val arePlayerControlsVisible: Boolean | ||
get() = playerControlsVisibility == View.VISIBLE | ||
} | ||
|
||
/** | ||
* provides the visibility status of the fullscreen player controls_layout view. | ||
* this can be used for detecting when the player controls are shown | ||
*/ | ||
interface PlayerControlsVisibilityObserver { | ||
/** | ||
* current visibility int of the controls_layout view | ||
*/ | ||
val playerControlsVisibility: Int | ||
|
||
/** | ||
* is the value of [playerControlsVisibility] equal to [View.VISIBLE]? | ||
*/ | ||
val arePlayerControlsVisible: Boolean | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
...java/app/revanced/integrations/swipecontrols/controller/gesture/ClassicSwipeController.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package app.revanced.integrations.swipecontrols.controller.gesture | ||
|
||
import android.view.MotionEvent | ||
import app.revanced.integrations.shared.PlayerControlsVisibilityObserver | ||
import app.revanced.integrations.shared.PlayerControlsVisibilityObserverImpl | ||
import app.revanced.integrations.swipecontrols.SwipeControlsHostActivity | ||
import app.revanced.integrations.swipecontrols.controller.gesture.core.BaseGestureController | ||
import app.revanced.integrations.swipecontrols.controller.gesture.core.SwipeDetector | ||
import app.revanced.integrations.swipecontrols.misc.contains | ||
import app.revanced.integrations.swipecontrols.misc.toPoint | ||
|
||
/** | ||
* provides the classic swipe controls experience, as it was with 'XFenster' | ||
* | ||
* @param controller reference to the main swipe controller | ||
*/ | ||
class ClassicSwipeController( | ||
private val controller: SwipeControlsHostActivity | ||
) : BaseGestureController(controller), | ||
PlayerControlsVisibilityObserver by PlayerControlsVisibilityObserverImpl(controller) { | ||
/** | ||
* the last event captured in [onDown] | ||
*/ | ||
private var lastOnDownEvent: MotionEvent? = null | ||
|
||
override val shouldForceInterceptEvents: Boolean | ||
get() = currentSwipe == SwipeDetector.SwipeDirection.VERTICAL | ||
|
||
override fun isInSwipeZone(motionEvent: MotionEvent): Boolean { | ||
val inVolumeZone = if (controller.config.enableVolumeControls) | ||
(motionEvent.toPoint() in controller.zones.volume) else false | ||
val inBrightnessZone = if (controller.config.enableBrightnessControl) | ||
(motionEvent.toPoint() in controller.zones.brightness) else false | ||
|
||
return inVolumeZone || inBrightnessZone | ||
} | ||
|
||
override fun shouldDropMotion(motionEvent: MotionEvent): Boolean { | ||
// ignore gestures with more than one pointer | ||
// when such a gesture is detected, dispatch the first event of the gesture to downstream | ||
if (motionEvent.pointerCount > 1) { | ||
lastOnDownEvent?.let { | ||
controller.dispatchDownstreamTouchEvent(it) | ||
it.recycle() | ||
} | ||
lastOnDownEvent = null | ||
return true | ||
} | ||
|
||
// ignore gestures when player controls are visible | ||
return arePlayerControlsVisible | ||
} | ||
|
||
override fun onDown(motionEvent: MotionEvent): Boolean { | ||
// save the event for later | ||
lastOnDownEvent?.recycle() | ||
lastOnDownEvent = MotionEvent.obtain(motionEvent) | ||
|
||
// must be inside swipe zone | ||
return isInSwipeZone(motionEvent) | ||
} | ||
|
||
override fun onSingleTapUp(motionEvent: MotionEvent): Boolean { | ||
MotionEvent.obtain(motionEvent).let { | ||
it.action = MotionEvent.ACTION_DOWN | ||
controller.dispatchDownstreamTouchEvent(it) | ||
it.recycle() | ||
} | ||
|
||
return false | ||
} | ||
|
||
override fun onDoubleTapEvent(motionEvent: MotionEvent?): Boolean { | ||
MotionEvent.obtain(motionEvent).let { | ||
controller.dispatchDownstreamTouchEvent(it) | ||
it.recycle() | ||
} | ||
|
||
return super.onDoubleTapEvent(motionEvent) | ||
} | ||
|
||
override fun onLongPress(motionEvent: MotionEvent?) { | ||
MotionEvent.obtain(motionEvent).let { | ||
controller.dispatchDownstreamTouchEvent(it) | ||
it.recycle() | ||
} | ||
|
||
super.onLongPress(motionEvent) | ||
} | ||
|
||
override fun onSwipe( | ||
from: MotionEvent, | ||
to: MotionEvent, | ||
distanceX: Double, | ||
distanceY: Double | ||
): Boolean { | ||
// cancel if not vertical | ||
if (currentSwipe != SwipeDetector.SwipeDirection.VERTICAL) return false | ||
return when (from.toPoint()) { | ||
in controller.zones.volume -> { | ||
scrollVolume(distanceY) | ||
true | ||
} | ||
in controller.zones.brightness -> { | ||
scrollBrightness(distanceY) | ||
true | ||
} | ||
else -> false | ||
} | ||
} | ||
} |
28 changes: 0 additions & 28 deletions
28
...app/revanced/integrations/swipecontrols/controller/gesture/NoPtSSwipeGestureController.kt
This file was deleted.
Oops, something went wrong.
72 changes: 72 additions & 0 deletions
72
...java/app/revanced/integrations/swipecontrols/controller/gesture/PressToSwipeController.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package app.revanced.integrations.swipecontrols.controller.gesture | ||
|
||
import android.view.MotionEvent | ||
import app.revanced.integrations.swipecontrols.SwipeControlsHostActivity | ||
import app.revanced.integrations.swipecontrols.controller.gesture.core.BaseGestureController | ||
import app.revanced.integrations.swipecontrols.controller.gesture.core.SwipeDetector | ||
import app.revanced.integrations.swipecontrols.misc.contains | ||
import app.revanced.integrations.swipecontrols.misc.toPoint | ||
|
||
/** | ||
* provides the press-to-swipe (PtS) swipe controls experience | ||
* | ||
* @param controller reference to the main swipe controller | ||
*/ | ||
class PressToSwipeController( | ||
private val controller: SwipeControlsHostActivity | ||
) : BaseGestureController(controller) { | ||
/** | ||
* monitors if the user is currently in a swipe session. | ||
*/ | ||
private var isInSwipeSession = false | ||
|
||
override val shouldForceInterceptEvents: Boolean | ||
get() = currentSwipe == SwipeDetector.SwipeDirection.VERTICAL && isInSwipeSession | ||
|
||
override fun shouldDropMotion(motionEvent: MotionEvent): Boolean = false | ||
|
||
override fun isInSwipeZone(motionEvent: MotionEvent): Boolean { | ||
val inVolumeZone = if (controller.config.enableVolumeControls) | ||
(motionEvent.toPoint() in controller.zones.volume) else false | ||
val inBrightnessZone = if (controller.config.enableBrightnessControl) | ||
(motionEvent.toPoint() in controller.zones.brightness) else false | ||
|
||
return inVolumeZone || inBrightnessZone | ||
} | ||
|
||
override fun onUp(motionEvent: MotionEvent) { | ||
super.onUp(motionEvent) | ||
isInSwipeSession = false | ||
} | ||
|
||
override fun onLongPress(motionEvent: MotionEvent) { | ||
// enter swipe session with feedback | ||
isInSwipeSession = true | ||
controller.overlay.onEnterSwipeSession() | ||
|
||
// send GestureDetector a ACTION_CANCEL event so it will handle further events | ||
motionEvent.action = MotionEvent.ACTION_CANCEL | ||
detector.onTouchEvent(motionEvent) | ||
} | ||
|
||
override fun onSwipe( | ||
from: MotionEvent, | ||
to: MotionEvent, | ||
distanceX: Double, | ||
distanceY: Double | ||
): Boolean { | ||
// cancel if not in swipe session or vertical | ||
if (!isInSwipeSession || currentSwipe != SwipeDetector.SwipeDirection.VERTICAL) return false | ||
return when (from.toPoint()) { | ||
in controller.zones.volume -> { | ||
scrollVolume(distanceY) | ||
true | ||
} | ||
in controller.zones.brightness -> { | ||
scrollBrightness(distanceY) | ||
true | ||
} | ||
else -> false | ||
} | ||
} | ||
} |
Oops, something went wrong.