revanced-integrations/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/BaseGestureController.kt

157 lines
4.8 KiB
Kotlin

package app.revanced.integrations.youtube.swipecontrols.controller.gesture.core
import android.view.GestureDetector
import android.view.MotionEvent
import app.revanced.integrations.youtube.swipecontrols.SwipeControlsHostActivity
/**
* the common base of all [GestureController] classes.
* handles most of the boilerplate code needed for gesture detection
*
* @param controller reference to the main swipe controller
*/
abstract class BaseGestureController(
private val controller: SwipeControlsHostActivity
) : GestureController,
GestureDetector.SimpleOnGestureListener(),
SwipeDetector by SwipeDetectorImpl(
controller.config.swipeMagnitudeThreshold.toDouble()
),
VolumeAndBrightnessScroller by VolumeAndBrightnessScrollerImpl(
controller,
controller.audio,
controller.screen,
controller.overlay,
10,
1
) {
/**
* the main gesture detector that powers everything
*/
@Suppress("LeakingThis")
protected val detector = GestureDetector(controller, this)
/**
* were downstream event cancelled already? used in [onScroll]
*/
private var didCancelDownstream = false
override fun submitTouchEvent(motionEvent: MotionEvent): Boolean {
// ignore if swipe is disabled
if (!controller.config.enableSwipeControls) {
return false
}
// create a copy of the event so we can modify it
// without causing any issues downstream
val me = MotionEvent.obtain(motionEvent)
// check if we should drop this motion
val dropped = shouldDropMotion(me)
if (dropped) {
me.action = MotionEvent.ACTION_CANCEL
}
// send the event to the detector
// if we force intercept events, the event is always consumed
val consumed = detector.onTouchEvent(me) || shouldForceInterceptEvents
// invoke the custom onUp handler
if (me.action == MotionEvent.ACTION_UP || me.action == MotionEvent.ACTION_CANCEL) {
onUp(me)
}
// recycle the copy
me.recycle()
// do not consume dropped events
// or events outside of any swipe zone
return !dropped && consumed && isInSwipeZone(me)
}
/**
* custom handler for [MotionEvent.ACTION_UP] event, because GestureDetector doesn't offer that :|
*
* @param motionEvent the motion event
*/
open fun onUp(motionEvent: MotionEvent) {
didCancelDownstream = false
resetSwipe()
resetScroller()
}
override fun onScroll(
from: MotionEvent,
to: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
// submit to swipe detector
submitForSwipe(from, to, distanceX, distanceY)
// call swipe callback if in a swipe
return if (currentSwipe != SwipeDetector.SwipeDirection.NONE) {
val consumed = onSwipe(
from,
to,
distanceX.toDouble(),
distanceY.toDouble()
)
// if the swipe was consumed, cancel downstream events once
if (consumed && !didCancelDownstream) {
didCancelDownstream = true
MotionEvent.obtain(from).let {
it.action = MotionEvent.ACTION_CANCEL
controller.dispatchDownstreamTouchEvent(it)
it.recycle()
}
}
consumed
} else {
false
}
}
/**
* should [submitTouchEvent] force- intercept all touch events?
*/
abstract val shouldForceInterceptEvents: Boolean
/**
* check if provided motion event is in any active swipe zone?
*
* @param motionEvent the event to check
* @return is the event in any active swipe zone?
*/
abstract fun isInSwipeZone(motionEvent: MotionEvent): Boolean
/**
* check if a touch event should be dropped.
* when a event is dropped, the gesture detector received a [MotionEvent.ACTION_CANCEL] event and the event is not consumed
*
* @param motionEvent the event to check
* @return should the event be dropped?
*/
abstract fun shouldDropMotion(motionEvent: MotionEvent): Boolean
/**
* handler for swipe events, once a swipe is detected.
* the direction of the swipe can be accessed in [currentSwipe]
*
* @param from start event of the swipe
* @param to end event of the swipe
* @param distanceX the horizontal distance of the swipe
* @param distanceY the vertical distance of the swipe
* @return was the event consumed?
*/
abstract fun onSwipe(
from: MotionEvent,
to: MotionEvent,
distanceX: Double,
distanceY: Double
): Boolean
}