NewPipe/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt

285 lines
9.6 KiB
Kotlin
Raw Normal View History

2022-04-08 09:35:14 +02:00
package org.schabi.newpipe.player.gesture
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import androidx.core.view.isVisible
2022-04-08 09:35:14 +02:00
import org.schabi.newpipe.MainActivity
import org.schabi.newpipe.ktx.AnimationType
import org.schabi.newpipe.ktx.animate
import org.schabi.newpipe.player.ui.PopupPlayerUi
import kotlin.math.abs
import kotlin.math.hypot
import kotlin.math.max
import kotlin.math.min
class PopupPlayerGestureListener(
private val playerUi: PopupPlayerUi,
) : BasePlayerGestureListener(playerUi) {
private var isMoving = false
private var initialPopupX: Int = -1
private var initialPopupY: Int = -1
private var isResizing = false
// initial coordinates and distance between fingers
private var initPointerDistance = -1.0
private var initFirstPointerX = -1f
private var initFirstPointerY = -1f
private var initSecPointerX = -1f
private var initSecPointerY = -1f
override fun onTouch(v: View, event: MotionEvent): Boolean {
super.onTouch(v, event)
if (event.pointerCount == 2 && !isMoving && !isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.")
}
onPopupResizingStart()
// record coordinates of fingers
initFirstPointerX = event.getX(0)
initFirstPointerY = event.getY(0)
initSecPointerX = event.getX(1)
initSecPointerY = event.getY(1)
// record distance between fingers
initPointerDistance = hypot(
initFirstPointerX - initSecPointerX.toDouble(),
initFirstPointerY - initSecPointerY.toDouble()
)
isResizing = true
}
if (event.action == MotionEvent.ACTION_MOVE && !isMoving && isResizing) {
if (DEBUG) {
Log.d(
TAG,
"onTouch() ACTION_MOVE > v = [$v], e1.getRaw =" +
"[${event.rawX}, ${event.rawY}]"
)
}
return handleMultiDrag(event)
}
if (event.action == MotionEvent.ACTION_UP) {
if (DEBUG) {
Log.d(
TAG,
"onTouch() ACTION_UP > v = [$v], e1.getRaw =" +
" [${event.rawX}, ${event.rawY}]"
)
}
if (isMoving) {
isMoving = false
onScrollEnd(event)
}
if (isResizing) {
isResizing = false
initPointerDistance = (-1).toDouble()
initFirstPointerX = (-1).toFloat()
initFirstPointerY = (-1).toFloat()
initSecPointerX = (-1).toFloat()
initSecPointerY = (-1).toFloat()
onPopupResizingEnd()
player.changeState(player.currentState)
}
if (!playerUi.isPopupClosing) {
playerUi.savePopupPositionAndSizeToPrefs()
2022-04-08 09:35:14 +02:00
}
}
v.performClick()
return true
}
override fun onScrollEnd(event: MotionEvent) {
super.onScrollEnd(event)
if (playerUi.isInsideClosingRadius(event)) {
playerUi.closePopup()
} else if (!playerUi.isPopupClosing) {
playerUi.closeOverlayBinding.closeButton.animate(false, 200)
binding.closingOverlay.animate(false, 200)
}
}
private fun handleMultiDrag(event: MotionEvent): Boolean {
if (initPointerDistance == -1.0 || event.pointerCount != 2) {
return false
}
2022-04-08 09:35:14 +02:00
// get the movements of the fingers
val firstPointerMove = hypot(
event.getX(0) - initFirstPointerX.toDouble(),
event.getY(0) - initFirstPointerY.toDouble()
)
val secPointerMove = hypot(
event.getX(1) - initSecPointerX.toDouble(),
event.getY(1) - initSecPointerY.toDouble()
)
// minimum threshold beyond which pinch gesture will work
val minimumMove = ViewConfiguration.get(player.context).scaledTouchSlop
if (max(firstPointerMove, secPointerMove) <= minimumMove) {
return false
}
2022-04-08 09:35:14 +02:00
// calculate current distance between the pointers
val currentPointerDistance = hypot(
event.getX(0) - event.getX(1).toDouble(),
event.getY(0) - event.getY(1).toDouble()
)
2022-04-08 09:35:14 +02:00
val popupWidth = playerUi.popupLayoutParams.width.toDouble()
// change co-ordinates of popup so the center stays at the same position
val newWidth = popupWidth * currentPointerDistance / initPointerDistance
initPointerDistance = currentPointerDistance
playerUi.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt()
2022-04-08 09:35:14 +02:00
playerUi.checkPopupPositionBounds()
playerUi.updateScreenSize()
playerUi.changePopupSize(min(playerUi.screenWidth.toDouble(), newWidth).toInt())
return true
2022-04-08 09:35:14 +02:00
}
private fun onPopupResizingStart() {
if (DEBUG) {
Log.d(TAG, "onPopupResizingStart called")
}
binding.loadingPanel.visibility = View.GONE
playerUi.hideControls(0, 0)
binding.fastSeekOverlay.animate(false, 0)
binding.currentDisplaySeek.animate(false, 0, AnimationType.ALPHA, 0)
}
private fun onPopupResizingEnd() {
if (DEBUG) {
Log.d(TAG, "onPopupResizingEnd called")
}
}
override fun onLongPress(e: MotionEvent) {
2022-04-08 09:35:14 +02:00
playerUi.updateScreenSize()
playerUi.checkPopupPositionBounds()
playerUi.changePopupSize(playerUi.screenWidth)
}
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
2022-04-08 09:35:14 +02:00
velocityX: Float,
velocityY: Float
): Boolean {
return if (player.popupPlayerSelected()) {
val absVelocityX = abs(velocityX)
val absVelocityY = abs(velocityY)
if (absVelocityX.coerceAtLeast(absVelocityY) > TOSS_FLING_VELOCITY) {
if (absVelocityX > TOSS_FLING_VELOCITY) {
playerUi.popupLayoutParams.x = velocityX.toInt()
}
if (absVelocityY > TOSS_FLING_VELOCITY) {
playerUi.popupLayoutParams.y = velocityY.toInt()
}
playerUi.checkPopupPositionBounds()
playerUi.windowManager.updateViewLayout(binding.root, playerUi.popupLayoutParams)
return true
}
return false
} else {
true
}
}
override fun onDownNotDoubleTapping(e: MotionEvent): Boolean {
// Fix popup position when the user touch it, it may have the wrong one
// because the soft input is visible (the draggable area is currently resized).
playerUi.updateScreenSize()
playerUi.checkPopupPositionBounds()
playerUi.popupLayoutParams.let {
initialPopupX = it.x
initialPopupY = it.y
}
return true // we want `super.onDown(e)` to be called
}
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
if (DEBUG)
Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]")
if (isDoubleTapping)
return true
if (player.exoPlayerIsNull())
return false
onSingleTap()
return true
}
override fun onScroll(
initialEvent: MotionEvent,
movingEvent: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
if (isResizing) {
return super.onScroll(initialEvent, movingEvent, distanceX, distanceY)
}
if (!isMoving) {
playerUi.closeOverlayBinding.closeButton.animate(true, 200)
}
isMoving = true
2022-07-18 05:39:18 +02:00
val diffX = (movingEvent.rawX - initialEvent.rawX)
val posX = (initialPopupX + diffX).coerceIn(
0f,
(playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat()
.coerceAtLeast(0f)
2022-07-18 05:39:18 +02:00
)
val diffY = (movingEvent.rawY - initialEvent.rawY)
val posY = (initialPopupY + diffY).coerceIn(
0f,
(playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat()
.coerceAtLeast(0f)
2022-07-18 05:39:18 +02:00
)
2022-04-08 09:35:14 +02:00
playerUi.popupLayoutParams.x = posX.toInt()
playerUi.popupLayoutParams.y = posY.toInt()
// -- Determine if the ClosingOverlayView (red X) has to be shown or hidden --
val showClosingOverlayView: Boolean = playerUi.isInsideClosingRadius(movingEvent)
// Check if an view is in expected state and if not animate it into the correct state
if (binding.closingOverlay.isVisible != showClosingOverlayView) {
2022-04-08 09:35:14 +02:00
binding.closingOverlay.animate(showClosingOverlayView, 200)
}
playerUi.windowManager.updateViewLayout(binding.root, playerUi.popupLayoutParams)
return true
}
override fun getDisplayPortion(e: MotionEvent): DisplayPortion {
return when {
e.x < playerUi.popupLayoutParams.width / 3.0 -> DisplayPortion.LEFT
e.x > playerUi.popupLayoutParams.width * 2.0 / 3.0 -> DisplayPortion.RIGHT
else -> DisplayPortion.MIDDLE
}
}
override fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion {
return when {
e.x < playerUi.popupLayoutParams.width / 2.0 -> DisplayPortion.LEFT_HALF
else -> DisplayPortion.RIGHT_HALF
}
}
companion object {
private val TAG = PopupPlayerGestureListener::class.java.simpleName
private val DEBUG = MainActivity.DEBUG
private const val TOSS_FLING_VELOCITY = 2500
}
}