add ui mode states

This commit is contained in:
Christian Schabesberger 2024-08-19 19:16:17 +02:00
parent 151ab85ea8
commit 8ac9a5a6ff
13 changed files with 256 additions and 80 deletions

53
misc/tiny_placeholder.svg Normal file
View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1280"
height="720"
viewBox="0 0 338.66667 190.5"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
sodipodi:docname="tiny_placeholder.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.53354917"
inkscape:cx="641.92772"
inkscape:cy="411.39601"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="vector-effect:non-scaling-stroke;fill:#000000;fill-opacity:0.2358;stroke:none;stroke-width:0.264583;-inkscape-stroke:hairline"
id="rect1"
width="338.66666"
height="190.5"
x="0"
y="0" />
<path
style="vector-effect:non-scaling-stroke;fill:#ffffff;fill-opacity:0.671068;stroke:none;stroke-width:0.264583;-inkscape-stroke:hairline"
d="M 125.56261,40.662967 V 149.83703 l 91.77479,-52.9862 z"
id="path1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -31,7 +31,7 @@ class ActivityBrainSlug(val viewModel: VideoPlayerViewModel) {
set(value) { set(value) {
field = value field = value
field?.let { field?.let {
if (viewModel.uiState.value.fullscreen) { if (viewModel.uiState.value.uiMode.fullscreen) {
removeSystemInsets() removeSystemInsets()
} else { } else {
addSystemInsets() addSystemInsets()
@ -43,7 +43,7 @@ class ActivityBrainSlug(val viewModel: VideoPlayerViewModel) {
var fullscreenPlayerView: VideoPlayerView? = null var fullscreenPlayerView: VideoPlayerView? = null
set(value) { set(value) {
field = value field = value
if (viewModel.uiState.value.fullscreen) { if (viewModel.uiState.value.uiMode.fullscreen) {
value?.visibility = View.VISIBLE value?.visibility = View.VISIBLE
field?.viewModel = viewModel field?.viewModel = viewModel
} else { } else {
@ -55,7 +55,7 @@ class ActivityBrainSlug(val viewModel: VideoPlayerViewModel) {
var embeddedPlayerView: VideoPlayerView? = null var embeddedPlayerView: VideoPlayerView? = null
set(value) { set(value) {
field = value field = value
if (viewModel.uiState.value.fullscreen) { if (viewModel.uiState.value.uiMode.fullscreen) {
field?.viewModel = null field?.viewModel = null
value?.visibility = View.GONE value?.visibility = View.GONE
} else { } else {
@ -83,7 +83,7 @@ class ActivityBrainSlug(val viewModel: VideoPlayerViewModel) {
fun addViewToHideOnFullscreen(view: View) { fun addViewToHideOnFullscreen(view: View) {
viewsToHideOnFullscreen.add(view) viewsToHideOnFullscreen.add(view)
if (viewModel.uiState.value.fullscreen) { if (viewModel.uiState.value.uiMode.fullscreen) {
view.visibility = View.GONE view.visibility = View.GONE
} }
} }

View File

@ -0,0 +1,96 @@
package net.newpipe.newplayer.model
enum class UIModeState {
PLACEHOLDER,
EMBEDDED_VIDEO,
EMBEDDED_VIDEO_CONTROLLER_UI,
EMBEDDED_VIDEO_CHAPTER_SELECT,
EMBEDDED_VIDEO_STREAM_SELECT,
FULLSCREEN_VIDEO,
FULLSCREEN_VIDEO_CONTROLLER_UI,
FULLSCREEN_VIDEO_CHAPTER_SELECT,
FULLSCREEN_VIDEO_STREAM_SELECT;
val fullscreen: Boolean
get() =
when (this) {
EMBEDDED_VIDEO_CHAPTER_SELECT -> true
EMBEDDED_VIDEO_STREAM_SELECT -> true
FULLSCREEN_VIDEO -> true
FULLSCREEN_VIDEO_CONTROLLER_UI -> true
FULLSCREEN_VIDEO_CHAPTER_SELECT -> true
FULLSCREEN_VIDEO_STREAM_SELECT -> true
else -> false
}
val controllerUiVisible: Boolean
get() =
when (this) {
EMBEDDED_VIDEO_CONTROLLER_UI -> true
FULLSCREEN_VIDEO_CONTROLLER_UI -> true
else -> false
}
val systemUiVisible: Boolean
get() =
when (this) {
FULLSCREEN_VIDEO -> false
else -> true
}
// STATE TRANSITIONS
fun getControllerUiVisibleState() =
when (this) {
EMBEDDED_VIDEO -> EMBEDDED_VIDEO_CONTROLLER_UI
FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO_CONTROLLER_UI
else -> this
}
fun getUiHiddenState() =
when (this) {
FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO
FULLSCREEN_VIDEO_CONTROLLER_UI -> FULLSCREEN_VIDEO
FULLSCREEN_VIDEO_CHAPTER_SELECT -> FULLSCREEN_VIDEO
FULLSCREEN_VIDEO_STREAM_SELECT -> FULLSCREEN_VIDEO
EMBEDDED_VIDEO -> EMBEDDED_VIDEO
EMBEDDED_VIDEO_CONTROLLER_UI -> EMBEDDED_VIDEO
EMBEDDED_VIDEO_CHAPTER_SELECT -> EMBEDDED_VIDEO
EMBEDDED_VIDEO_STREAM_SELECT -> EMBEDDED_VIDEO
else -> this
}
fun getStreamSelectUiState() =
when (this) {
FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO_STREAM_SELECT
FULLSCREEN_VIDEO_CHAPTER_SELECT -> FULLSCREEN_VIDEO_STREAM_SELECT
FULLSCREEN_VIDEO_CONTROLLER_UI -> FULLSCREEN_VIDEO_STREAM_SELECT
EMBEDDED_VIDEO -> EMBEDDED_VIDEO_STREAM_SELECT
EMBEDDED_VIDEO_CHAPTER_SELECT -> EMBEDDED_VIDEO_STREAM_SELECT
EMBEDDED_VIDEO_CONTROLLER_UI -> EMBEDDED_VIDEO_STREAM_SELECT
else -> this
}
fun getChapterSelectUiState() =
when (this) {
FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO_CHAPTER_SELECT
FULLSCREEN_VIDEO_STREAM_SELECT -> FULLSCREEN_VIDEO_CHAPTER_SELECT
FULLSCREEN_VIDEO_CONTROLLER_UI -> FULLSCREEN_VIDEO_CHAPTER_SELECT
EMBEDDED_VIDEO -> EMBEDDED_VIDEO_CHAPTER_SELECT
EMBEDDED_VIDEO_STREAM_SELECT -> EMBEDDED_VIDEO_CHAPTER_SELECT
EMBEDDED_VIDEO_CONTROLLER_UI -> EMBEDDED_VIDEO_CHAPTER_SELECT
else -> this
}
}

View File

@ -26,9 +26,8 @@ import net.newpipe.newplayer.ui.ContentScale
@Parcelize @Parcelize
data class VideoPlayerUIState( data class VideoPlayerUIState(
val uiMode: UIModeState,
val playing: Boolean, val playing: Boolean,
var fullscreen: Boolean,
val uiVisible: Boolean,
val contentRatio: Float, val contentRatio: Float,
val embeddedUiRatio: Float, val embeddedUiRatio: Float,
val contentFitMode: ContentScale, val contentFitMode: ContentScale,
@ -45,9 +44,8 @@ data class VideoPlayerUIState(
) : Parcelable { ) : Parcelable {
companion object { companion object {
val DEFAULT = VideoPlayerUIState( val DEFAULT = VideoPlayerUIState(
uiMode = UIModeState.PLACEHOLDER,
playing = false, playing = false,
fullscreen = false,
uiVisible = false,
contentRatio = 16 / 9f, contentRatio = 16 / 9f,
embeddedUiRatio = 16f / 9f, embeddedUiRatio = 16f / 9f,
contentFitMode = ContentScale.FIT_INSIDE, contentFitMode = ContentScale.FIT_INSIDE,

View File

@ -133,6 +133,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
override fun onVideoSizeChanged(videoSize: androidx.media3.common.VideoSize) { override fun onVideoSizeChanged(videoSize: androidx.media3.common.VideoSize) {
super.onVideoSizeChanged(videoSize) super.onVideoSizeChanged(videoSize)
updateContentRatio(VideoSize.fromMedia3VideoSize(videoSize)) updateContentRatio(VideoSize.fromMedia3VideoSize(videoSize))
} }
@ -210,7 +211,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
override fun showUi() { override fun showUi() {
mutableUiState.update { mutableUiState.update {
it.copy(uiVisible = true) it.copy(uiMode = it.uiMode.getControllerUiVisibleState())
} }
resetHideUiDelayedJob() resetHideUiDelayedJob()
resetProgressUpdatePeriodicallyJob() resetProgressUpdatePeriodicallyJob()
@ -255,7 +256,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
progressUpdaterJob?.cancel() progressUpdaterJob?.cancel()
uiVisibilityJob?.cancel() uiVisibilityJob?.cancel()
mutableUiState.update { mutableUiState.update {
it.copy(uiVisible = false) it.copy(uiMode = it.uiMode.getUiHiddenState())
} }
} }
@ -283,7 +284,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
) )
} }
if (mutableUiState.value.uiVisible) { if (mutableUiState.value.uiMode.controllerUiVisible) {
resetHideUiDelayedJob() resetHideUiDelayedJob()
} }
} }
@ -301,7 +302,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
} }
override fun brightnessChange(changeRate: Float, systemBrightness: Float) { override fun brightnessChange(changeRate: Float, systemBrightness: Float) {
if (mutableUiState.value.fullscreen) { if (mutableUiState.value.uiMode.fullscreen) {
val currentBrightness = mutableUiState.value.brightness val currentBrightness = mutableUiState.value.brightness
?: if (systemBrightness < 0f) 0.5f else systemBrightness ?: if (systemBrightness < 0f) 0.5f else systemBrightness
Log.d( Log.d(
@ -336,7 +337,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
uiVisibilityJob?.cancel() uiVisibilityJob?.cancel()
finishFastSeek() finishFastSeek()
mutableUiState.update { mutableUiState.update {
it.copy(fullscreen = false, uiVisible = false) it.copy(uiMode = UIModeState.EMBEDDED_VIDEO)
} }
} }
@ -345,7 +346,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
uiVisibilityJob?.cancel() uiVisibilityJob?.cancel()
finishFastSeek() finishFastSeek()
mutableUiState.update { mutableUiState.update {
it.copy(fullscreen = true, uiVisible = false) it.copy(uiMode = UIModeState.FULLSCREEN_VIDEO)
} }
} }

View File

@ -20,7 +20,6 @@
package net.newpipe.newplayer.ui package net.newpipe.newplayer.ui
import android.app.Activity import android.app.Activity
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
@ -57,15 +56,16 @@ import net.newpipe.newplayer.ui.videoplayer.TopUI
import net.newpipe.newplayer.ui.videoplayer.GestureUI import net.newpipe.newplayer.ui.videoplayer.GestureUI
import net.newpipe.newplayer.utils.getDefaultBrightness import net.newpipe.newplayer.utils.getDefaultBrightness
val CONTROLLER_UI_BACKGROUND_COLOR = Color(0x75000000)
@Composable @Composable
fun VideoPlayerControllerUI( fun VideoPlayerControllerUI(
viewModel: VideoPlayerViewModel, viewModel: VideoPlayerViewModel, uiState: VideoPlayerUIState
uiState: VideoPlayerUIState
) { ) {
val context = LocalContext.current val context = LocalContext.current
if (uiState.fullscreen) { if (uiState.uiMode.fullscreen) {
BackHandler { BackHandler {
viewModel.switchToEmbeddedView() viewModel.switchToEmbeddedView()
} }
@ -77,21 +77,16 @@ fun VideoPlayerControllerUI(
} }
val insets = val insets =
WindowInsets.systemBars WindowInsets.systemBars.union(WindowInsets.displayCutout).union(WindowInsets.waterfall)
.union(WindowInsets.displayCutout)
.union(WindowInsets.waterfall)
AnimatedVisibility(uiState.uiVisible) { AnimatedVisibility(uiState.uiMode.controllerUiVisible) {
Surface( Surface(
modifier = Modifier.fillMaxSize(), color = Color(0x75000000) modifier = Modifier.fillMaxSize(), color = CONTROLLER_UI_BACKGROUND_COLOR
) {} ) {}
} }
GestureUI( GestureUI(
modifier = Modifier modifier = Modifier.fillMaxSize(), viewModel = viewModel, uiState = uiState
.fillMaxSize(),
viewModel = viewModel,
uiState = uiState
) )
if (uiState.isLoading) { if (uiState.isLoading) {
@ -106,7 +101,7 @@ fun VideoPlayerControllerUI(
} }
} }
AnimatedVisibility(uiState.uiVisible) { AnimatedVisibility(uiState.uiMode.controllerUiVisible) {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
CenterUI( CenterUI(
@ -117,7 +112,7 @@ fun VideoPlayerControllerUI(
} }
Box( Box(
modifier = if (uiState.fullscreen) Modifier.windowInsetsPadding(insets) else Modifier modifier = if (uiState.uiMode.fullscreen) Modifier.windowInsetsPadding(insets) else Modifier
) { ) {
TopUI( TopUI(
modifier = Modifier modifier = Modifier

View File

@ -87,21 +87,21 @@ fun VideoPlayerUI(
val lifecycleOwner = LocalLifecycleOwner.current val lifecycleOwner = LocalLifecycleOwner.current
// Setup fullscreen // Setup fullscreen
if (uiState.fullscreen) { if (uiState.uiMode.fullscreen) {
LaunchedEffect(key1 = true) { LaunchedEffect(key1 = true) {
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = false WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = false
} }
} }
// Setup immersive mode // Setup immersive mode
if (uiState.fullscreen && !uiState.uiVisible) { if (uiState.uiMode.systemUiVisible) {
LaunchedEffect(key1 = true) {
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
}
} else {
LaunchedEffect(key1 = false) { LaunchedEffect(key1 = false) {
windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
} }
} else {
LaunchedEffect(key1 = true) {
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
}
} }
// Prepare stuff for the SurfaceView to which the video will be rendered // Prepare stuff for the SurfaceView to which the video will be rendered
@ -117,7 +117,7 @@ fun VideoPlayerUI(
} }
// Set Screen Rotation // Set Screen Rotation
if (uiState.fullscreen) { if (uiState.uiMode.fullscreen) {
if (uiState.contentRatio < 1) { if (uiState.contentRatio < 1) {
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
} else { } else {
@ -141,7 +141,7 @@ fun VideoPlayerUI(
// Set UI // Set UI
Surface( Surface(
modifier = Modifier.then( modifier = Modifier.then(
if (uiState.fullscreen) Modifier.fillMaxSize() if (uiState.uiMode.fullscreen) Modifier.fillMaxSize()
else Modifier else Modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(uiState.embeddedUiRatio) .aspectRatio(uiState.embeddedUiRatio)
@ -152,7 +152,8 @@ fun VideoPlayerUI(
player = viewModel.internalPlayer, player = viewModel.internalPlayer,
lifecycle = lifecycle, lifecycle = lifecycle,
fitMode = uiState.contentFitMode, fitMode = uiState.contentFitMode,
uiRatio = if (uiState.fullscreen) screenRatio else uiState.embeddedUiRatio, uiRatio = if (uiState.uiMode.fullscreen) screenRatio
else uiState.embeddedUiRatio,
contentRatio = uiState.contentRatio contentRatio = uiState.contentRatio
) )
} }

View File

@ -23,6 +23,7 @@ package net.newpipe.newplayer.ui.theme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import net.newpipe.newplayer.model.UIModeState
import net.newpipe.newplayer.model.VideoPlayerUIState import net.newpipe.newplayer.model.VideoPlayerUIState
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
import net.newpipe.newplayer.ui.PreviewBackgroundSurface import net.newpipe.newplayer.ui.PreviewBackgroundSurface
@ -77,9 +78,8 @@ fun VideoPlayerControllerUIPreviewEmbeddedColorPreview() {
VideoPlayerControllerUI( VideoPlayerControllerUI(
viewModel = VideoPlayerViewModelDummy(), viewModel = VideoPlayerViewModelDummy(),
uiState = VideoPlayerUIState.DEFAULT.copy( uiState = VideoPlayerUIState.DEFAULT.copy(
uiMode = UIModeState.EMBEDDED_VIDEO_CONTROLLER_UI,
playing = true, playing = true,
fullscreen = false,
uiVisible = true,
seekerPosition = 0.3f, seekerPosition = 0.3f,
isLoading = false, isLoading = false,
durationInMs = 9 * 60 * 1000, durationInMs = 9 * 60 * 1000,

View File

@ -42,6 +42,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.core.os.ConfigurationCompat import androidx.core.os.ConfigurationCompat
import net.newpipe.newplayer.R import net.newpipe.newplayer.R
import net.newpipe.newplayer.model.UIModeState
import net.newpipe.newplayer.model.VideoPlayerUIState import net.newpipe.newplayer.model.VideoPlayerUIState
import net.newpipe.newplayer.model.VideoPlayerViewModel import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
@ -77,11 +78,11 @@ fun BottomUI(
Text(getTimeStringFromMs(uiState.durationInMs, getLocale() ?: Locale.US)) Text(getTimeStringFromMs(uiState.durationInMs, getLocale() ?: Locale.US))
IconButton( IconButton(
onClick = if (uiState.fullscreen) viewModel::switchToEmbeddedView onClick = if (uiState.uiMode.fullscreen) viewModel::switchToEmbeddedView
else viewModel::switchToFullscreen else viewModel::switchToFullscreen
) { ) {
Icon( Icon(
imageVector = if (uiState.fullscreen) Icons.Filled.FullscreenExit imageVector = if (uiState.uiMode.fullscreen) Icons.Filled.FullscreenExit
else Icons.Filled.Fullscreen, else Icons.Filled.Fullscreen,
contentDescription = stringResource(R.string.widget_description_toggle_fullscreen) contentDescription = stringResource(R.string.widget_description_toggle_fullscreen)
) )
@ -152,7 +153,7 @@ fun VideoPlayerControllerBottomUIPreview() {
modifier = Modifier, modifier = Modifier,
viewModel = VideoPlayerViewModelDummy(), viewModel = VideoPlayerViewModelDummy(),
uiState = VideoPlayerUIState.DEFAULT.copy( uiState = VideoPlayerUIState.DEFAULT.copy(
fullscreen = true, uiMode = UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI,
seekerPosition = 0.4f, seekerPosition = 0.4f,
durationInMs = 90 * 60 * 1000, durationInMs = 90 * 60 * 1000,
playbackPositionInMs = 3 * 60 * 1000, playbackPositionInMs = 3 * 60 * 1000,

View File

@ -36,7 +36,7 @@ val INDICATOR_BACKGROUND_COLOR = Color.Black.copy(alpha = 0.3f)
fun GestureUI( fun GestureUI(
modifier: Modifier, viewModel: VideoPlayerViewModel, uiState: VideoPlayerUIState modifier: Modifier, viewModel: VideoPlayerViewModel, uiState: VideoPlayerUIState
) { ) {
if (uiState.fullscreen) { if (uiState.uiMode.fullscreen) {
FullscreenGestureUI( FullscreenGestureUI(
modifier = modifier, viewModel = viewModel, uiState = uiState modifier = modifier, viewModel = viewModel, uiState = uiState
) )

View File

@ -0,0 +1,49 @@
package net.newpipe.newplayer.ui.videoplayer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import net.newpipe.newplayer.model.VideoPlayerUIState
import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
import net.newpipe.newplayer.ui.CONTROLLER_UI_BACKGROUND_COLOR
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
@Composable
fun StreamSelectUI(
isChapterSelect: Boolean = false,
viewModel: VideoPlayerViewModel,
uiState: VideoPlayerUIState
) {
Surface(modifier = Modifier.fillMaxSize(), color = CONTROLLER_UI_BACKGROUND_COLOR) {
Scaffold(
topBar = {
}
) {
}
}
}
private fun ChapterSelectTopBar() {
}
@Preview(device = "id:pixel_5")
@Composable
fun VideoPlayerStreamSelectUIPreview() {
VideoPlayerTheme {
Surface(modifier = Modifier.fillMaxSize(), color = Color.Green) {
StreamSelectUI(
viewModel = VideoPlayerViewModelDummy(),
uiState = VideoPlayerUIState.DEFAULT
)
}
}
}

View File

@ -54,7 +54,7 @@ fun EmbeddedGestureUI(
} }
val defaultOnRegularTap = { val defaultOnRegularTap = {
if (uiState.uiVisible) { if (uiState.uiMode.controllerUiVisible) {
viewModel.hideUi() viewModel.hideUi()
} else { } else {
viewModel.showUi() viewModel.showUi()

View File

@ -44,6 +44,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import net.newpipe.newplayer.model.UIModeState
import net.newpipe.newplayer.model.VideoPlayerUIState import net.newpipe.newplayer.model.VideoPlayerUIState
import net.newpipe.newplayer.model.VideoPlayerViewModel import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
@ -51,16 +52,12 @@ import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.utils.getDefaultBrightness import net.newpipe.newplayer.utils.getDefaultBrightness
private enum class IndicatorMode { private enum class IndicatorMode {
NONE, NONE, VOLUME_INDICATOR_VISSIBLE, BRIGHTNESS_INDICATOR_VISSIBLE
VOLUME_INDICATOR_VISSIBLE,
BRIGHTNESS_INDICATOR_VISSIBLE
} }
@Composable @Composable
fun FullscreenGestureUI( fun FullscreenGestureUI(
modifier: Modifier = Modifier, modifier: Modifier = Modifier, viewModel: VideoPlayerViewModel, uiState: VideoPlayerUIState
viewModel: VideoPlayerViewModel,
uiState: VideoPlayerUIState
) { ) {
var heightPx by remember { var heightPx by remember {
@ -72,7 +69,7 @@ fun FullscreenGestureUI(
} }
val defaultOnRegularTap = { val defaultOnRegularTap = {
if (uiState.uiVisible) { if (uiState.uiMode.controllerUiVisible) {
viewModel.hideUi() viewModel.hideUi()
} else { } else {
viewModel.showUi() viewModel.showUi()
@ -87,9 +84,7 @@ fun FullscreenGestureUI(
heightPx = coordinates.size.height.toFloat() heightPx = coordinates.size.height.toFloat()
}) { }) {
Row { Row {
GestureSurface( GestureSurface(modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f),
onRegularTap = defaultOnRegularTap, onRegularTap = defaultOnRegularTap,
onMultiTap = { onMultiTap = {
println("multitap ${-it}") println("multitap ${-it}")
@ -100,20 +95,16 @@ fun FullscreenGestureUI(
indicatorMode = IndicatorMode.NONE indicatorMode = IndicatorMode.NONE
}, },
onMovement = { change -> onMovement = { change ->
if (indicatorMode == IndicatorMode.NONE if (indicatorMode == IndicatorMode.NONE || indicatorMode == IndicatorMode.BRIGHTNESS_INDICATOR_VISSIBLE) {
|| indicatorMode == IndicatorMode.BRIGHTNESS_INDICATOR_VISSIBLE
) {
indicatorMode = IndicatorMode.BRIGHTNESS_INDICATOR_VISSIBLE indicatorMode = IndicatorMode.BRIGHTNESS_INDICATOR_VISSIBLE
if (heightPx != 0f) { if (heightPx != 0f) {
viewModel.brightnessChange(-change.y / heightPx, defaultBrightness) viewModel.brightnessChange(-change.y / heightPx, defaultBrightness)
} }
} }
} }) {
) {
FadedAnimationForSeekFeedback( FadedAnimationForSeekFeedback(
uiState.fastSeekSeconds, uiState.fastSeekSeconds, backwards = true
backwards = true
) { fastSeekSecondsToDisplay -> ) { fastSeekSecondsToDisplay ->
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
FastSeekVisualFeedback( FastSeekVisualFeedback(
@ -124,19 +115,14 @@ fun FullscreenGestureUI(
} }
} }
} }
GestureSurface( GestureSurface(modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f),
onRegularTap = defaultOnRegularTap, onRegularTap = defaultOnRegularTap,
onMovement = { movement -> onMovement = { movement ->
if (0 < movement.y) { if (0 < movement.y) {
viewModel.switchToEmbeddedView() viewModel.switchToEmbeddedView()
} }
} })
) GestureSurface(modifier = Modifier.weight(1f),
GestureSurface(
modifier = Modifier
.weight(1f),
onRegularTap = defaultOnRegularTap, onRegularTap = defaultOnRegularTap,
onMultiTap = viewModel::fastSeek, onMultiTap = viewModel::fastSeek,
onMultiTapFinished = viewModel::finishFastSeek, onMultiTapFinished = viewModel::finishFastSeek,
@ -144,16 +130,13 @@ fun FullscreenGestureUI(
indicatorMode = IndicatorMode.NONE indicatorMode = IndicatorMode.NONE
}, },
onMovement = { change -> onMovement = { change ->
if (indicatorMode == IndicatorMode.NONE if (indicatorMode == IndicatorMode.NONE || indicatorMode == IndicatorMode.VOLUME_INDICATOR_VISSIBLE) {
|| indicatorMode == IndicatorMode.VOLUME_INDICATOR_VISSIBLE
) {
indicatorMode = IndicatorMode.VOLUME_INDICATOR_VISSIBLE indicatorMode = IndicatorMode.VOLUME_INDICATOR_VISSIBLE
if (heightPx != 0f) { if (heightPx != 0f) {
viewModel.volumeChange(-change.y / heightPx) viewModel.volumeChange(-change.y / heightPx)
} }
} }
} }) {
) {
FadedAnimationForSeekFeedback(uiState.fastSeekSeconds) { fastSeekSecondsToDisplay -> FadedAnimationForSeekFeedback(uiState.fastSeekSeconds) { fastSeekSecondsToDisplay ->
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
FastSeekVisualFeedback( FastSeekVisualFeedback(
@ -228,13 +211,11 @@ fun FullscreenGestureUIPreview() {
VideoPlayerTheme { VideoPlayerTheme {
Surface(modifier = Modifier.wrapContentSize(), color = Color.DarkGray) { Surface(modifier = Modifier.wrapContentSize(), color = Color.DarkGray) {
FullscreenGestureUI( FullscreenGestureUI(
modifier = Modifier, modifier = Modifier, object : VideoPlayerViewModelDummy() {
object : VideoPlayerViewModelDummy() {
override fun fastSeek(steps: Int) { override fun fastSeek(steps: Int) {
println("fast seek by $steps steps") println("fast seek by $steps steps")
} }
}, }, VideoPlayerUIState.DEFAULT
VideoPlayerUIState.DEFAULT
) )
} }
} }
@ -290,7 +271,8 @@ fun FullscreenGestureUIPreviewInteractive() {
} }
}, },
uiState = VideoPlayerUIState.DEFAULT.copy( uiState = VideoPlayerUIState.DEFAULT.copy(
uiVisible = uiVisible, uiMode = if (uiVisible) UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI
else UIModeState.FULLSCREEN_VIDEO,
fastSeekSeconds = seekSeconds, fastSeekSeconds = seekSeconds,
soundVolume = soundVolume, soundVolume = soundVolume,
brightness = brightnessValue brightness = brightnessValue