add initial support for multitap gesture

This commit is contained in:
Christian Schabesberger 2024-08-06 18:00:41 +02:00
parent cc4dfe7721
commit 628ba4db1b
2 changed files with 51 additions and 38 deletions

View File

@ -21,25 +21,15 @@
package net.newpipe.newplayer.ui.videoplayer package net.newpipe.newplayer.ui.videoplayer
import android.util.Log import android.util.Log
import android.view.MotionEvent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -47,17 +37,9 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.newpipe.newplayer.R
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.FastSeekVisualFeedback import net.newpipe.newplayer.ui.videoplayer.gesture_ui.FastSeekVisualFeedback
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.TouchSurface import net.newpipe.newplayer.ui.videoplayer.gesture_ui.TouchSurface
@ -95,16 +77,17 @@ fun GestureUI(
} }
} }
var fastSeekModeBackward by remember { var fastSeekBackwardBy:Int by remember {
mutableStateOf(false) mutableStateOf(0)
} }
var fastSeekModeForward by remember { var fastSeekForwardBy:Int by remember {
mutableStateOf(false) mutableStateOf(0)
} }
val composeScope = rememberCoroutineScope() val composeScope = rememberCoroutineScope()
/*
val doForwardSeek = { val doForwardSeek = {
fastSeekModeForward = true fastSeekModeForward = true
composeScope.launch { composeScope.launch {
@ -128,16 +111,26 @@ fun GestureUI(
resetFastSeekModeEnd() resetFastSeekModeEnd()
fastSeekBackward() fastSeekBackward()
} }
*/
val onMultitapBackward = { amount: Int ->
fastSeekBackwardBy = amount * fastSeekSeconds
}
val onMultitapForward = { amount: Int ->
fastSeekForwardBy = amount * fastSeekSeconds
}
if (fullscreen) { if (fullscreen) {
Row(modifier = modifier) { Row(modifier = modifier) {
TouchSurface( TouchSurface(
modifier = Modifier modifier = Modifier
.weight(1f), .weight(1f),
multitapDurationInMs = FAST_SEEKMODE_DURATION,
onRegularTap = defaultOnRegularTap, onRegularTap = defaultOnRegularTap,
onDoubleTab = doBackwardSeek onMultiTap = onMultitapBackward
) { ) {
FadedAnimationForSeekFeedback(visible = fastSeekModeBackward) { FadedAnimationForSeekFeedback(visible = fastSeekForwardBy != 0) {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
FastSeekVisualFeedback( FastSeekVisualFeedback(
seconds = fastSeekSeconds, seconds = fastSeekSeconds,
@ -151,6 +144,7 @@ fun GestureUI(
modifier = Modifier modifier = Modifier
.weight(1f), .weight(1f),
onRegularTap = defaultOnRegularTap, onRegularTap = defaultOnRegularTap,
multitapDurationInMs = FAST_SEEKMODE_DURATION,
onMovement = { movement -> onMovement = { movement ->
if (0 < movement.y) { if (0 < movement.y) {
switchToEmbeddedView() switchToEmbeddedView()
@ -161,9 +155,10 @@ fun GestureUI(
modifier = Modifier modifier = Modifier
.weight(1f), .weight(1f),
onRegularTap = defaultOnRegularTap, onRegularTap = defaultOnRegularTap,
onDoubleTab = doForwardSeek multitapDurationInMs = FAST_SEEKMODE_DURATION,
onMultiTap = onMultitapForward
) { ) {
FadedAnimationForSeekFeedback(visible = fastSeekModeForward) { FadedAnimationForSeekFeedback(visible = fastSeekBackwardBy != 0) {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
FastSeekVisualFeedback( FastSeekVisualFeedback(
modifier = Modifier.align(Alignment.CenterStart), modifier = Modifier.align(Alignment.CenterStart),
@ -188,11 +183,12 @@ fun GestureUI(
TouchSurface( TouchSurface(
modifier = Modifier modifier = Modifier
.weight(1f), .weight(1f),
onDoubleTab = doBackwardSeek, multitapDurationInMs = FAST_SEEKMODE_DURATION,
onRegularTap = defaultOnRegularTap, onRegularTap = defaultOnRegularTap,
onMultiTap = onMultitapBackward,
onMovement = handleDownwardMovement onMovement = handleDownwardMovement
) { ) {
FadedAnimationForSeekFeedback(visible = fastSeekModeBackward) { FadedAnimationForSeekFeedback(visible = fastSeekBackwardBy != 0) {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
FastSeekVisualFeedback( FastSeekVisualFeedback(
modifier = Modifier.align(Alignment.Center), modifier = Modifier.align(Alignment.Center),
@ -205,11 +201,12 @@ fun GestureUI(
TouchSurface( TouchSurface(
modifier = Modifier modifier = Modifier
.weight(1f), .weight(1f),
onDoubleTab = doForwardSeek, multitapDurationInMs = FAST_SEEKMODE_DURATION,
onRegularTap = defaultOnRegularTap, onRegularTap = defaultOnRegularTap,
onMovement = handleDownwardMovement onMovement = handleDownwardMovement,
onMultiTap = onMultitapForward
) { ) {
FadedAnimationForSeekFeedback(visible = fastSeekModeForward) { FadedAnimationForSeekFeedback(visible = fastSeekForwardBy != 0) {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
FastSeekVisualFeedback( FastSeekVisualFeedback(
modifier = Modifier.align(Alignment.Center), modifier = Modifier.align(Alignment.Center),
@ -220,6 +217,7 @@ fun GestureUI(
} }
} }
} }
} }
} }

View File

@ -37,14 +37,15 @@ import androidx.compose.ui.input.pointer.pointerInteropFilter
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.newpipe.newplayer.ui.videoplayer.DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS
@Composable @Composable
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
fun TouchSurface( fun TouchSurface(
modifier: Modifier, modifier: Modifier,
color: Color = Color.Transparent, color: Color = Color.Transparent,
onDoubleTab: () -> Unit = {}, multitapDurationInMs: Long,
onMultiTap: (Int) -> Unit = {},
onMultiTapFinished: () -> Unit = {},
onRegularTap: () -> Unit = {}, onRegularTap: () -> Unit = {},
onMovement: (TouchedPosition) -> Unit = {}, onMovement: (TouchedPosition) -> Unit = {},
content: @Composable () -> Unit = {} content: @Composable () -> Unit = {}
@ -72,16 +73,30 @@ fun TouchSurface(
true true
} }
val defaultActionUp = { onDoubleTap: () -> Unit, onRegularTap: () -> Unit -> var multitapAmount:Int by remember {
mutableStateOf(0)
}
var cancelMultitapJob: Job? by remember {
mutableStateOf(null)
}
val defaultActionUp = { onMultiTap: (Int) -> Unit, onRegularTap: () -> Unit ->
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
if (!moveOccured) { if (!moveOccured) {
val timeSinceLastTouch = currentTime - lastTouchTime val timeSinceLastTouch = currentTime - lastTouchTime
if (timeSinceLastTouch <= DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS) { if (timeSinceLastTouch <= multitapDurationInMs) {
regularTabJob?.cancel() regularTabJob?.cancel()
onDoubleTap() cancelMultitapJob?.cancel()
multitapAmount++
onMultiTap(multitapAmount)
cancelMultitapJob = composableScope.launch {
delay(multitapDurationInMs)
onMultiTapFinished()
}
} else { } else {
regularTabJob = composableScope.launch { regularTabJob = composableScope.launch {
delay(DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS) delay(multitapDurationInMs)
onRegularTap() onRegularTap()
} }
} }
@ -103,7 +118,7 @@ fun TouchSurface(
Box(modifier = modifier.pointerInteropFilter { Box(modifier = modifier.pointerInteropFilter {
when (it.action) { when (it.action) {
MotionEvent.ACTION_DOWN -> defaultActionDown(it) MotionEvent.ACTION_DOWN -> defaultActionDown(it)
MotionEvent.ACTION_UP -> defaultActionUp(onDoubleTab, onRegularTap) MotionEvent.ACTION_UP -> defaultActionUp(onMultiTap, onRegularTap)
MotionEvent.ACTION_MOVE -> handleMove(it, onMovement) MotionEvent.ACTION_MOVE -> handleMove(it, onMovement)
else -> false else -> false