LibreTube/app/src/main/java/com/github/libretube/util/PlayerGestureController.kt

131 lines
4.5 KiB
Kotlin
Raw Normal View History

package com.github.libretube.util
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import com.github.libretube.ui.interfaces.PlayerGestureOptions
import kotlin.math.abs
class PlayerGestureController(context: Context, private val listner: PlayerGestureOptions) :
View.OnTouchListener {
// width and height should be obtained each time using getter to adopt layout size changes.
private val width get() = Resources.getSystem().displayMetrics.widthPixels
private val height get() = Resources.getSystem().displayMetrics.heightPixels
private val orientation get() = Resources.getSystem().configuration.orientation
private val elapsedTime get() = SystemClock.elapsedRealtime()
private val handler: Handler = Handler(Looper.getMainLooper())
private val gestureDetector: GestureDetector
private var isMoving = false
var isEnabled = true
init {
gestureDetector = GestureDetector(context, GestureListener(), handler)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View, event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_UP && isMoving) {
isMoving = false
listner.onSwipeEnd()
}
// Event can be already consumed by some view which may lead to NPE.
try {
gestureDetector.onTouchEvent(event)
} catch (_: Exception) { }
// If orientation is landscape then allow `onScroll` to consume event and return true.
return orientation == Configuration.ORIENTATION_LANDSCAPE
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
private var lastClick = 0L
private var lastDoubleClick = 0L
private var xPos = 0.0F
override fun onDown(e: MotionEvent): Boolean {
if (isMoving) return false
if (isEnabled && isSecondClick()) {
handler.removeCallbacks(runnable)
lastDoubleClick = elapsedTime
val eventPositionPercentageX = xPos / width
when {
eventPositionPercentageX < 0.4 -> listner.onDoubleTapLeftScreen()
eventPositionPercentageX > 0.6 -> listner.onDoubleTapRightScreen()
else -> listner.onDoubleTapCenterScreen()
}
} else {
if (recentDoubleClick()) return true
handler.removeCallbacks(runnable)
handler.postDelayed(runnable, MAX_TIME_DIFF)
lastClick = elapsedTime
xPos = e.x
}
return true
}
override fun onScroll(
e1: MotionEvent,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
if (!isEnabled) return false
val insideThreshHold = abs(e2.y - e1.y) <= MOVEMENT_THRESHOLD
val insideBorder = (e1.x < BORDER_THRESHOLD || e1.y < BORDER_THRESHOLD || e1.x > width - BORDER_THRESHOLD || e1.y > height - BORDER_THRESHOLD)
// If the movement is inside threshold or scroll is horizontal then return false
if (
!isMoving && (
insideThreshHold || insideBorder ||
abs(distanceX) > abs(
distanceY
)
)
) {
return false
}
isMoving = true
when {
width * 0.5 > e1.x -> listner.onSwipeLeftScreen(distanceY)
width * 0.5 < e1.x -> listner.onSwipeRightScreen(distanceY)
}
return true
}
private val runnable = Runnable {
// If user is scrolling then avoid single tap call
if (isMoving || isSecondClick()) return@Runnable
listner.onSingleTap()
}
private fun isSecondClick(): Boolean {
return elapsedTime - lastClick < MAX_TIME_DIFF
}
private fun recentDoubleClick(): Boolean {
return elapsedTime - lastDoubleClick < MAX_TIME_DIFF / 2
}
}
companion object {
private const val MAX_TIME_DIFF = 400L
private const val MOVEMENT_THRESHOLD = 30
private const val BORDER_THRESHOLD = 90
}
}