From 07a6b0a03f0e4c77a7e3e55631b2d858dc270e6f Mon Sep 17 00:00:00 2001 From: Christian Schabesberger Date: Mon, 5 Aug 2024 18:07:17 +0200 Subject: [PATCH] advance touch ui: make fullscreen and and embedded view switch possible --- .../newplayer/model/VideoPlayerViewModel.kt | 7 +- .../model/VideoPlayerViewModelImpl.kt | 26 ++- .../newplayer/ui/VideoPlayerControllerUI.kt | 38 +++- .../net/newpipe/newplayer/ui/VideoPlayerUI.kt | 5 +- .../net/newpipe/newplayer/ui/theme/Color.kt | 3 +- .../newplayer/ui/videoplayer/TouchUI.kt | 181 ++++++++++++++++-- 6 files changed, 230 insertions(+), 30 deletions(-) diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModel.kt b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModel.kt index da08da2..5b3316e 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModel.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModel.kt @@ -47,8 +47,13 @@ interface VideoPlayerViewModel { fun hideUi() fun seekPositionChanged(newValue: Float) fun seekingFinished() + fun embeddedDraggedDown(offset: Float) + fun fastSeekForward() + fun fastSeekBackward() interface Listener { - fun onFullscreenToggle(isFullscreen: Boolean) + fun onFullscreenToggle(isFullscreen: Boolean) {} + + fun embeddedPlayerDraggedDown(offset: Float) {} } } \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt index 2ad1fb7..0ed6867 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt @@ -263,6 +263,18 @@ class VideoPlayerViewModelImpl @Inject constructor( Log.i(TAG, "Seek to Ms: $seekPositionInMs") } + override fun embeddedDraggedDown(offset: Float) { + callbackListeners.forEach { it?.embeddedPlayerDraggedDown(offset) } + } + + override fun fastSeekForward() { + newPlayer?.fastSeekForward() + } + + override fun fastSeekBackward() { + newPlayer?.fastSeekBackward() + } + override fun switchToEmbeddedView() { callbackListeners.forEach { it?.onFullscreenToggle(false) } uiVisibilityJob?.cancel() @@ -330,13 +342,25 @@ class VideoPlayerViewModelImpl @Inject constructor( } override fun seekPositionChanged(newValue: Float) { - println("dummy impl") + println("dymmy seekPositionChanged: newValue: ${newValue}") } override fun seekingFinished() { println("dummy impl") } + override fun embeddedDraggedDown(offset: Float) { + println("dymmy embeddedDraggedDown: offset: ${offset}") + } + + override fun fastSeekForward() { + println("dummy impl") + } + + override fun fastSeekBackward() { + println("dummy impl") + } + override fun pause() { println("dummy pause") } diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerControllerUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerControllerUI.kt index bc57ea0..01ebe22 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerControllerUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerControllerUI.kt @@ -71,7 +71,10 @@ fun VideoPlayerControllerUI( showUi: () -> Unit, hideUi: () -> Unit, seekPositionChanged: (Float) -> Unit, - seekingFinished: () -> Unit + seekingFinished: () -> Unit, + embeddedDraggedDownBy: (Float) -> Unit, + fastSeekBackward: () -> Unit, + fastSeekForward: () -> Unit, ) { if (fullscreen) { @@ -88,12 +91,17 @@ fun VideoPlayerControllerUI( if (!uiVissible) { TouchUi( modifier = Modifier - .fillMaxSize() - .windowInsetsPadding(WindowInsets.systemGestures), + .fillMaxSize(), +// .windowInsetsPadding(WindowInsets.systemGestures), hideUi = hideUi, showUi = showUi, uiVissible = uiVissible, - fullscreen = fullscreen + fullscreen = fullscreen, + switchToFullscreen = switchToFullscreen, + switchToEmbeddedView = switchToEmbeddedView, + embeddedDraggedDownBy = embeddedDraggedDownBy, + fastSeekForward = fastSeekForward, + fastSeekBackward = fastSeekBackward ) } @@ -123,7 +131,12 @@ fun VideoPlayerControllerUI( hideUi = hideUi, showUi = showUi, uiVissible = uiVissible, - fullscreen = fullscreen + fullscreen = fullscreen, + switchToFullscreen = switchToFullscreen, + switchToEmbeddedView = switchToEmbeddedView, + embeddedDraggedDownBy = embeddedDraggedDownBy, + fastSeekForward = fastSeekForward, + fastSeekBackward = fastSeekBackward ) Box(modifier = Modifier.fillMaxSize()) { @@ -215,7 +228,10 @@ fun VideoPlayerControllerUIPreviewEmbedded() { showUi = {}, hideUi = {}, seekPositionChanged = {}, - seekingFinished = {}) + seekingFinished = {}, + embeddedDraggedDownBy = {}, + fastSeekBackward = {}, + fastSeekForward = {}) } } } @@ -242,7 +258,10 @@ fun VideoPlayerControllerUIPreviewLandscape() { showUi = {}, hideUi = {}, seekPositionChanged = {}, - seekingFinished = {}) + seekingFinished = {}, + embeddedDraggedDownBy = {}, + fastSeekBackward = {}, + fastSeekForward = {}) } } } @@ -270,7 +289,10 @@ fun VideoPlayerControllerUIPreviewPortrait() { showUi = {}, hideUi = {}, seekPositionChanged = {}, - seekingFinished = {}) + seekingFinished = {}, + embeddedDraggedDownBy = {}, + fastSeekForward = {}, + fastSeekBackward = {}) } } } \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerUI.kt index 32f362b..55507b1 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerUI.kt @@ -161,7 +161,10 @@ fun VideoPlayerUI( showUi = viewModel::showUi, hideUi = viewModel::hideUi, seekPositionChanged = viewModel::seekPositionChanged, - seekingFinished = viewModel::seekingFinished + seekingFinished = viewModel::seekingFinished, + embeddedDraggedDownBy = viewModel::embeddedDraggedDown, + fastSeekForward = viewModel::fastSeekForward, + fastSeekBackward = viewModel::fastSeekBackward ) } } diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/theme/Color.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/theme/Color.kt index 05c9264..ed80ba4 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/theme/Color.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/theme/Color.kt @@ -92,7 +92,8 @@ fun VideoPlayerControllerUIPreviewEmbeddedColorPreview() { showUi = {}, hideUi = {}, seekPositionChanged = {}, - seekingFinished = {}) + seekingFinished = {}, + embeddedDraggedDownBy = {}) } } } diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TouchUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TouchUI.kt index 415ffed..c3f36ca 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TouchUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TouchUI.kt @@ -20,15 +20,33 @@ package net.newpipe.newplayer.ui.videoplayer +import android.util.Log import android.view.MotionEvent import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInteropFilter +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +private const val TAG = "TouchUi" + +private data class TouchedPosition(val x: Float, val y: Float) { + operator fun minus(other: TouchedPosition) = TouchedPosition(this.x - other.x, this.y - other.y) +} + +const val DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS:Long = 150 @OptIn(ExperimentalComposeUiApi::class) @Composable @@ -38,32 +56,159 @@ fun TouchUi( showUi: () -> Unit, uiVissible: Boolean, fullscreen: Boolean, + switchToFullscreen: () -> Unit, + switchToEmbeddedView: () -> Unit, + embeddedDraggedDownBy: (Float) -> Unit, + fastSeekBackward: () -> Unit, + fastSeekForward: () -> Unit, ) { - Box(modifier = Modifier - .pointerInteropFilter { - when (it.action) { - MotionEvent.ACTION_DOWN -> { - true - } - MotionEvent.ACTION_UP -> { - if (uiVissible) { + var moveOccured by remember { + mutableStateOf(false) + } + + var lastTouchedPosition by remember { + mutableStateOf(TouchedPosition(0f, 0f)) + } + + var lastTouchTime by remember { + mutableStateOf(System.currentTimeMillis()) + } + + val composableScope = rememberCoroutineScope() + var showUiJob: Job? by remember{ + mutableStateOf(null) + } + + val defaultActionDown = { event: MotionEvent -> + lastTouchedPosition = TouchedPosition(event.x, event.y) + moveOccured = false + true + } + + val defaultActionUp = { onDoubleTap: () -> Unit -> + val currentTime = System.currentTimeMillis() + if (!moveOccured) { + val timeSinceLastTouch = currentTime - lastTouchTime + if(timeSinceLastTouch <= DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS) { + showUiJob?.cancel() + onDoubleTap() + } else { + if (uiVissible) { + showUiJob = composableScope.launch { + delay(DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS) hideUi() - } else { + } + + } else { + showUiJob = composableScope.launch { + delay(DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS) showUi() } - true } - - MotionEvent.ACTION_MOVE -> { - true - } - - else -> false } - }) { - Surface(color = Color.Transparent, modifier = Modifier.fillMaxSize()) { + } + moveOccured = false + lastTouchTime = currentTime + true + } + val handleMove = { event: MotionEvent, lambda: (movement: TouchedPosition) -> Unit -> + val currentTouchedPosition = TouchedPosition(event.x, event.y) + val movement = currentTouchedPosition - lastTouchedPosition + lastTouchedPosition = currentTouchedPosition + moveOccured = true + lambda(movement) + true + } + + if (fullscreen) { + Row(modifier = modifier) { + TouchSurface(modifier = Modifier.weight(1f) + .pointerInteropFilter { + when (it.action) { + MotionEvent.ACTION_DOWN -> defaultActionDown(it) + MotionEvent.ACTION_UP -> defaultActionUp(fastSeekBackward) + MotionEvent.ACTION_MOVE -> handleMove(it) { movement -> + + } + + else -> false + } + + }) + TouchSurface(modifier = Modifier + .weight(1f) + .pointerInteropFilter { + when (it.action) { + MotionEvent.ACTION_DOWN -> defaultActionDown(it) + MotionEvent.ACTION_UP -> defaultActionUp({}) + MotionEvent.ACTION_MOVE -> handleMove(it) { movement -> + if (0 < movement.y) { + switchToEmbeddedView() + } + } + + else -> false + } + }) + TouchSurface(modifier = Modifier.weight(1f) + .pointerInteropFilter { + when (it.action) { + MotionEvent.ACTION_DOWN -> defaultActionDown(it) + MotionEvent.ACTION_UP -> defaultActionUp(fastSeekForward) + MotionEvent.ACTION_MOVE -> handleMove(it) { movement -> + + } + + else -> false + } + }) + } + } else { + Row(modifier = modifier) { + TouchSurface(modifier = Modifier + .weight(1f) + .pointerInteropFilter { + when (it.action) { + MotionEvent.ACTION_DOWN -> defaultActionDown(it) + MotionEvent.ACTION_UP -> defaultActionUp(fastSeekBackward) + MotionEvent.ACTION_MOVE -> handleMove(it) { movement -> + Log.d(TAG, "${it.x}:${it.y}") + if (0 < movement.y) { + embeddedDraggedDownBy(movement.y) + } else { + switchToFullscreen() + } + } + + else -> false + } + }) + TouchSurface(modifier = Modifier + .weight(1f) + .pointerInteropFilter { + when (it.action) { + MotionEvent.ACTION_DOWN -> defaultActionDown(it) + MotionEvent.ACTION_UP -> defaultActionUp(fastSeekForward) + MotionEvent.ACTION_MOVE -> handleMove(it) { movement -> + if (0 < movement.y) { + embeddedDraggedDownBy(movement.y) + } else { + switchToFullscreen() + } + } + + else -> false + } + }) } } } + +@Composable +private fun TouchSurface(modifier: Modifier, color: Color = Color.Transparent) { + Box(modifier = modifier) { + Surface(color = color, modifier = Modifier.fillMaxSize()) {} + } +} \ No newline at end of file