simplify ui change

This commit is contained in:
Christian Schabesberger 2024-09-21 15:37:12 +02:00
parent 25593c8ed6
commit 87cc8a2c77
15 changed files with 173 additions and 116 deletions

View File

@ -32,8 +32,10 @@ enum class PlayMode {
EMBEDDED_VIDEO, EMBEDDED_VIDEO,
FULLSCREEN_VIDEO, FULLSCREEN_VIDEO,
PIP, PIP,
BACKGROUND, BACKGROUND_VIDEO,
AUDIO_FOREGROUND, BACKGROUND_AUDIO,
FULLSCREEN_AUDIO,
EMBEDDED_AUDIO
} }
enum class RepeatMode { enum class RepeatMode {

View File

@ -44,8 +44,7 @@ interface NewPlayerViewModel {
fun pause() fun pause()
fun prevStream() fun prevStream()
fun nextStream() fun nextStream()
fun switchToFullscreen(embeddedUiConfig: EmbeddedUiConfig) fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig)
fun switchToEmbeddedView()
fun onBackPressed() fun onBackPressed()
fun showUi() fun showUi()
fun hideUi() fun hideUi()
@ -56,8 +55,6 @@ interface NewPlayerViewModel {
fun finishFastSeek() fun finishFastSeek()
fun brightnessChange(changeRate: Float, systemBrightness: Float) fun brightnessChange(changeRate: Float, systemBrightness: Float)
fun volumeChange(changeRate: Float) fun volumeChange(changeRate: Float)
fun openStreamSelection(selectChapter: Boolean, embeddedUiConfig: EmbeddedUiConfig)
fun closeStreamSelection()
fun chapterSelected(chapterId: Int) fun chapterSelected(chapterId: Int)
fun streamSelected(streamId: Int) fun streamSelected(streamId: Int)
fun cycleRepeatMode() fun cycleRepeatMode()

View File

@ -1,6 +1,7 @@
package net.newpipe.newplayer.model package net.newpipe.newplayer.model
import android.os.Bundle import android.os.Bundle
import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
@ -8,6 +9,7 @@ import kotlinx.coroutines.flow.asSharedFlow
import net.newpipe.newplayer.NewPlayer import net.newpipe.newplayer.NewPlayer
import net.newpipe.newplayer.ui.ContentScale import net.newpipe.newplayer.ui.ContentScale
@UnstableApi
open class NewPlayerViewModelDummy : NewPlayerViewModel { open class NewPlayerViewModelDummy : NewPlayerViewModel {
override var newPlayer: NewPlayer? = null override var newPlayer: NewPlayer? = null
override val uiState = MutableStateFlow(NewPlayerUIState.DEFAULT) override val uiState = MutableStateFlow(NewPlayerUIState.DEFAULT)
@ -25,18 +27,10 @@ open class NewPlayerViewModelDummy : NewPlayerViewModel {
println("dummy impl") println("dummy impl")
} }
override fun switchToEmbeddedView() {
println("dummy impl")
}
override fun onBackPressed() { override fun onBackPressed() {
println("dummy impl") println("dummy impl")
} }
override fun switchToFullscreen(embeddedUiConfig: EmbeddedUiConfig) {
println("dummy impl")
}
override fun showUi() { override fun showUi() {
println("dummy impl") println("dummy impl")
} }
@ -73,14 +67,6 @@ open class NewPlayerViewModelDummy : NewPlayerViewModel {
println("dummy impl") println("dummy impl")
} }
override fun openStreamSelection(selectChapter: Boolean, embeddedUiConfig: EmbeddedUiConfig) {
println("dummy impl")
}
override fun closeStreamSelection() {
println("dummy impl")
}
override fun chapterSelected(chapterId: Int) { override fun chapterSelected(chapterId: Int) {
println("dummp impl chapter selected: $chapterId") println("dummp impl chapter selected: $chapterId")
} }
@ -128,4 +114,8 @@ open class NewPlayerViewModelDummy : NewPlayerViewModel {
override fun nextStream() { override fun nextStream() {
println("dummy impl") println("dummy impl")
} }
override fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig) {
println("dummy uiMode change: New UI Mode State: $newUiModeState")
}
} }

View File

@ -150,7 +150,7 @@ class NewPlayerViewModelImpl @Inject constructor(
mutableUiState.update { mutableUiState.update {
it.copy(playing = isPlaying, isLoading = false) it.copy(playing = isPlaying, isLoading = false)
} }
if (isPlaying && uiState.value.uiMode.controllerUiVisible) { if (isPlaying && uiState.value.uiMode.videoControllerUiVisible) {
resetHideUiDelayedJob() resetHideUiDelayedJob()
} else { } else {
uiVisibilityJob?.cancel() uiVisibilityJob?.cancel()
@ -329,6 +329,41 @@ class NewPlayerViewModelImpl @Inject constructor(
} }
} }
override fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig) {
if (!uiState.value.uiMode.fullscreen) {
this.embeddedUiConfig = embeddedUiConfig
}
if (newUiModeState.isStreamSelect) {
resetPlaylistProgressUpdaterJob()
uiVisibilityJob?.cancel()
}
if (newUiModeState.isChapterSelect) {
resetPlaylistProgressUpdaterJob()
uiVisibilityJob?.cancel()
}
if ((uiState.value.uiMode.isStreamSelect || uiState.value.uiMode.isChapterSelect)
&& (!newUiModeState.isStreamSelect && !newUiModeState.isChapterSelect)
) {
playlistProgressUpdaterJob?.cancel()
progressUpdaterJob?.cancel()
}
if(uiState.value.uiMode.fullscreen && !newUiModeState.fullscreen) {
uiVisibilityJob?.cancel()
finishFastSeek()
}
if(!uiState.value.uiMode.fullscreen && newUiModeState.fullscreen) {
uiVisibilityJob?.cancel()
finishFastSeek()
}
updateUiMode(newUiModeState)
}
override fun showUi() { override fun showUi() {
mutableUiState.update { mutableUiState.update {
it.copy(uiMode = it.uiMode.getControllerUiVisibleState()) it.copy(uiMode = it.uiMode.getControllerUiVisibleState())
@ -430,13 +465,13 @@ class NewPlayerViewModelImpl @Inject constructor(
) )
} }
if (mutableUiState.value.uiMode.controllerUiVisible) { if (mutableUiState.value.uiMode.videoControllerUiVisible) {
resetHideUiDelayedJob() resetHideUiDelayedJob()
} }
} }
override fun finishFastSeek() { override fun finishFastSeek() {
if (mutableUiState.value.uiMode.controllerUiVisible) { if (mutableUiState.value.uiMode.videoControllerUiVisible) {
resetHideUiDelayedJob() resetHideUiDelayedJob()
} }
@ -482,34 +517,6 @@ class NewPlayerViewModelImpl @Inject constructor(
} }
} }
override fun openStreamSelection(selectChapter: Boolean, embeddedUiConfig: EmbeddedUiConfig) {
uiVisibilityJob?.cancel()
if (!uiState.value.uiMode.fullscreen) {
this.embeddedUiConfig = embeddedUiConfig
}
updateUiMode(
if (selectChapter) uiState.value.uiMode.getChapterSelectUiState()
else uiState.value.uiMode.getStreamSelectUiState()
)
if (selectChapter) {
resetProgressUpdatePeriodicallyJob()
} else {
resetPlaylistProgressUpdaterJob()
}
}
override fun closeStreamSelection() {
playlistProgressUpdaterJob?.cancel()
progressUpdaterJob?.cancel()
updateUiMode(uiState.value.uiMode.getUiHiddenState())
}
override fun switchToEmbeddedView() {
uiVisibilityJob?.cancel()
finishFastSeek()
updateUiMode(UIModeState.EMBEDDED_VIDEO)
}
override fun onBackPressed() { override fun onBackPressed() {
val nextMode = uiState.value.uiMode.getNextModeWhenBackPressed() val nextMode = uiState.value.uiMode.getNextModeWhenBackPressed()
if (nextMode != null) { if (nextMode != null) {
@ -519,14 +526,6 @@ class NewPlayerViewModelImpl @Inject constructor(
} }
} }
override fun switchToFullscreen(embeddedUiConfig: EmbeddedUiConfig) {
uiVisibilityJob?.cancel()
finishFastSeek()
this.embeddedUiConfig = embeddedUiConfig
updateUiMode(UIModeState.FULLSCREEN_VIDEO)
}
override fun chapterSelected(chapterId: Int) { override fun chapterSelected(chapterId: Int) {
newPlayer?.selectChapter(chapterId) newPlayer?.selectChapter(chapterId)
} }

View File

@ -33,7 +33,12 @@ enum class UIModeState {
FULLSCREEN_VIDEO, FULLSCREEN_VIDEO,
FULLSCREEN_VIDEO_CONTROLLER_UI, FULLSCREEN_VIDEO_CONTROLLER_UI,
FULLSCREEN_VIDEO_CHAPTER_SELECT, FULLSCREEN_VIDEO_CHAPTER_SELECT,
FULLSCREEN_VIDEO_STREAM_SELECT; FULLSCREEN_VIDEO_STREAM_SELECT,
EMBEDDED_AUDIO,
FULLSCREEN_AUDIO,
AUDIO_CHAPTER_SELECT,
AUDIO_STREAM_SELECT;
val fullscreen: Boolean val fullscreen: Boolean
get() = get() =
@ -44,10 +49,13 @@ enum class UIModeState {
FULLSCREEN_VIDEO_CONTROLLER_UI -> true FULLSCREEN_VIDEO_CONTROLLER_UI -> true
FULLSCREEN_VIDEO_CHAPTER_SELECT -> true FULLSCREEN_VIDEO_CHAPTER_SELECT -> true
FULLSCREEN_VIDEO_STREAM_SELECT -> true FULLSCREEN_VIDEO_STREAM_SELECT -> true
FULLSCREEN_AUDIO -> true
AUDIO_CHAPTER_SELECT -> true
AUDIO_STREAM_SELECT -> true
else -> false else -> false
} }
val controllerUiVisible: Boolean val videoControllerUiVisible: Boolean
get() = get() =
when (this) { when (this) {
EMBEDDED_VIDEO_CONTROLLER_UI -> true EMBEDDED_VIDEO_CONTROLLER_UI -> true
@ -60,6 +68,7 @@ enum class UIModeState {
when (this) { when (this) {
EMBEDDED_VIDEO_STREAM_SELECT -> true EMBEDDED_VIDEO_STREAM_SELECT -> true
FULLSCREEN_VIDEO_STREAM_SELECT -> true FULLSCREEN_VIDEO_STREAM_SELECT -> true
AUDIO_STREAM_SELECT -> true
else -> false else -> false
} }
@ -68,6 +77,7 @@ enum class UIModeState {
when (this) { when (this) {
EMBEDDED_VIDEO_CHAPTER_SELECT -> true EMBEDDED_VIDEO_CHAPTER_SELECT -> true
FULLSCREEN_VIDEO_CHAPTER_SELECT -> true FULLSCREEN_VIDEO_CHAPTER_SELECT -> true
AUDIO_CHAPTER_SELECT -> true
else -> false else -> false
} }
@ -125,6 +135,10 @@ enum class UIModeState {
EMBEDDED_VIDEO_CHAPTER_SELECT -> EMBEDDED_VIDEO_STREAM_SELECT EMBEDDED_VIDEO_CHAPTER_SELECT -> EMBEDDED_VIDEO_STREAM_SELECT
EMBEDDED_VIDEO_CONTROLLER_UI -> EMBEDDED_VIDEO_STREAM_SELECT EMBEDDED_VIDEO_CONTROLLER_UI -> EMBEDDED_VIDEO_STREAM_SELECT
FULLSCREEN_AUDIO -> AUDIO_STREAM_SELECT
EMBEDDED_AUDIO -> AUDIO_STREAM_SELECT
AUDIO_CHAPTER_SELECT -> AUDIO_STREAM_SELECT
else -> this else -> this
} }
@ -138,6 +152,10 @@ enum class UIModeState {
EMBEDDED_VIDEO_STREAM_SELECT -> EMBEDDED_VIDEO_CHAPTER_SELECT EMBEDDED_VIDEO_STREAM_SELECT -> EMBEDDED_VIDEO_CHAPTER_SELECT
EMBEDDED_VIDEO_CONTROLLER_UI -> EMBEDDED_VIDEO_CHAPTER_SELECT EMBEDDED_VIDEO_CONTROLLER_UI -> EMBEDDED_VIDEO_CHAPTER_SELECT
FULLSCREEN_AUDIO -> AUDIO_CHAPTER_SELECT
EMBEDDED_AUDIO -> AUDIO_CHAPTER_SELECT
AUDIO_STREAM_SELECT -> AUDIO_CHAPTER_SELECT
else -> this else -> this
} }
@ -151,6 +169,10 @@ enum class UIModeState {
FULLSCREEN_VIDEO_CONTROLLER_UI -> PlayMode.FULLSCREEN_VIDEO FULLSCREEN_VIDEO_CONTROLLER_UI -> PlayMode.FULLSCREEN_VIDEO
FULLSCREEN_VIDEO_CHAPTER_SELECT -> PlayMode.FULLSCREEN_VIDEO FULLSCREEN_VIDEO_CHAPTER_SELECT -> PlayMode.FULLSCREEN_VIDEO
FULLSCREEN_VIDEO_STREAM_SELECT -> PlayMode.FULLSCREEN_VIDEO FULLSCREEN_VIDEO_STREAM_SELECT -> PlayMode.FULLSCREEN_VIDEO
EMBEDDED_AUDIO -> PlayMode.EMBEDDED_AUDIO
FULLSCREEN_AUDIO -> PlayMode.FULLSCREEN_AUDIO
AUDIO_CHAPTER_SELECT -> PlayMode.FULLSCREEN_AUDIO
AUDIO_STREAM_SELECT -> PlayMode.FULLSCREEN_AUDIO
} }
fun getNextModeWhenBackPressed() = when (this) { fun getNextModeWhenBackPressed() = when (this) {
@ -160,7 +182,13 @@ enum class UIModeState {
FULLSCREEN_VIDEO_STREAM_SELECT -> FULLSCREEN_VIDEO FULLSCREEN_VIDEO_STREAM_SELECT -> FULLSCREEN_VIDEO
FULLSCREEN_VIDEO_CHAPTER_SELECT -> FULLSCREEN_VIDEO FULLSCREEN_VIDEO_CHAPTER_SELECT -> FULLSCREEN_VIDEO
FULLSCREEN_VIDEO_CONTROLLER_UI -> EMBEDDED_VIDEO FULLSCREEN_VIDEO_CONTROLLER_UI -> EMBEDDED_VIDEO
else -> null PLACEHOLDER -> null
EMBEDDED_VIDEO -> null
EMBEDDED_VIDEO_CONTROLLER_UI -> null
EMBEDDED_AUDIO -> null
FULLSCREEN_AUDIO -> EMBEDDED_AUDIO
AUDIO_CHAPTER_SELECT -> FULLSCREEN_AUDIO
AUDIO_STREAM_SELECT -> FULLSCREEN_AUDIO
} }
companion object { companion object {
@ -170,8 +198,10 @@ enum class UIModeState {
PlayMode.EMBEDDED_VIDEO -> EMBEDDED_VIDEO PlayMode.EMBEDDED_VIDEO -> EMBEDDED_VIDEO
PlayMode.FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO PlayMode.FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO
PlayMode.PIP -> TODO() PlayMode.PIP -> TODO()
PlayMode.BACKGROUND -> TODO() PlayMode.BACKGROUND_VIDEO -> TODO()
PlayMode.AUDIO_FOREGROUND -> TODO() PlayMode.BACKGROUND_AUDIO -> TODO()
PlayMode.FULLSCREEN_AUDIO -> FULLSCREEN_AUDIO
PlayMode.EMBEDDED_AUDIO -> EMBEDDED_AUDIO
} }
} }
} }

