make embedded ui config foo have a more streamlinet implementation

This commit is contained in:
Christian Schabesberger 2024-08-29 10:40:36 +02:00
parent 888d518304
commit 8ad95be57a
12 changed files with 137 additions and 64 deletions

View File

@ -0,0 +1,4 @@
kotlin version: 2.0.20-Beta2
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready

View File

@ -55,13 +55,39 @@ enum class UIModeState {
else -> false
}
val systemUiVisible: Boolean
val isStreamSelect: Boolean
get() =
when (this) {
when(this) {
EMBEDDED_VIDEO_STREAM_SELECT -> true
FULLSCREEN_VIDEO_STREAM_SELECT -> true
else -> false
}
val isChapterSelect: Boolean
get() =
when(this) {
EMBEDDED_VIDEO_CHAPTER_SELECT -> true
FULLSCREEN_VIDEO_CHAPTER_SELECT -> true
else -> false
}
val systemInsetsVisible: Boolean
get() =
when(this) {
FULLSCREEN_VIDEO -> false
else -> true
}
val fitScreenRotation: Boolean
get() =
when(this) {
FULLSCREEN_VIDEO -> true
FULLSCREEN_VIDEO_CONTROLLER_UI -> true
FULLSCREEN_VIDEO_CHAPTER_SELECT -> true
FULLSCREEN_VIDEO_STREAM_SELECT -> true
else -> false
}
// STATE TRANSITIONS
fun getControllerUiVisibleState() =
@ -72,7 +98,6 @@ enum class UIModeState {
else -> this
}
fun getUiHiddenState() =
when (this) {
FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO

View File

@ -43,7 +43,7 @@ interface VideoPlayerViewModel {
fun pause()
fun prevStream()
fun nextStream()
fun switchToFullscreen()
fun switchToFullscreen(embeddedUiConfig: EmbeddedUiConfig)
fun switchToEmbeddedView()
fun showUi()
fun hideUi()
@ -54,5 +54,6 @@ interface VideoPlayerViewModel {
fun finishFastSeek()
fun brightnessChange(changeRate: Float, systemBrightness: Float)
fun volumeChange(changeRate: Float)
fun onReportEmbeddedConfig(embeddedUiConfig: EmbeddedUiConfig?)
fun openStreamSelection(selectChapter: Boolean)
fun closeStreamSelection()
}

View File

@ -65,12 +65,15 @@ class VideoPlayerViewModelImpl @Inject constructor(
private var uiVisibilityJob: Job? = null
private var progressUpdaterJob: Job? = null
// this is necesary to restore the embedded view UI configuration when returning from fullscreen
private var embeddedUiConfig: EmbeddedUiConfig? = null
private val audioManager =
getSystemService(application.applicationContext, AudioManager::class.java)!!
init {
val soundVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat() /
audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toFloat()
val soundVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
.toFloat() / audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toFloat()
mutableUiState.update {
it.copy(soundVolume = soundVolume)
}
@ -163,7 +166,8 @@ class VideoPlayerViewModelImpl @Inject constructor(
val currentMode = mutableUiState.value.uiMode.toPlayMode()
if (currentMode != newMode) {
mutableUiState.update {
it.copy(uiMode = UIModeState.fromPlayMode(newMode))
it.copy(uiMode = UIModeState.fromPlayMode(newMode),
embeddedUiConfig = embeddedUiConfig)
}
}
}
@ -353,18 +357,20 @@ class VideoPlayerViewModelImpl @Inject constructor(
}
}
override fun onReportEmbeddedConfig(embeddedUiConfig: EmbeddedUiConfig?) {
if (embeddedUiConfig == null) {
override fun openStreamSelection(selectChapter: Boolean) {
mutableUiState.update {
it.copy(embeddedUiConfig = null)
it.copy(
uiMode = if (selectChapter) it.uiMode.getChapterSelectUiState()
else it.uiMode.getStreamSelectUiState()
)
}
} else {
if (uiState.value.embeddedUiConfig == null) {
println("gurken: ${embeddedUiConfig}")
}
override fun closeStreamSelection() {
mutableUiState.update {
it.copy(embeddedUiConfig = embeddedUiConfig)
}
}
it.copy(
uiMode = it.uiMode.getUiHiddenState()
)
}
}
@ -374,10 +380,11 @@ class VideoPlayerViewModelImpl @Inject constructor(
updateUiMode(UIModeState.EMBEDDED_VIDEO)
}
override fun switchToFullscreen() {
override fun switchToFullscreen(embeddedUiConfig: EmbeddedUiConfig) {
uiVisibilityJob?.cancel()
finishFastSeek()
this.embeddedUiConfig = embeddedUiConfig
updateUiMode(UIModeState.FULLSCREEN_VIDEO)
}
@ -405,8 +412,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
newPlayer?.let { newPlayer ->
viewModelScope.launch {
val playlist = getPlaylistItemsFromItemList(
newPlayer.playlist,
newPlayer.repository
newPlayer.playlist, newPlayer.repository
)
mutableUiState.update {
it.copy(playList = playlist)

View File

@ -29,7 +29,7 @@ open class VideoPlayerViewModelDummy : VideoPlayerViewModel {
println("dummy impl")
}
override fun switchToFullscreen() {
override fun switchToFullscreen(embeddedUiConfig: EmbeddedUiConfig) {
println("dummy impl")
}
@ -69,7 +69,11 @@ open class VideoPlayerViewModelDummy : VideoPlayerViewModel {
println("dummy impl")
}
override fun onReportEmbeddedConfig(embeddedUiConfig: EmbeddedUiConfig?) {
override fun openStreamSelection(selectChapter: Boolean) {
println("dummy impl")
}
override fun closeStreamSelection() {
println("dummy impl")
}

View File

@ -119,7 +119,9 @@ fun VideoPlayerControllerUI(
.align(Alignment.TopStart)
.fillMaxWidth()
.defaultMinSize(minHeight = 45.dp)
.padding(top = 4.dp, start = 16.dp, end = 16.dp)
.padding(top = 4.dp, start = 16.dp, end = 16.dp),
viewModel = viewModel,
uiState = uiState
)
BottomUI(

View File

@ -24,6 +24,7 @@ import android.app.Activity
import android.content.pm.ActivityInfo
import android.util.Log
import android.view.SurfaceView
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
@ -37,7 +38,6 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -57,6 +57,7 @@ import net.newpipe.newplayer.model.EmbeddedUiConfig
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.StreamSelectUI
import net.newpipe.newplayer.utils.LockScreenOrientation
import net.newpipe.newplayer.utils.getDefaultBrightness
import net.newpipe.newplayer.utils.setScreenBrightness
@ -89,37 +90,17 @@ fun VideoPlayerUI(
val lifecycleOwner = LocalLifecycleOwner.current
val defaultBrightness = getDefaultBrightness(activity)
val screenOrientation = activity.requestedOrientation
// Setup fullscreen
var embeddedUiConfig: EmbeddedUiConfig? by rememberSaveable {
mutableStateOf(null)
}
LaunchedEffect(uiState.uiMode.fullscreen) {
if (uiState.uiMode.fullscreen) {
viewModel.onReportEmbeddedConfig(
EmbeddedUiConfig(
WindowCompat.getInsetsController(
window,
view
).isAppearanceLightStatusBars,
defaultBrightness,
screenOrientation,
)
)
} else {
viewModel.onReportEmbeddedConfig(null)
}
}
LaunchedEffect(uiState.uiMode.fullscreen) {
if (uiState.uiMode.fullscreen) {
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars =
false
} else {
println("gurken dings")
uiState.embeddedUiConfig?.let {
println("gurken bumbs")
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars =
it.systemBarInLightMode
}
@ -128,17 +109,16 @@ fun VideoPlayerUI(
// setup immersive mode
LaunchedEffect(
key1 = uiState.uiMode.controllerUiVisible,
key2 = uiState.uiMode.fullscreen
key1 = uiState.uiMode.systemInsetsVisible,
) {
if (uiState.uiMode.fullscreen && !uiState.uiMode.systemUiVisible) {
if (uiState.uiMode.fullscreen && !uiState.uiMode.systemInsetsVisible) {
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
} else {
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
}
}
if (uiState.uiMode.fullscreen) {
if (uiState.uiMode.fitScreenRotation) {
if (uiState.contentRatio < 1) {
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
} else {
@ -195,9 +175,19 @@ fun VideoPlayerUI(
)
}
// the checks if VideoPlayerControllerUI should be visible or not are done by
// The VideoPlayerControllerUI composable itself. This is because Visibility of
// the controller is more complicated than just using a simple if statement.
VideoPlayerControllerUI(
viewModel, uiState = uiState
)
AnimatedVisibility(visible = uiState.uiMode.isStreamSelect) {
StreamSelectUI(viewModel = viewModel, uiState = uiState, isChapterSelect = false)
}
AnimatedVisibility(visible = uiState.uiMode.isChapterSelect) {
StreamSelectUI(viewModel = viewModel, uiState = uiState, isChapterSelect = true)
}
}
}
}

View File

@ -20,6 +20,7 @@
package net.newpipe.newplayer.ui.videoplayer
import android.app.Activity
import android.app.LocaleConfig
import android.icu.text.DecimalFormat
import androidx.compose.foundation.layout.Arrangement
@ -38,6 +39,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.os.ConfigurationCompat
@ -51,6 +53,7 @@ import net.newpipe.newplayer.ui.seeker.Seeker
import net.newpipe.newplayer.ui.seeker.SeekerColors
import net.newpipe.newplayer.ui.seeker.SeekerDefaults
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.utils.getEmbeddedUiConfig
import net.newpipe.newplayer.utils.getLocale
import net.newpipe.newplayer.utils.getTimeStringFromMs
import java.util.Locale
@ -79,9 +82,14 @@ fun BottomUI(
Text(getTimeStringFromMs(uiState.durationInMs, getLocale() ?: Locale.US))
val embeddedUiConfig = getEmbeddedUiConfig(LocalContext.current as Activity)
IconButton(
onClick = if (uiState.uiMode.fullscreen) viewModel::switchToEmbeddedView
else viewModel::switchToFullscreen
else {
{ // <- head of lambda ... yea kotlin is weird
viewModel.switchToFullscreen(embeddedUiConfig)
}
}
) {
Icon(
imageVector = if (uiState.uiMode.fullscreen) Icons.Filled.FullscreenExit

View File

@ -80,7 +80,7 @@ fun StreamSelectUI(
topBar = {
if (isChapterSelect) {
ChapterSelectTopBar(onClose = {
TODO("implement me")
viewModel.closeStreamSelection()
})
} else {
StreamSelectTopBar()

View File

@ -44,11 +44,18 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
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.theme.VideoPlayerTheme
import net.newpipe.newplayer.ui.theme.video_player_onSurface
@Composable
fun TopUI(modifier: Modifier) {
fun TopUI(
modifier: Modifier,
viewModel: VideoPlayerViewModel,
uiState: VideoPlayerUIState
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
@ -81,7 +88,7 @@ fun TopUI(modifier: Modifier) {
)
}
IconButton(
onClick = { /*TODO*/ },
onClick = { viewModel.openStreamSelection(selectChapter = true) },
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.MenuBook,
@ -89,7 +96,7 @@ fun TopUI(modifier: Modifier) {
)
}
IconButton(
onClick = { /*TODO*/ },
onClick = { viewModel.openStreamSelection(selectChapter = false) },
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.List,
@ -109,7 +116,7 @@ fun TopUI(modifier: Modifier) {
fun VideoPlayerControllerTopUIPreview() {
VideoPlayerTheme {
Surface(color = Color.Black) {
TopUI(modifier = Modifier)
TopUI(modifier = Modifier, VideoPlayerViewModelDummy(), VideoPlayerUIState.DEFAULT)
}
}
}

View File

@ -21,6 +21,7 @@
package net.newpipe.newplayer.ui.videoplayer.gesture_ui
import android.app.Activity
import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
@ -35,11 +36,13 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.utils.getEmbeddedUiConfig
private const val TAG = "EmbeddedGestureUI"
@ -52,6 +55,8 @@ fun EmbeddedGestureUI(
mutableStateOf(false)
}
val embeddedUiConfig = getEmbeddedUiConfig(LocalContext.current as Activity)
val handleMovement = { movement: TouchedPosition ->
Log.d(TAG, "${movement.x}:${movement.y}")
if (0 < movement.y) {
@ -61,7 +66,7 @@ fun EmbeddedGestureUI(
// this check is there to allow a temporary move up in the downward gesture
if (downwardMovementMode == false) {
viewModel.switchToFullscreen()
viewModel.switchToFullscreen(embeddedUiConfig)
} else {
viewModel.embeddedDraggedDown(movement.y)
}

View File

@ -31,7 +31,10 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.os.ConfigurationCompat
import androidx.core.view.WindowCompat
import net.newpipe.newplayer.model.EmbeddedUiConfig
import java.util.Locale
@Composable
@ -44,14 +47,14 @@ fun LockScreenOrientation(orientation: Int) {
}
@SuppressLint("NewApi")
fun getDefaultBrightness(activity: Activity) : Float {
fun getDefaultBrightness(activity: Activity): Float {
val window = activity.window
val layout = window.attributes as WindowManager.LayoutParams
return if(layout.screenBrightness < 0) 0.5f else layout.screenBrightness
return if (layout.screenBrightness < 0) 0.5f else layout.screenBrightness
}
@SuppressLint("NewApi")
fun setScreenBrightness(value:Float, activity: Activity) {
fun setScreenBrightness(value: Float, activity: Activity) {
val window = activity.window
val layout = window.attributes as WindowManager.LayoutParams
layout.screenBrightness = value
@ -72,6 +75,24 @@ fun getLocale(): Locale? {
return ConfigurationCompat.getLocales(configuration).get(0)
}
@Composable
@ReadOnlyComposable
fun getEmbeddedUiConfig(activity: Activity): EmbeddedUiConfig {
val window = activity.window
val view = LocalView.current
val isLightStatusBar = WindowCompat.getInsetsController(
window,
view
).isAppearanceLightStatusBars
val screenOrientation = activity.requestedOrientation
val defaultBrightness = getDefaultBrightness(activity)
return EmbeddedUiConfig(
systemBarInLightMode = isLightStatusBar,
brightness = defaultBrightness,
screenOrientation = screenOrientation
)
}
private const val HOURS_PER_DAY = 24
private const val MINUTES_PER_HOUR = 60