refactor(player): replace manual hidden UI visibility with ValueChangeTimedVisibility counters

- Replace hiddenSeekPreviewPositionMs with hiddenSeek counter for ValueChangeTimedVisibility
- Add resumeStopFeedbackCounter to decouple feedback visibility from isPlaying state
- Consolidate onPausePlayback/onResumePlayback into single onTogglePlayback
- Increase right D-pad seek from 10s to 30s
- Remove obsolete LaunchedEffect resetting hidden seek state
This commit is contained in:
2026-06-19 11:42:16 +00:00
parent a07f9dd6e4
commit a4267d85ce

View File

@@ -31,6 +31,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@@ -97,12 +98,16 @@ fun TvPlayerScreen(
var controlsVisible by remember { mutableStateOf(true) } var controlsVisible by remember { mutableStateOf(true) }
var isPlaylistExpanded by remember { mutableStateOf(false) } var isPlaylistExpanded by remember { mutableStateOf(false) }
var trackPanelType by remember { mutableStateOf<TvTrackPanelType?>(null) } var trackPanelType by remember { mutableStateOf<TvTrackPanelType?>(null) }
// TODO why this is needed???
var pendingTrackButtonFocus by remember { mutableStateOf<TvTrackPanelType?>(null) } var pendingTrackButtonFocus by remember { mutableStateOf<TvTrackPanelType?>(null) }
var hiddenSeekPreviewPositionMs by remember { mutableStateOf<Long?>(null) }
// This is a hack for timed visibility.
var resumeStopFeedbackCounter by remember { mutableIntStateOf(0) }
var hiddenSeekCounter by remember { mutableIntStateOf(0) }
val context = LocalContext.current val context = LocalContext.current
val backgroundFocusRequester = remember { FocusRequester() } val backgroundFocusRequester = remember { FocusRequester() }
// Main section focus requesters // Main section focus requesters
@@ -180,7 +185,6 @@ fun TvPlayerScreen(
&& uiState.durationMs > 0L && uiState.durationMs > 0L
&& (uiState.durationMs - uiState.positionMs) <= 60_000L && (uiState.durationMs - uiState.positionMs) <= 60_000L
&& !uiState.isEnded && !uiState.isEnded
val showHiddenSeekPreview = !controlsVisible && hiddenSeekPreviewPositionMs != null
LaunchedEffect(showSkipIntroButton, showNextEpisodeOverlay) { LaunchedEffect(showSkipIntroButton, showNextEpisodeOverlay) {
rootFocusRequester = when { rootFocusRequester = when {
showSkipIntroButton -> { showSkipIntroButton -> {
@@ -210,13 +214,6 @@ fun TvPlayerScreen(
pendingTrackButtonFocus = null pendingTrackButtonFocus = null
} }
// TODO check if neccessary
LaunchedEffect(controlsVisible, isPlaylistExpanded, trackPanelType, uiState.isEnded, uiState.error) {
if (controlsVisible || isPlaylistExpanded || trackPanelType != null || uiState.isEnded || uiState.error != null) {
hiddenSeekPreviewPositionMs = null
}
}
val subtitleBottomPaddingFraction = val subtitleBottomPaddingFraction =
if (controlsVisible) { if (controlsVisible) {
CONTROLS_VISIBLE_SUBTITLE_BOTTOM_PADDING_FRACTION CONTROLS_VISIBLE_SUBTITLE_BOTTOM_PADDING_FRACTION
@@ -254,9 +251,16 @@ fun TvPlayerScreen(
onCloseTrackPanel = closeTrackPanel, onCloseTrackPanel = closeTrackPanel,
onCollapsePlaylist = {}, onCollapsePlaylist = {},
onHideControls = ::hideControls, onHideControls = ::hideControls,
onPausePlayback = { viewModel.pausePlayback() }, onTogglePlayback = {
onResumePlayback = { viewModel.resumePlayback() }, // This is a hack to trigger the ValueChangeTimedVisibility to show the hidden resume/stop feedback.
onSeekRelative = { viewModel.seekBy(it) }, resumeStopFeedbackCounter++
viewModel.togglePlayPause()
},
onSeekRelative = {
// This is a hack to trigger the ValueChangeTimedVisibility to show the hidden seek timeline.
hiddenSeekCounter++
viewModel.seekBy(it)
},
) )
if (event.type == KeyEventType.KeyDown) { if (event.type == KeyEventType.KeyDown) {
hideControlsWithTimeout() hideControlsWithTimeout()
@@ -331,7 +335,7 @@ fun TvPlayerScreen(
.align(Alignment.BottomEnd) .align(Alignment.BottomEnd)
.padding( .padding(
end = 24.dp, end = 24.dp,
bottom = if (hiddenSeekPreviewPositionMs != null) 104.dp else 24.dp bottom = 24.dp
) )
) { ) {
TvIconButton( TvIconButton(
@@ -362,15 +366,15 @@ fun TvPlayerScreen(
} }
} }
// TODO: use TimedVisibility ValueChangeTimedVisibility(
AnimatedVisibility( value = hiddenSeekCounter,
visible = showHiddenSeekPreview, hideAfterMillis = 2500L,
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.padding(horizontal = 32.dp, vertical = 28.dp) .padding(horizontal = 32.dp, vertical = 28.dp)
) { ) {
HiddenTvSeekTimeline( HiddenTvSeekTimeline(
positionMs = hiddenSeekPreviewPositionMs ?: uiState.positionMs, positionMs = uiState.positionMs,
durationMs = uiState.durationMs, durationMs = uiState.durationMs,
bufferedMs = uiState.bufferedMs, bufferedMs = uiState.bufferedMs,
chapterMarkers = uiState.chapters, chapterMarkers = uiState.chapters,
@@ -391,10 +395,11 @@ fun TvPlayerScreen(
) )
ValueChangeTimedVisibility( ValueChangeTimedVisibility(
value = uiState.isPlaying, value = resumeStopFeedbackCounter,
hideAfterMillis = TV_HIDDEN_STOP_FEEDBACK_MS,
modifier = Modifier.align(Alignment.Center) modifier = Modifier.align(Alignment.Center)
) { value -> ) {
TvPlayerResumeStopFeedback(resume = value) TvPlayerResumeStopFeedback(resume = uiState.isPlaying)
} }
AnimatedVisibility( AnimatedVisibility(
@@ -468,8 +473,7 @@ internal fun handleTvPlayerRootKeyEvent(
onCloseTrackPanel: () -> Unit, onCloseTrackPanel: () -> Unit,
onCollapsePlaylist: () -> Unit, onCollapsePlaylist: () -> Unit,
onHideControls: () -> Unit, onHideControls: () -> Unit,
onPausePlayback: () -> Unit, onTogglePlayback: () -> Unit,
onResumePlayback: () -> Unit,
onSeekRelative: (Long) -> Unit, onSeekRelative: (Long) -> Unit,
onShowControls: () -> Unit, onShowControls: () -> Unit,
): Boolean { ): Boolean {
@@ -504,7 +508,7 @@ internal fun handleTvPlayerRootKeyEvent(
} }
Key.DirectionRight -> { Key.DirectionRight -> {
onSeekRelative(10_000) onSeekRelative(30_000)
true true
} }
@@ -517,11 +521,8 @@ internal fun handleTvPlayerRootKeyEvent(
if (popupVisible) { if (popupVisible) {
// Do nothing because the focused component is not he root, but an overlay // Do nothing because the focused component is not he root, but an overlay
false false
} else if (isPlaying) {
onPausePlayback()
true
} else { } else {
onResumePlayback() onTogglePlayback()
true true
} }
} }