View File

@ -40,7 +40,7 @@ import androidx.compose.ui.unit.dp
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
@Composable @Composable
fun VideoPlayerLoadingPlaceholder(aspectRatio: Float = 3F / 1F) { fun LoadingPlaceholder(aspectRatio: Float = 3F / 1F) {
Surface( Surface(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -63,6 +63,6 @@ fun VideoPlayerLoadingPlaceholder(aspectRatio: Float = 3F / 1F) {
@Composable @Composable
fun VideoPlayerLoaidingPlaceholderPreview() { fun VideoPlayerLoaidingPlaceholderPreview() {
VideoPlayerTheme { VideoPlayerTheme {
VideoPlayerLoadingPlaceholder() LoadingPlaceholder()
} }
} }

View File

@ -26,26 +26,16 @@ import android.util.Log
import android.view.SurfaceView import android.view.SurfaceView
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
@ -59,9 +49,8 @@ import androidx.media3.common.util.UnstableApi
import net.newpipe.newplayer.model.UIModeState import net.newpipe.newplayer.model.UIModeState
import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModel
import net.newpipe.newplayer.model.NewPlayerViewModelDummy import net.newpipe.newplayer.model.NewPlayerViewModelDummy
import net.newpipe.newplayer.ui.streamselect.StreamSelectUI import net.newpipe.newplayer.ui.audioplayer.AudioPlayerUI
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.ui.videoplayer.VideoPlayerControllerUI
import net.newpipe.newplayer.ui.videoplayer.VideoPlayerUi import net.newpipe.newplayer.ui.videoplayer.VideoPlayerUi
import net.newpipe.newplayer.utils.LockScreenOrientation import net.newpipe.newplayer.utils.LockScreenOrientation
import net.newpipe.newplayer.utils.getDefaultBrightness import net.newpipe.newplayer.utils.getDefaultBrightness
@ -75,9 +64,9 @@ fun NewPlayerUI(
viewModel: NewPlayerViewModel?, viewModel: NewPlayerViewModel?,
) { ) {
if (viewModel == null) { if (viewModel == null) {
VideoPlayerLoadingPlaceholder() LoadingPlaceholder()
} else if (viewModel.newPlayer == null) { } else if (viewModel.newPlayer == null) {
VideoPlayerLoadingPlaceholder(viewModel.uiState.collectAsState().value.embeddedUiRatio) LoadingPlaceholder(viewModel.uiState.collectAsState().value.embeddedUiRatio)
} else { } else {
val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsState()
@ -142,12 +131,22 @@ fun NewPlayerUI(
) )
} }
// Set UI if (uiState.uiMode == UIModeState.FULLSCREEN_VIDEO ||
if (uiState.uiMode == UIModeState.PLACEHOLDER) { uiState.uiMode == UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI ||
VideoPlayerLoadingPlaceholder(uiState.embeddedUiRatio) uiState.uiMode == UIModeState.FULLSCREEN_VIDEO_CHAPTER_SELECT ||
} else { uiState.uiMode == UIModeState.FULLSCREEN_VIDEO_STREAM_SELECT ||
uiState.uiMode == UIModeState.EMBEDDED_VIDEO ||
uiState.uiMode == UIModeState.EMBEDDED_VIDEO_CONTROLLER_UI ||
uiState.uiMode == UIModeState.EMBEDDED_VIDEO_STREAM_SELECT ||
uiState.uiMode == UIModeState.EMBEDDED_VIDEO_CHAPTER_SELECT
) {
VideoPlayerUi(viewModel = viewModel, uiState = uiState) VideoPlayerUi(viewModel = viewModel, uiState = uiState)
} else if (uiState.uiMode == UIModeState.FULLSCREEN_AUDIO) {
AudioPlayerUI(viewModel = viewModel, uiState = uiState)
} else {
LoadingPlaceholder(uiState.embeddedUiRatio)
} }
} }
} }

