add ui mode states
This commit is contained in:
parent
151ab85ea8
commit
8ac9a5a6ff
|
@ -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 |
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue