restructure code and remove boilerplate by propagating viewmodel and uistate

This commit is contained in:
Christian Schabesberger 2024-08-09 14:34:18 +02:00
parent 0d6227071e
commit 819dc80387
12 changed files with 294 additions and 501 deletions

View File

@ -38,7 +38,7 @@ data class VideoPlayerUIState(
val isLoading: Boolean,
val durationInMs: Long,
val playbackPositionInMs: Long,
val fastseekSeconds: Int,
val fastSeekSeconds: Int,
val soundVolume: Float,
// when null use system value
@ -58,7 +58,7 @@ data class VideoPlayerUIState(
isLoading = true,
durationInMs = 0,
playbackPositionInMs = 0,
fastseekSeconds = 0,
fastSeekSeconds = 0,
soundVolume = 0f,
brightness = null
)

View File

@ -279,7 +279,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
override fun fastSeek(count: Int) {
mutableUiState.update {
it.copy(
fastseekSeconds = count * (newPlayer?.fastSeekAmountSec ?: 10)
fastSeekSeconds = count * (newPlayer?.fastSeekAmountSec ?: 10)
)
}
@ -289,13 +289,13 @@ class VideoPlayerViewModelImpl @Inject constructor(
}
override fun finishFastSeek() {
val fastSeekAmount = mutableUiState.value.fastseekSeconds
val fastSeekAmount = mutableUiState.value.fastSeekSeconds
if (fastSeekAmount != 0) {
Log.d(TAG, "$fastSeekAmount")
newPlayer?.currentPosition = (newPlayer?.currentPosition ?: 0) + (fastSeekAmount * 1000)
mutableUiState.update {
it.copy(fastseekSeconds = 0)
it.copy(fastSeekSeconds = 0)
}
}
}
@ -356,84 +356,4 @@ class VideoPlayerViewModelImpl @Inject constructor(
} ?: minContentRatio
companion object {
val dummy = object : VideoPlayerViewModel {
override var newPlayer: NewPlayer? = null
override val internalPlayer: Player? = null
override val uiState = MutableStateFlow(VideoPlayerUIState.DEFAULT)
override var minContentRatio = 4F / 3F
override var maxContentRatio = 16F / 9F
override var contentFitMode = ContentScale.FIT_INSIDE
override fun initUIState(instanceState: Bundle) {
println("dummy impl")
}
override fun addCallbackListener(listener: VideoPlayerViewModel.Listener) {
println("dummy impl")
}
override fun play() {
println("dummy impl")
}
override fun switchToEmbeddedView() {
println("dummy impl")
}
override fun switchToFullscreen() {
println("dummy impl")
}
override fun showUi() {
println("dummy impl")
}
override fun hideUi() {
println("dummy impl")
}
override fun seekPositionChanged(newValue: Float) {
println("dymmy seekPositionChanged: newValue: ${newValue}")
}
override fun seekingFinished() {
println("dummy impl")
}
override fun embeddedDraggedDown(offset: Float) {
println("dymmy embeddedDraggedDown: offset: ${offset}")
}
override fun fastSeek(steps: Int) {
println("dummy impl")
}
override fun finishFastSeek() {
println("dummy impl")
}
override fun brightnessChange(changeRate: Float, currentValue: Float) {
println("dummy impl")
}
override fun volumeChange(changeRate: Float) {
println("dummy impl")
}
override fun pause() {
println("dummy pause")
}
override fun prevStream() {
println("dummy impl")
}
override fun nextStream() {
println("dummy impl")
}
}
}
}

View File

@ -0,0 +1,84 @@
package net.newpipe.newplayer.model
import android.os.Bundle
import androidx.media3.common.Player
import kotlinx.coroutines.flow.MutableStateFlow
import net.newpipe.newplayer.NewPlayer
import net.newpipe.newplayer.ui.ContentScale
open class VideoPlayerViewModelDummy : VideoPlayerViewModel {
override var newPlayer: NewPlayer? = null
override val internalPlayer: Player? = null
override val uiState = MutableStateFlow(VideoPlayerUIState.DEFAULT)
override var minContentRatio = 4F / 3F
override var maxContentRatio = 16F / 9F
override var contentFitMode = ContentScale.FIT_INSIDE
override fun initUIState(instanceState: Bundle) {
println("dummy impl")
}
override fun addCallbackListener(listener: VideoPlayerViewModel.Listener) {
println("dummy impl")
}
override fun play() {
println("dummy impl")
}
override fun switchToEmbeddedView() {
println("dummy impl")
}
override fun switchToFullscreen() {
println("dummy impl")
}
override fun showUi() {
println("dummy impl")
}
override fun hideUi() {
println("dummy impl")
}
override fun seekPositionChanged(newValue: Float) {
println("dymmy seekPositionChanged: newValue: ${newValue}")
}
override fun seekingFinished() {
println("dummy impl")
}
override fun embeddedDraggedDown(offset: Float) {
println("dymmy embeddedDraggedDown: offset: ${offset}")
}
override fun fastSeek(steps: Int) {
println("dummy impl")
}
override fun finishFastSeek() {
println("dummy impl")
}
override fun brightnessChange(changeRate: Float, currentValue: Float) {
println("dummy impl")
}
override fun volumeChange(changeRate: Float) {
println("dummy impl")
}
override fun pause() {
println("dummy pause")
}
override fun prevStream() {
println("dummy impl")
}
override fun nextStream() {
println("dummy impl")
}
}

View File

@ -48,54 +48,34 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import net.newpipe.newplayer.model.VideoPlayerUIState
import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
import net.newpipe.newplayer.model.VideoPlayerViewModelImpl
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.ui.videoplayer.BottomUI
import net.newpipe.newplayer.ui.videoplayer.CenterUI
import net.newpipe.newplayer.ui.videoplayer.TopUI
import net.newpipe.newplayer.ui.videoplayer.GestureUI
import net.newpipe.newplayer.utils.getScreenBrightnes
import net.newpipe.newplayer.utils.getDefaultBrightness
@Composable
fun VideoPlayerControllerUI(
isPlaying: Boolean,
fullscreen: Boolean,
uiVissible: Boolean,
seekPosition: Float,
isLoading: Boolean,
durationInMs: Long,
playbackPositionInMs: Long,
bufferedPercentage: Float,
fastSeekSeconds: Int,
soundVolume: Float,
brightnes: Float,
play: () -> Unit,
pause: () -> Unit,
prevStream: () -> Unit,
nextStream: () -> Unit,
switchToFullscreen: () -> Unit,
switchToEmbeddedView: () -> Unit,
showUi: () -> Unit,
hideUi: () -> Unit,
seekPositionChanged: (Float) -> Unit,
seekingFinished: () -> Unit,
embeddedDraggedDownBy: (Float) -> Unit,
fastSeek: (Int) -> Unit,
finishFastSeek: () -> Unit,
brightnessChange: (Float, Float) -> Unit,
volumeChange: (Float) -> Unit
viewModel: VideoPlayerViewModel,
uiState: VideoPlayerUIState
) {
val context = LocalContext.current
if (fullscreen) {
if (uiState.fullscreen) {
BackHandler {
switchToEmbeddedView()
viewModel.switchToEmbeddedView()
}
}
val internalBrightnessChange = { rateChange: Float ->
val systemBrightness = getScreenBrightnes(context as Activity)
brightnessChange(rateChange, systemBrightness)
val systemBrightness = getDefaultBrightness(context as Activity)
viewModel.brightnessChange(rateChange, systemBrightness)
}
val insets =
@ -103,35 +83,23 @@ fun VideoPlayerControllerUI(
.union(WindowInsets.displayCutout)
.union(WindowInsets.waterfall)
if (!uiVissible) {
if (!uiState.uiVissible) {
GestureUI(
modifier = Modifier
.fillMaxSize(),
// .windowInsetsPadding(WindowInsets.systemGestures),
hideUi = hideUi,
showUi = showUi,
uiVissible = uiVissible,
fullscreen = fullscreen,
fastSeekSeconds = fastSeekSeconds,
brightnes = brightnes,
soundVolume = soundVolume,
switchToFullscreen = switchToFullscreen,
switchToEmbeddedView = switchToEmbeddedView,
embeddedDraggedDownBy = embeddedDraggedDownBy,
fastSeek = fastSeek,
fastSeekFinished = finishFastSeek,
brightnessChange = internalBrightnessChange,
volumeChange = volumeChange
viewModel = viewModel,
uiState = uiState
)
}
AnimatedVisibility(uiVissible) {
AnimatedVisibility(uiState.uiVissible) {
Surface(
modifier = Modifier.fillMaxSize(), color = Color(0x75000000)
) {}
}
if (isLoading) {
if (uiState.isLoading) {
Box(modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator(
modifier = Modifier
@ -143,41 +111,25 @@ fun VideoPlayerControllerUI(
}
}
AnimatedVisibility(uiVissible) {
AnimatedVisibility(uiState.uiVissible) {
GestureUI(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.systemGestures),
hideUi = hideUi,
showUi = showUi,
uiVissible = uiVissible,
fullscreen = fullscreen,
fastSeekSeconds = fastSeekSeconds,
soundVolume = soundVolume,
brightnes = brightnes,
switchToFullscreen = switchToFullscreen,
switchToEmbeddedView = switchToEmbeddedView,
embeddedDraggedDownBy = embeddedDraggedDownBy,
fastSeek = fastSeek,
fastSeekFinished = finishFastSeek,
volumeChange = volumeChange,
brightnessChange = internalBrightnessChange
viewModel = viewModel,
uiState = uiState
)
Box(modifier = Modifier.fillMaxSize()) {
CenterUI(
modifier = Modifier.align(Alignment.Center),
isPlaying = isPlaying,
isLoading = isLoading,
play = play,
pause = pause,
prevStream = prevStream,
nextStream = nextStream
viewModel = viewModel,
uiState = uiState
)
}
Box(
modifier = if (fullscreen) Modifier.windowInsetsPadding(insets) else Modifier
modifier = if (uiState.fullscreen) Modifier.windowInsetsPadding(insets) else Modifier
) {
TopUI(
modifier = Modifier
@ -193,15 +145,8 @@ fun VideoPlayerControllerUI(
.padding(start = 16.dp, end = 16.dp)
.defaultMinSize(minHeight = 40.dp)
.fillMaxWidth(),
isFullscreen = fullscreen,
durationInMs = durationInMs,
playbackPositionInMs = playbackPositionInMs,
seekPosition = seekPosition,
bufferedPercentage = bufferedPercentage,
switchToFullscreen = switchToFullscreen,
switchToEmbeddedView = switchToEmbeddedView,
seekPositionChanged = seekPositionChanged,
seekingFinished = seekingFinished
viewModel = viewModel,
uiState = uiState
)
}
}
@ -236,32 +181,7 @@ fun PreviewBackgroundSurface(
fun VideoPlayerControllerUIPreviewEmbedded() {
VideoPlayerTheme {
PreviewBackgroundSurface {
VideoPlayerControllerUI(isPlaying = false,
fullscreen = false,
uiVissible = true,
seekPosition = 0.3F,
isLoading = false,
durationInMs = 9 * 60 * 1000,
playbackPositionInMs = 6 * 60 * 1000,
bufferedPercentage = 0.4f,
fastSeekSeconds = 0,
soundVolume = 0f,
brightnes = 0f,
play = {},
pause = {},
prevStream = {},
nextStream = {},
switchToFullscreen = {},
switchToEmbeddedView = {},
showUi = {},
hideUi = {},
seekPositionChanged = {},
seekingFinished = {},
embeddedDraggedDownBy = {},
fastSeek = {},
finishFastSeek = {},
brightnessChange = { a, b ->},
volumeChange = {})
VideoPlayerControllerUI(VideoPlayerViewModelDummy(), VideoPlayerUIState.DEFAULT)
}
}
}
@ -271,32 +191,7 @@ fun VideoPlayerControllerUIPreviewEmbedded() {
fun VideoPlayerControllerUIPreviewLandscape() {
VideoPlayerTheme {
PreviewBackgroundSurface {
VideoPlayerControllerUI(isPlaying = true,
fullscreen = true,
uiVissible = true,
seekPosition = 0.3F,
isLoading = true,
durationInMs = 9 * 60 * 1000,
playbackPositionInMs = 6 * 60 * 1000,
bufferedPercentage = 0.4f,
fastSeekSeconds = 0,
brightnes = 0f,
soundVolume = 0f,
play = {},
pause = {},
prevStream = {},
nextStream = {},
switchToEmbeddedView = {},
switchToFullscreen = {},
showUi = {},
hideUi = {},
seekPositionChanged = {},
seekingFinished = {},
embeddedDraggedDownBy = {},
fastSeek = {},
finishFastSeek = {},
brightnessChange = { a, b -> },
volumeChange = {})
VideoPlayerControllerUI(VideoPlayerViewModelDummy(), VideoPlayerUIState.DEFAULT)
}
}
}
@ -306,33 +201,7 @@ fun VideoPlayerControllerUIPreviewLandscape() {
fun VideoPlayerControllerUIPreviewPortrait() {
VideoPlayerTheme {
PreviewBackgroundSurface {
VideoPlayerControllerUI(
isPlaying = false,
fullscreen = true,
uiVissible = true,
seekPosition = 0.3F,
isLoading = false,
durationInMs = 9 * 60 * 1000,
playbackPositionInMs = 6 * 60 * 1000,
bufferedPercentage = 0.4f,
fastSeekSeconds = 0,
brightnes = 0f,
soundVolume = 0f,
play = {},
pause = {},
prevStream = {},
nextStream = {},
switchToEmbeddedView = {},
switchToFullscreen = {},
showUi = {},
hideUi = {},
seekPositionChanged = {},
seekingFinished = {},
embeddedDraggedDownBy = {},
fastSeek = {},
finishFastSeek = {},
brightnessChange = { a, b -> },
volumeChange = {})
VideoPlayerControllerUI(VideoPlayerViewModelDummy(), VideoPlayerUIState.DEFAULT)
}
}
}

View File

@ -53,11 +53,11 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.media3.common.Player
import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelImpl
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.utils.LockScreenOrientation
import net.newpipe.newplayer.utils.getScreenBrightnes
import net.newpipe.newplayer.utils.setScreenBrightnes
import net.newpipe.newplayer.utils.getDefaultBrightness
import net.newpipe.newplayer.utils.setScreenBrightness
private const val TAG = "VideoPlayerUI"
@ -89,8 +89,7 @@ fun VideoPlayerUI(
// Setup fullscreen
if (uiState.fullscreen) {
LaunchedEffect(key1 = true) {
WindowCompat.getInsetsController(window, view)
.isAppearanceLightStatusBars = false
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = false
}
}
@ -130,13 +129,12 @@ fun VideoPlayerUI(
val screenRatio =
displayMetrics.widthPixels.toFloat() / displayMetrics.heightPixels.toFloat()
val systemScreenBrightnes = getScreenBrightnes(activity)
val defaultBrightness = getDefaultBrightness(activity)
LaunchedEffect(key1 = uiState.brightness) {
Log.d(TAG, "New Brightnes: ${uiState.brightness}")
setScreenBrightnes(
uiState.brightness
?: if (systemScreenBrightnes < 0f) 0.5f else systemScreenBrightnes, activity
setScreenBrightness(
uiState.brightness ?: defaultBrightness, activity
)
}
@ -160,33 +158,7 @@ fun VideoPlayerUI(
}
VideoPlayerControllerUI(
isPlaying = uiState.playing,
fullscreen = uiState.fullscreen,
uiVissible = uiState.uiVissible,
seekPosition = uiState.seekerPosition,
isLoading = uiState.isLoading,
durationInMs = uiState.durationInMs,
playbackPositionInMs = uiState.playbackPositionInMs,
bufferedPercentage = uiState.bufferedPercentage,
fastSeekSeconds = uiState.fastseekSeconds,
brightnes = uiState.brightness
?: if (systemScreenBrightnes < 0f) 0.5f else systemScreenBrightnes,
soundVolume = uiState.soundVolume,
play = viewModel::play,
pause = viewModel::pause,
prevStream = viewModel::prevStream,
nextStream = viewModel::nextStream,
switchToFullscreen = viewModel::switchToFullscreen,
switchToEmbeddedView = viewModel::switchToEmbeddedView,
showUi = viewModel::showUi,
hideUi = viewModel::hideUi,
seekPositionChanged = viewModel::seekPositionChanged,
seekingFinished = viewModel::seekingFinished,
embeddedDraggedDownBy = viewModel::embeddedDraggedDown,
fastSeek = viewModel::fastSeek,
finishFastSeek = viewModel::finishFastSeek,
volumeChange = viewModel::volumeChange,
brightnessChange = viewModel::brightnessChange
viewModel, uiState = uiState
)
}
}
@ -253,6 +225,6 @@ fun PlaySurface(
@Composable
fun PlayerUIPreviewEmbeded() {
VideoPlayerTheme {
VideoPlayerUI(viewModel = VideoPlayerViewModelImpl.dummy)
VideoPlayerUI(viewModel = VideoPlayerViewModelDummy())
}
}

View File

@ -23,6 +23,8 @@ package net.newpipe.newplayer.ui.theme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import net.newpipe.newplayer.model.VideoPlayerUIState
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
import net.newpipe.newplayer.ui.PreviewBackgroundSurface
import net.newpipe.newplayer.ui.VideoPlayerControllerUI
@ -72,32 +74,20 @@ val video_player_scrim = Color(0xFF000000)
fun VideoPlayerControllerUIPreviewEmbeddedColorPreview() {
VideoPlayerTheme {
PreviewBackgroundSurface {
VideoPlayerControllerUI(isPlaying = false,
fullscreen = false,
uiVissible = true,
seekPosition = 0.3F,
isLoading = false,
durationInMs = 9*60*1000,
playbackPositionInMs = 6*60*1000,
bufferedPercentage = 0.4f,
fastSeekSeconds = 10,
brightnes = 0f,
soundVolume = 0f,
play = {},
pause = {},
prevStream = {},
nextStream = {},
switchToFullscreen = {},
switchToEmbeddedView = {},
showUi = {},
hideUi = {},
seekPositionChanged = {},
seekingFinished = {},
embeddedDraggedDownBy = {},
fastSeek = {},
finishFastSeek = {},
brightnessChange = {a, b ->},
volumeChange = {})
VideoPlayerControllerUI(
viewModel = VideoPlayerViewModelDummy(),
uiState = VideoPlayerUIState.DEFAULT.copy(
playing = true,
fullscreen = false,
uiVissible = true,
seekerPosition = 0.3f,
isLoading = false,
durationInMs = 9 * 60 * 1000,
playbackPositionInMs = 6 * 60 * 1000,
bufferedPercentage = 0.4f,
fastSeekSeconds = 10,
),
)
}
}
}

View File

@ -42,6 +42,9 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.os.ConfigurationCompat
import net.newpipe.newplayer.R
import net.newpipe.newplayer.model.VideoPlayerUIState
import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
import net.newpipe.newplayer.ui.seeker.DefaultSeekerColor
import net.newpipe.newplayer.ui.seeker.Seeker
import net.newpipe.newplayer.ui.seeker.SeekerColors
@ -52,39 +55,33 @@ import kotlin.math.min
@Composable
fun BottomUI(
modifier: Modifier,
isFullscreen: Boolean,
seekPosition: Float,
durationInMs: Long,
playbackPositionInMs: Long,
bufferedPercentage: Float,
switchToFullscreen: () -> Unit,
switchToEmbeddedView: () -> Unit,
seekPositionChanged: (Float) -> Unit,
seekingFinished: () -> Unit
modifier: Modifier, viewModel: VideoPlayerViewModel, uiState: VideoPlayerUIState
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier
) {
Text(getTimeStringFromMs(playbackPositionInMs, getLocale() ?: Locale.US))
Text(getTimeStringFromMs(uiState.playbackPositionInMs, getLocale() ?: Locale.US))
Seeker(
Modifier.weight(1F),
value = seekPosition,
onValueChange = seekPositionChanged,
onValueChangeFinished = seekingFinished,
readAheadValue = bufferedPercentage,
value = uiState.seekerPosition,
onValueChange = viewModel::seekPositionChanged,
onValueChangeFinished = viewModel::seekingFinished,
readAheadValue = uiState.bufferedPercentage,
colors = customizedSeekerColors()
)
//Slider(value = 0.4F, onValueChange = {}, modifier = Modifier.weight(1F))
Text(getTimeStringFromMs(durationInMs, getLocale() ?: Locale.US))
Text(getTimeStringFromMs(uiState.durationInMs, getLocale() ?: Locale.US))
IconButton(onClick = if (isFullscreen) switchToEmbeddedView else switchToFullscreen) {
IconButton(
onClick = if (uiState.fullscreen) viewModel::switchToEmbeddedView
else viewModel::switchToFullscreen
) {
Icon(
imageVector = if (isFullscreen) Icons.Filled.FullscreenExit
imageVector = if (uiState.fullscreen) Icons.Filled.FullscreenExit
else Icons.Filled.Fullscreen,
contentDescription = stringResource(R.string.widget_description_toggle_fullscreen)
)
@ -93,7 +90,7 @@ fun BottomUI(
}
@Composable
private fun customizedSeekerColors() : SeekerColors {
private fun customizedSeekerColors(): SeekerColors {
val colors = DefaultSeekerColor(
progressColor = MaterialTheme.colorScheme.primary,
thumbColor = MaterialTheme.colorScheme.primary,
@ -124,7 +121,7 @@ private const val MILLIS_PER_DAY =
private const val MILLIS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MILLIS_PER_SECOND
private const val MILLIS_PER_MINUTE = SECONDS_PER_MINUTE * MILLIS_PER_SECOND
private fun getTimeStringFromMs(timeSpanInMs: Long, locale: Locale) : String {
private fun getTimeStringFromMs(timeSpanInMs: Long, locale: Locale): String {
val days = timeSpanInMs / MILLIS_PER_DAY
val millisThisDay = timeSpanInMs - days * MILLIS_PER_DAY
val hours = millisThisDay / MILLIS_PER_HOUR
@ -153,15 +150,14 @@ fun VideoPlayerControllerBottomUIPreview() {
Surface(color = Color.Black) {
BottomUI(
modifier = Modifier,
isFullscreen = true,
seekPosition = 0.4F,
durationInMs = 90 * 60 * 1000,
playbackPositionInMs = 3 * 60 * 1000,
bufferedPercentage = 0.4f,
switchToFullscreen = { },
switchToEmbeddedView = { },
seekPositionChanged = {},
seekingFinished = {}
viewModel = VideoPlayerViewModelDummy(),
uiState = VideoPlayerUIState.DEFAULT.copy(
fullscreen = true,
seekerPosition = 0.4f,
durationInMs = 90 * 60 * 1000,
playbackPositionInMs = 3 * 60 * 1000,
bufferedPercentage = 0.4f
),
)
}
}

View File

@ -42,41 +42,41 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import net.newpipe.newplayer.R
import net.newpipe.newplayer.model.VideoPlayerUIState
import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
import net.newpipe.newplayer.model.VideoPlayerViewModelImpl
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
@Composable
fun CenterUI(
modifier: Modifier,
isPlaying: Boolean,
isLoading: Boolean,
play: () -> Unit,
pause: () -> Unit,
nextStream: () -> Unit,
prevStream: () -> Unit
modifier: Modifier = Modifier,
viewModel: VideoPlayerViewModel,
uiState: VideoPlayerUIState
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier,
) {
if (!isLoading) {
if (!uiState.isLoading) {
CenterControllButton(
buttonModifier = Modifier.size(80.dp),
iconModifier = Modifier.size(40.dp),
icon = Icons.Filled.SkipPrevious,
contentDescriptoion = stringResource(R.string.widget_description_previous_stream),
onClick = prevStream
onClick = viewModel::prevStream
)
CenterControllButton(
buttonModifier = Modifier.size(80.dp),
iconModifier = Modifier.size(60.dp),
icon = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
icon = if (uiState.playing) Icons.Filled.Pause else Icons.Filled.PlayArrow,
contentDescriptoion = stringResource(
if (isPlaying) R.string.widget_description_pause
if (uiState.playing) R.string.widget_description_pause
else R.string.widget_description_play
),
onClick = if (isPlaying) pause else play
onClick = if (uiState.playing) viewModel::pause else viewModel::play
)
CenterControllButton(
@ -84,7 +84,7 @@ fun CenterUI(
iconModifier = Modifier.size(40.dp),
icon = Icons.Filled.SkipNext,
contentDescriptoion = stringResource(R.string.widget_description_next_stream),
onClick = nextStream
onClick = viewModel::nextStream
)
}
}
@ -122,13 +122,12 @@ fun VideoPlayerControllerUICenterUIPreview() {
VideoPlayerTheme {
Surface(color = Color.Black) {
CenterUI(
modifier = Modifier,
isPlaying = true,
isLoading = false,
play = { },
pause = { },
nextStream = { }) {
}
viewModel = VideoPlayerViewModelDummy(),
uiState = VideoPlayerUIState.DEFAULT.copy(
isLoading = false,
playing = true
)
)
}
}
}
}

View File

@ -23,6 +23,8 @@ package net.newpipe.newplayer.ui.videoplayer
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import net.newpipe.newplayer.model.VideoPlayerUIState
import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.EmbeddedGestureUI
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.FullscreenGestureUI
@ -39,54 +41,15 @@ val INDICATOR_BACKGROUND_COLOR = Color.Black.copy(alpha = 0.3f)
@Composable
fun GestureUI(
modifier: Modifier,
hideUi: () -> Unit,
showUi: () -> Unit,
uiVissible: Boolean,
fullscreen: Boolean,
fastSeekSeconds: Int,
brightnes: Float,
soundVolume: Float,
switchToFullscreen: () -> Unit,
switchToEmbeddedView: () -> Unit,
embeddedDraggedDownBy: (Float) -> Unit,
fastSeek: (Int) -> Unit,
fastSeekFinished: () -> Unit,
volumeChange: (Float) -> Unit,
brightnessChange: (Float) -> Unit,
modifier: Modifier, viewModel: VideoPlayerViewModel, uiState: VideoPlayerUIState
) {
val defaultOnRegularTap = {
if (uiVissible) {
hideUi()
} else {
showUi()
}
}
if (fullscreen) {
if (uiState.fullscreen) {
FullscreenGestureUI(
modifier = modifier,
uiVisible = uiVissible,
fastSeekSeconds = fastSeekSeconds,
hideUi = hideUi,
showUi = showUi,
fastSeek = fastSeek,
brightnes = brightnes,
volume = soundVolume,
switchToEmbeddedView = switchToEmbeddedView,
fastSeekFinished = fastSeekFinished,
volumeChange = volumeChange,
brightnesChange = brightnessChange)
modifier = modifier, viewModel = viewModel, uiState = uiState
)
} else {
EmbeddedGestureUI(
modifier = modifier,
fastSeekSeconds = fastSeekSeconds,
uiVissible = uiVissible,
switchToFullscreen = switchToFullscreen,
embeddedDraggedDownBy = embeddedDraggedDownBy,
fastSeek = fastSeek,
fastSeekFinished = fastSeekFinished,
hideUi = hideUi,
showUi = showUi)
modifier = modifier, viewModel = viewModel, uiState = uiState
)
}
}

View File

@ -32,6 +32,9 @@ import androidx.compose.ui.Alignment
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.theme.VideoPlayerTheme
import net.newpipe.newplayer.ui.videoplayer.FAST_SEEK_MODE_DURATION
@ -39,49 +42,34 @@ private const val TAG = "EmbeddedGestureUI"
@Composable
fun EmbeddedGestureUI(
modifier: Modifier = Modifier,
fastSeekSeconds: Int,
uiVissible: Boolean,
switchToFullscreen: () -> Unit,
embeddedDraggedDownBy: (Float) -> Unit,
fastSeek: (Int) -> Unit,
fastSeekFinished: () -> Unit,
hideUi: () -> Unit,
showUi: () -> Unit
modifier: Modifier = Modifier, viewModel: VideoPlayerViewModel, uiState: VideoPlayerUIState
) {
val handleDownwardMovement = { movement: TouchedPosition ->
Log.d(TAG, "${movement.x}:${movement.y}")
if (0 < movement.y) {
embeddedDraggedDownBy(movement.y)
viewModel.embeddedDraggedDown(movement.y)
} else {
switchToFullscreen()
viewModel.switchToFullscreen()
}
}
val defaultOnRegularTap = {
if (uiVissible) {
hideUi()
if (uiState.uiVissible) {
viewModel.hideUi()
} else {
showUi()
viewModel.showUi()
}
}
Row(modifier = modifier) {
GestureSurface(
modifier = Modifier
.weight(1f),
multiTapTimeoutInMs = FAST_SEEK_MODE_DURATION,
onRegularTap = defaultOnRegularTap,
onMultiTap = {
fastSeek(-it)
},
onMultiTapFinished = fastSeekFinished,
onMovement = handleDownwardMovement
modifier = Modifier.weight(1f), onRegularTap = defaultOnRegularTap, onMultiTap = {
viewModel.fastSeek(-it)
}, onMultiTapFinished = viewModel::finishFastSeek, onMovement = handleDownwardMovement
) {
FadedAnimationForSeekFeedback(
fastSeekSeconds,
backwards = true
uiState.fastSeekSeconds, backwards = true
) { fastSeekSecondsToDisplay ->
Box(modifier = Modifier.fillMaxSize()) {
FastSeekVisualFeedback(
@ -93,15 +81,13 @@ fun EmbeddedGestureUI(
}
}
GestureSurface(
modifier = Modifier
.weight(1f),
multiTapTimeoutInMs = FAST_SEEK_MODE_DURATION,
modifier = Modifier.weight(1f),
onRegularTap = defaultOnRegularTap,
onMovement = handleDownwardMovement,
onMultiTap = fastSeek,
onMultiTapFinished = fastSeekFinished
onMultiTap = viewModel::fastSeek,
onMultiTapFinished = viewModel::finishFastSeek
) {
FadedAnimationForSeekFeedback(fastSeekSeconds) { fastSeekSecondsToDisplay ->
FadedAnimationForSeekFeedback(uiState.fastSeekSeconds) { fastSeekSecondsToDisplay ->
Box(modifier = Modifier.fillMaxSize()) {
FastSeekVisualFeedback(
modifier = Modifier.align(Alignment.Center),
@ -122,14 +108,21 @@ fun EmbeddedGestureUIPreview() {
Surface(modifier = Modifier.wrapContentSize(), color = Color.DarkGray) {
EmbeddedGestureUI(
modifier = Modifier,
hideUi = { },
showUi = { },
uiVissible = false,
fastSeekSeconds = 0,
switchToFullscreen = { println("switch to fullscreen") },
embeddedDraggedDownBy = { println("embedded dragged down") },
fastSeek = { println("Fast seek by $it steps") },
fastSeekFinished = {})
viewModel = object : VideoPlayerViewModelDummy() {
override fun switchToEmbeddedView() {
println("switch to fullscreen")
}
override fun embeddedDraggedDown(offset: Float) {
println("embedded view dragged down by $offset")
}
override fun fastSeek(steps: Int) {
println("fast seek by $steps steps")
}
},
uiState = VideoPlayerUIState.DEFAULT,
)
}
}
}

View File

@ -21,14 +21,12 @@
package net.newpipe.newplayer.ui.videoplayer.gesture_ui
import android.app.Activity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
@ -44,9 +42,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
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.theme.VideoPlayerTheme
import net.newpipe.newplayer.ui.videoplayer.FAST_SEEK_MODE_DURATION
import net.newpipe.newplayer.utils.getDefaultBrightness
private enum class IndicatorMode {
NONE,
@ -57,17 +59,8 @@ private enum class IndicatorMode {
@Composable
fun FullscreenGestureUI(
modifier: Modifier = Modifier,
uiVisible: Boolean,
fastSeekSeconds: Int,
volume: Float,
brightnes: Float,
hideUi: () -> Unit,
showUi: () -> Unit,
fastSeek: (Int) -> Unit,
fastSeekFinished: () -> Unit,
switchToEmbeddedView: () -> Unit,
volumeChange: (Float) -> Unit,
brightnesChange: (Float) -> Unit
viewModel: VideoPlayerViewModel,
uiState: VideoPlayerUIState
) {
var heightPx by remember {
@ -80,13 +73,17 @@ fun FullscreenGestureUI(
val defaultOnRegularTap = {
if (uiVisible) {
hideUi()
if (uiState.uiVisible) {
viewModel.hideUi()
} else {
showUi()
viewModel.showUi()
}
}
val activity = LocalContext.current as Activity
val defaultBrightness = getDefaultBrightness(activity)
Box(modifier = modifier.onGloballyPositioned { coordinates ->
heightPx = coordinates.size.height.toFloat()
}) {
@ -97,9 +94,9 @@ fun FullscreenGestureUI(
onRegularTap = defaultOnRegularTap,
onMultiTap = {
println("multitap ${-it}")
fastSeek(-it)
viewModel.fastSeek(-it)
},
onMultiTapFinished = fastSeekFinished,
onMultiTapFinished = viewModel::finishFastSeek,
onUp = {
indicatorMode = IndicatorMode.NONE
},
@ -110,13 +107,13 @@ fun FullscreenGestureUI(
indicatorMode = IndicatorMode.BRIGHTNESS_INDICATOR_VISSIBLE
if (heightPx != 0f) {
brightnesChange(-change.y / heightPx)
viewModel.brightnessChange(-change.y / heightPx, defaultBrightness)
}
}
}
) {
FadedAnimationForSeekFeedback(
fastSeekSeconds,
uiState.fastSeekSeconds,
backwards = true
) { fastSeekSecondsToDisplay ->
Box(modifier = Modifier.fillMaxSize()) {
@ -134,7 +131,7 @@ fun FullscreenGestureUI(
onRegularTap = defaultOnRegularTap,
onMovement = { movement ->
if (0 < movement.y) {
switchToEmbeddedView()
viewModel.switchToEmbeddedView()
}
}
)
@ -142,8 +139,8 @@ fun FullscreenGestureUI(
modifier = Modifier
.weight(1f),
onRegularTap = defaultOnRegularTap,
onMultiTap = fastSeek,
onMultiTapFinished = fastSeekFinished,
onMultiTap = viewModel::fastSeek,
onMultiTapFinished = viewModel::finishFastSeek,
onUp = {
indicatorMode = IndicatorMode.NONE
},
@ -153,12 +150,12 @@ fun FullscreenGestureUI(
) {
indicatorMode = IndicatorMode.VOLUME_INDICATOR_VISSIBLE
if (heightPx != 0f) {
volumeChange(-change.y / heightPx)
viewModel.volumeChange(-change.y / heightPx)
}
}
}
) {
FadedAnimationForSeekFeedback(fastSeekSeconds) { fastSeekSecondsToDisplay ->
FadedAnimationForSeekFeedback(uiState.fastSeekSeconds) { fastSeekSecondsToDisplay ->
Box(modifier = Modifier.fillMaxSize()) {
FastSeekVisualFeedback(
modifier = Modifier.align(Alignment.CenterStart),
@ -174,7 +171,7 @@ fun FullscreenGestureUI(
modifier = Modifier.align(Alignment.Center),
visible = indicatorMode == IndicatorMode.VOLUME_INDICATOR_VISSIBLE,
) {
VolumeCircle(volumeFraction = volume)
VolumeCircle(volumeFraction = uiState.soundVolume)
}
IndicatorAnimation(
@ -182,7 +179,7 @@ fun FullscreenGestureUI(
visible = indicatorMode == IndicatorMode.BRIGHTNESS_INDICATOR_VISSIBLE,
) {
VolumeCircle(
volumeFraction = brightnes,
volumeFraction = uiState.brightness ?: defaultBrightness,
modifier = Modifier.align(Alignment.Center),
isBrightness = true
)
@ -233,17 +230,13 @@ fun FullscreenGestureUIPreview() {
Surface(modifier = Modifier.wrapContentSize(), color = Color.DarkGray) {
FullscreenGestureUI(
modifier = Modifier,
hideUi = { },
showUi = { },
uiVisible = false,
fastSeekSeconds = 0,
volume = 0f,
brightnes = 0f,
fastSeek = { println("fast seek by $it steps") },
fastSeekFinished = {},
switchToEmbeddedView = {},
brightnesChange = {},
volumeChange = {})
object : VideoPlayerViewModelDummy() {
override fun fastSeek(steps: Int) {
println("fast seek by $steps steps")
}
},
VideoPlayerUIState.DEFAULT
)
}
}
}
@ -256,7 +249,7 @@ fun FullscreenGestureUIPreviewInteractive() {
mutableStateOf(0)
}
var brightnesValue by remember {
var brightnessValue by remember {
mutableStateOf(0f)
}
@ -272,23 +265,38 @@ fun FullscreenGestureUIPreviewInteractive() {
Surface(modifier = Modifier.wrapContentSize(), color = Color.Gray) {
FullscreenGestureUI(
modifier = Modifier,
hideUi = { uiVisible = false },
showUi = { uiVisible = true },
uiVisible = uiVisible,
fastSeekSeconds = seekSeconds,
volume = soundVolume,
brightnes = brightnesValue,
fastSeek = { seekSeconds = it * 10 },
fastSeekFinished = {
seekSeconds = 0
object : VideoPlayerViewModelDummy() {
override fun hideUi() {
uiVisible = false
}
override fun showUi() {
uiVisible = true
}
override fun fastSeek(steps: Int) {
seekSeconds = steps * 10
}
override fun finishFastSeek() {
seekSeconds = 0
}
override fun brightnessChange(changeRate: Float, currentValue: Float) {
brightnessValue = (brightnessValue + changeRate).coerceIn(0f, 1f)
}
override fun volumeChange(changeRate: Float) {
soundVolume = (soundVolume + changeRate).coerceIn(0f, 1f)
}
},
switchToEmbeddedView = {},
brightnesChange = {
brightnesValue = (brightnesValue + it).coerceIn(0f, 1f)
},
volumeChange = {
soundVolume = (soundVolume + it).coerceIn(0f, 1f)
})
uiState = VideoPlayerUIState.DEFAULT.copy(
uiVissible = uiVisible,
fastSeekSeconds = seekSeconds,
soundVolume = soundVolume,
brightness = brightnessValue
),
)
}
AnimatedVisibility(uiVisible) {

View File

@ -24,7 +24,6 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.view.Window
import android.view.WindowManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@ -45,14 +44,14 @@ fun LockScreenOrientation(orientation: Int) {
}
@SuppressLint("NewApi")
fun getScreenBrightnes(activity: Activity) : Float {
fun getDefaultBrightness(activity: Activity) : Float {
val window = activity.window
val layout = window.attributes as WindowManager.LayoutParams
return layout.screenBrightness
return if(layout.screenBrightness < 0) 0.5f else layout.screenBrightness
}
@SuppressLint("NewApi")
fun setScreenBrightnes(value:Float, activity: Activity) {
fun setScreenBrightness(value:Float, activity: Activity) {
val window = activity.window
val layout = window.attributes as WindowManager.LayoutParams
layout.screenBrightness = value