View File

@ -40,6 +40,7 @@ import net.newpipe.newplayer.R
import net.newpipe.newplayer.model.EmbeddedUiConfig import net.newpipe.newplayer.model.EmbeddedUiConfig
import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerUIState
import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModel
import net.newpipe.newplayer.model.UIModeState
import net.newpipe.newplayer.utils.getEmbeddedUiConfig import net.newpipe.newplayer.utils.getEmbeddedUiConfig
@androidx.annotation.OptIn(UnstableApi::class) @androidx.annotation.OptIn(UnstableApi::class)
@ -59,10 +60,7 @@ fun AudioPlayerTopBar(
AnimatedVisibility(visible = uiState.chapters.isNotEmpty()) { AnimatedVisibility(visible = uiState.chapters.isNotEmpty()) {
IconButton( IconButton(
onClick = { onClick = {
viewModel.openStreamSelection( viewModel.changeUiMode(UIModeState.AUDIO_CHAPTER_SELECT, embeddedUiConfig)
selectChapter = true,
embeddedUiConfig
)
}, },
) { ) {
Icon( Icon(
@ -74,10 +72,7 @@ fun AudioPlayerTopBar(
AnimatedVisibility(visible = 1 < uiState.playList.size) { AnimatedVisibility(visible = 1 < uiState.playList.size) {
IconButton( IconButton(
onClick = { onClick = {
viewModel.openStreamSelection( viewModel.changeUiMode(UIModeState.AUDIO_STREAM_SELECT, embeddedUiConfig)
selectChapter = false,
embeddedUiConfig
)
}, },
) { ) {
Icon( Icon(

View File

@ -20,6 +20,7 @@
package net.newpipe.newplayer.ui.streamselect package net.newpipe.newplayer.ui.streamselect
import android.app.Activity
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
@ -37,18 +38,21 @@ import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import net.newpipe.newplayer.R import net.newpipe.newplayer.R
import net.newpipe.newplayer.RepeatMode import net.newpipe.newplayer.RepeatMode
import net.newpipe.newplayer.model.EmbeddedUiConfig
import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerUIState
import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModel
import net.newpipe.newplayer.model.NewPlayerViewModelDummy import net.newpipe.newplayer.model.NewPlayerViewModelDummy
import net.newpipe.newplayer.ui.common.RepeatModeButton import net.newpipe.newplayer.ui.common.RepeatModeButton
import net.newpipe.newplayer.ui.common.ShuffleModeButton import net.newpipe.newplayer.ui.common.ShuffleModeButton
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.utils.getEmbeddedUiConfig
import net.newpipe.newplayer.utils.getLocale import net.newpipe.newplayer.utils.getLocale
import net.newpipe.newplayer.utils.getPlaylistDurationInMS import net.newpipe.newplayer.utils.getPlaylistDurationInMS
import net.newpipe.newplayer.utils.getTimeStringFromMs import net.newpipe.newplayer.utils.getTimeStringFromMs
@ -62,6 +66,12 @@ fun StreamSelectTopBar(
uiState: NewPlayerUIState uiState: NewPlayerUIState
) { ) {
val embeddedUiConfig =
if (LocalContext.current is Activity)
getEmbeddedUiConfig(activity = LocalContext.current as Activity)
else
EmbeddedUiConfig.DUMMY
TopAppBar(modifier = modifier, TopAppBar(modifier = modifier,
colors = topAppBarColors(containerColor = Color.Transparent), colors = topAppBarColors(containerColor = Color.Transparent),
title = { title = {
@ -91,7 +101,9 @@ fun StreamSelectTopBar(
} }
IconButton( IconButton(
onClick = viewModel::closeStreamSelection onClick = {
viewModel.changeUiMode(uiState.uiMode.getUiHiddenState(), embeddedUiConfig)
}
) { ) {
Icon( Icon(
imageVector = Icons.Filled.Close, imageVector = Icons.Filled.Close,

View File

@ -20,6 +20,7 @@
package net.newpipe.newplayer.ui.streamselect package net.newpipe.newplayer.ui.streamselect
import android.app.Activity
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -36,15 +37,18 @@ import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import net.newpipe.newplayer.model.EmbeddedUiConfig
import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerUIState
import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModel
import net.newpipe.newplayer.model.NewPlayerViewModelDummy import net.newpipe.newplayer.model.NewPlayerViewModelDummy
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.ui.videoplayer.STREAMSELECT_UI_BACKGROUND_COLOR import net.newpipe.newplayer.ui.videoplayer.STREAMSELECT_UI_BACKGROUND_COLOR
import net.newpipe.newplayer.utils.ReorderHapticFeedbackType import net.newpipe.newplayer.utils.ReorderHapticFeedbackType
import net.newpipe.newplayer.utils.getEmbeddedUiConfig
import net.newpipe.newplayer.utils.getInsets import net.newpipe.newplayer.utils.getInsets
import net.newpipe.newplayer.utils.rememberReorderHapticFeedback import net.newpipe.newplayer.utils.rememberReorderHapticFeedback
import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.ReorderableItem
@ -60,6 +64,12 @@ fun StreamSelectUI(
uiState: NewPlayerUIState uiState: NewPlayerUIState
) { ) {
val insets = getInsets() val insets = getInsets()
val embeddedUiConfig = if (LocalContext.current is Activity)
getEmbeddedUiConfig(activity = LocalContext.current as Activity)
else
EmbeddedUiConfig.DUMMY
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = STREAMSELECT_UI_BACKGROUND_COLOR color = STREAMSELECT_UI_BACKGROUND_COLOR
@ -72,8 +82,12 @@ fun StreamSelectUI(
topBar = { topBar = {
if (isChapterSelect) { if (isChapterSelect) {
ChapterSelectTopBar( ChapterSelectTopBar(
onClose = onClose = {
viewModel::closeStreamSelection viewModel.changeUiMode(
uiState.uiMode.getUiHiddenState(),
embeddedUiConfig
)
}
) )
} else { } else {
StreamSelectTopBar(viewModel = viewModel, uiState = uiState) StreamSelectTopBar(viewModel = viewModel, uiState = uiState)
@ -142,7 +156,9 @@ fun ReorderableStreamItemsList(
verticalArrangement = Arrangement.spacedBy(5.dp), verticalArrangement = Arrangement.spacedBy(5.dp),
state = lazyListState state = lazyListState
) { ) {
itemsIndexed(uiState.playList, key = { _, item -> item.mediaId.toLong() }) { index, playlistItem -> itemsIndexed(
uiState.playList,
key = { _, item -> item.mediaId.toLong() }) { index, playlistItem ->
ReorderableItem( ReorderableItem(
state = reorderableLazyListState, state = reorderableLazyListState,
key = playlistItem.mediaId.toLong() key = playlistItem.mediaId.toLong()

View File

@ -60,7 +60,7 @@ fun VideoPlayerControllerUI(
val insets = getInsets() val insets = getInsets()
AnimatedVisibility(uiState.uiMode.controllerUiVisible) { AnimatedVisibility(uiState.uiMode.videoControllerUiVisible) {
Surface( Surface(
modifier = Modifier.fillMaxSize(), color = CONTROLLER_UI_BACKGROUND_COLOR modifier = Modifier.fillMaxSize(), color = CONTROLLER_UI_BACKGROUND_COLOR
) {} ) {}
@ -82,7 +82,7 @@ fun VideoPlayerControllerUI(
} }
} }
AnimatedVisibility(uiState.uiMode.controllerUiVisible) { AnimatedVisibility(uiState.uiMode.videoControllerUiVisible) {
AnimatedVisibility(visible = !uiState.isLoading) { AnimatedVisibility(visible = !uiState.isLoading) {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {

View File

@ -77,10 +77,13 @@ fun BottomUI(
} }
IconButton( IconButton(
onClick = if (uiState.uiMode.fullscreen) viewModel::switchToEmbeddedView onClick = if (uiState.uiMode.fullscreen) {
else { {
{ // <- head of lambda ... yea kotlin is weird viewModel.changeUiMode(UIModeState.EMBEDDED_VIDEO, embeddedUiConfig)
viewModel.switchToFullscreen(embeddedUiConfig) }
} else {
{
viewModel.changeUiMode(UIModeState.FULLSCREEN_VIDEO, embeddedUiConfig)
} }
} }
) { ) {
@ -94,7 +97,6 @@ fun BottomUI(
} }
/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////
// Preview // Preview
/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////

View File

@ -53,6 +53,7 @@ import net.newpipe.newplayer.R
import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerUIState
import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModel
import net.newpipe.newplayer.model.NewPlayerViewModelDummy import net.newpipe.newplayer.model.NewPlayerViewModelDummy
import net.newpipe.newplayer.model.UIModeState
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.ui.theme.video_player_onSurface import net.newpipe.newplayer.ui.theme.video_player_onSurface
import net.newpipe.newplayer.utils.getEmbeddedUiConfig import net.newpipe.newplayer.utils.getEmbeddedUiConfig
@ -103,7 +104,12 @@ fun TopUI(
} }
AnimatedVisibility(visible = uiState.chapters.isNotEmpty()) { AnimatedVisibility(visible = uiState.chapters.isNotEmpty()) {
IconButton( IconButton(
onClick = { viewModel.openStreamSelection(selectChapter = true, embeddedUiConfig) }, onClick = {
viewModel.changeUiMode(
uiState.uiMode.getChapterSelectUiState(),
embeddedUiConfig
)
}
) { ) {
Icon( Icon(
imageVector = Icons.AutoMirrored.Filled.MenuBook, imageVector = Icons.AutoMirrored.Filled.MenuBook,
@ -114,8 +120,8 @@ fun TopUI(
AnimatedVisibility(visible = 1 < uiState.playList.size) { AnimatedVisibility(visible = 1 < uiState.playList.size) {
IconButton( IconButton(
onClick = { onClick = {
viewModel.openStreamSelection( viewModel.changeUiMode(
selectChapter = false, uiState.uiMode.getStreamSelectUiState(),
embeddedUiConfig embeddedUiConfig
) )
}, },

View File

@ -23,6 +23,7 @@ package net.newpipe.newplayer.ui.videoplayer.gesture_ui
import android.app.Activity import android.app.Activity
import android.util.Log import android.util.Log
import androidx.annotation.OptIn
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -38,14 +39,17 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
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 androidx.media3.common.util.UnstableApi
import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerUIState
import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModel
import net.newpipe.newplayer.model.NewPlayerViewModelDummy import net.newpipe.newplayer.model.NewPlayerViewModelDummy
import net.newpipe.newplayer.model.UIModeState
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.utils.getEmbeddedUiConfig import net.newpipe.newplayer.utils.getEmbeddedUiConfig
private const val TAG = "EmbeddedGestureUI" private const val TAG = "EmbeddedGestureUI"
@OptIn(UnstableApi::class)
@Composable @Composable
fun EmbeddedGestureUI( fun EmbeddedGestureUI(
modifier: Modifier = Modifier, viewModel: NewPlayerViewModel, uiState: NewPlayerUIState modifier: Modifier = Modifier, viewModel: NewPlayerViewModel, uiState: NewPlayerUIState
@ -66,7 +70,7 @@ fun EmbeddedGestureUI(
// this check is there to allow a temporary move up in the downward gesture // this check is there to allow a temporary move up in the downward gesture
if (downwardMovementMode == false) { if (downwardMovementMode == false) {
viewModel.switchToFullscreen(embeddedUiConfig) viewModel.changeUiMode(UIModeState.FULLSCREEN_VIDEO, embeddedUiConfig)
} else { } else {
viewModel.embeddedDraggedDown(movement.y) viewModel.embeddedDraggedDown(movement.y)
} }
@ -78,7 +82,7 @@ fun EmbeddedGestureUI(
} }
val defaultOnRegularTap = { val defaultOnRegularTap = {
if (uiState.uiMode.controllerUiVisible) { if (uiState.uiMode.videoControllerUiVisible) {
viewModel.hideUi() viewModel.hideUi()
} else { } else {
viewModel.showUi() viewModel.showUi()
@ -151,6 +155,7 @@ fun EmbeddedGestureUI(
} }
@OptIn(UnstableApi::class)
@Preview(device = "spec:width=600px,height=400px,dpi=440,orientation=landscape") @Preview(device = "spec:width=600px,height=400px,dpi=440,orientation=landscape")
@Composable @Composable
fun EmbeddedGestureUIPreview() { fun EmbeddedGestureUIPreview() {
@ -159,10 +164,6 @@ fun EmbeddedGestureUIPreview() {
EmbeddedGestureUI( EmbeddedGestureUI(
modifier = Modifier, modifier = Modifier,
viewModel = object : NewPlayerViewModelDummy() { viewModel = object : NewPlayerViewModelDummy() {
override fun switchToEmbeddedView() {
println("switch to fullscreen")
}
override fun embeddedDraggedDown(offset: Float) { override fun embeddedDraggedDown(offset: Float) {
println("embedded view dragged down by $offset") println("embedded view dragged down by $offset")
} }

View File

@ -22,6 +22,7 @@
package net.newpipe.newplayer.ui.videoplayer.gesture_ui package net.newpipe.newplayer.ui.videoplayer.gesture_ui
import android.app.Activity import android.app.Activity
import androidx.annotation.OptIn
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.Spring import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring import androidx.compose.animation.core.spring
@ -44,17 +45,20 @@ 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 androidx.media3.common.util.UnstableApi
import net.newpipe.newplayer.model.UIModeState import net.newpipe.newplayer.model.UIModeState
import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerUIState
import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModel
import net.newpipe.newplayer.model.NewPlayerViewModelDummy import net.newpipe.newplayer.model.NewPlayerViewModelDummy
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.utils.getDefaultBrightness import net.newpipe.newplayer.utils.getDefaultBrightness
import net.newpipe.newplayer.utils.getEmbeddedUiConfig
private enum class IndicatorMode { private enum class IndicatorMode {
NONE, VOLUME_INDICATOR_VISSIBLE, BRIGHTNESS_INDICATOR_VISSIBLE NONE, VOLUME_INDICATOR_VISSIBLE, BRIGHTNESS_INDICATOR_VISSIBLE
} }
@OptIn(UnstableApi::class)
@Composable @Composable
fun FullscreenGestureUI( fun FullscreenGestureUI(
modifier: Modifier = Modifier, viewModel: NewPlayerViewModel, uiState: NewPlayerUIState modifier: Modifier = Modifier, viewModel: NewPlayerViewModel, uiState: NewPlayerUIState
@ -69,7 +73,7 @@ fun FullscreenGestureUI(
} }
val defaultOnRegularTap = { val defaultOnRegularTap = {
if (uiState.uiMode.controllerUiVisible) { if (uiState.uiMode.videoControllerUiVisible) {
viewModel.hideUi() viewModel.hideUi()
} else { } else {
viewModel.showUi() viewModel.showUi()
@ -79,6 +83,7 @@ fun FullscreenGestureUI(
val activity = LocalContext.current as Activity val activity = LocalContext.current as Activity
val defaultBrightness = getDefaultBrightness(activity) val defaultBrightness = getDefaultBrightness(activity)
val embeddedUiConfig = getEmbeddedUiConfig(activity = activity)
Box(modifier = modifier.onGloballyPositioned { coordinates -> Box(modifier = modifier.onGloballyPositioned { coordinates ->
heightPx = coordinates.size.height.toFloat() heightPx = coordinates.size.height.toFloat()
@ -119,7 +124,7 @@ fun FullscreenGestureUI(
onRegularTap = defaultOnRegularTap, onRegularTap = defaultOnRegularTap,
onMovement = { movement -> onMovement = { movement ->
if (0 < movement.y) { if (0 < movement.y) {
viewModel.switchToEmbeddedView() viewModel.changeUiMode(UIModeState.EMBEDDED_VIDEO, embeddedUiConfig)
} }
}, },
onMultiTap = { count -> onMultiTap = { count ->
@ -215,6 +220,7 @@ fun IndicatorAnimation(modifier: Modifier, visible: Boolean, content: @Composabl
} }
@OptIn(UnstableApi::class)
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape") @Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
@Composable @Composable
fun FullscreenGestureUIPreview() { fun FullscreenGestureUIPreview() {
@ -231,6 +237,7 @@ fun FullscreenGestureUIPreview() {
} }
} }
@OptIn(UnstableApi::class)
@Preview(device = "spec:parent=pixel_8,orientation=landscape") @Preview(device = "spec:parent=pixel_8,orientation=landscape")
@Composable @Composable
fun FullscreenGestureUIPreviewInteractive() { fun FullscreenGestureUIPreviewInteractive() {
@ -255,6 +262,7 @@ fun FullscreenGestureUIPreviewInteractive() {
Surface(modifier = Modifier.wrapContentSize(), color = Color.Gray) { Surface(modifier = Modifier.wrapContentSize(), color = Color.Gray) {
FullscreenGestureUI( FullscreenGestureUI(
modifier = Modifier, modifier = Modifier,
@OptIn(UnstableApi::class)
object : NewPlayerViewModelDummy() { object : NewPlayerViewModelDummy() {
override fun hideUi() { override fun hideUi() {
uiVisible = false uiVisible = false