From a4267d85cec88837249f95bba10a66127ca99292 Mon Sep 17 00:00:00 2001 From: Barnabas Balogh Date: Fri, 19 Jun 2026 11:42:16 +0000 Subject: [PATCH] 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 --- .../ui/screen/player/TvPlayerScreen.kt | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/player/TvPlayerScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/player/TvPlayerScreen.kt index 8067b08f..97267cdc 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/player/TvPlayerScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/player/TvPlayerScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -97,12 +98,16 @@ fun TvPlayerScreen( var controlsVisible by remember { mutableStateOf(true) } var isPlaylistExpanded by remember { mutableStateOf(false) } var trackPanelType by remember { mutableStateOf(null) } - // TODO why this is needed??? var pendingTrackButtonFocus by remember { mutableStateOf(null) } - var hiddenSeekPreviewPositionMs by remember { mutableStateOf(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 backgroundFocusRequester = remember { FocusRequester() } // Main section focus requesters @@ -180,7 +185,6 @@ fun TvPlayerScreen( && uiState.durationMs > 0L && (uiState.durationMs - uiState.positionMs) <= 60_000L && !uiState.isEnded - val showHiddenSeekPreview = !controlsVisible && hiddenSeekPreviewPositionMs != null LaunchedEffect(showSkipIntroButton, showNextEpisodeOverlay) { rootFocusRequester = when { showSkipIntroButton -> { @@ -210,13 +214,6 @@ fun TvPlayerScreen( 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 = if (controlsVisible) { CONTROLS_VISIBLE_SUBTITLE_BOTTOM_PADDING_FRACTION @@ -254,9 +251,16 @@ fun TvPlayerScreen( onCloseTrackPanel = closeTrackPanel, onCollapsePlaylist = {}, onHideControls = ::hideControls, - onPausePlayback = { viewModel.pausePlayback() }, - onResumePlayback = { viewModel.resumePlayback() }, - onSeekRelative = { viewModel.seekBy(it) }, + onTogglePlayback = { + // This is a hack to trigger the ValueChangeTimedVisibility to show the hidden resume/stop feedback. + 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) { hideControlsWithTimeout() @@ -331,7 +335,7 @@ fun TvPlayerScreen( .align(Alignment.BottomEnd) .padding( end = 24.dp, - bottom = if (hiddenSeekPreviewPositionMs != null) 104.dp else 24.dp + bottom = 24.dp ) ) { TvIconButton( @@ -362,15 +366,15 @@ fun TvPlayerScreen( } } - // TODO: use TimedVisibility - AnimatedVisibility( - visible = showHiddenSeekPreview, + ValueChangeTimedVisibility( + value = hiddenSeekCounter, + hideAfterMillis = 2500L, modifier = Modifier .align(Alignment.BottomCenter) .padding(horizontal = 32.dp, vertical = 28.dp) ) { HiddenTvSeekTimeline( - positionMs = hiddenSeekPreviewPositionMs ?: uiState.positionMs, + positionMs = uiState.positionMs, durationMs = uiState.durationMs, bufferedMs = uiState.bufferedMs, chapterMarkers = uiState.chapters, @@ -391,10 +395,11 @@ fun TvPlayerScreen( ) ValueChangeTimedVisibility( - value = uiState.isPlaying, + value = resumeStopFeedbackCounter, + hideAfterMillis = TV_HIDDEN_STOP_FEEDBACK_MS, modifier = Modifier.align(Alignment.Center) - ) { value -> - TvPlayerResumeStopFeedback(resume = value) + ) { + TvPlayerResumeStopFeedback(resume = uiState.isPlaying) } AnimatedVisibility( @@ -468,8 +473,7 @@ internal fun handleTvPlayerRootKeyEvent( onCloseTrackPanel: () -> Unit, onCollapsePlaylist: () -> Unit, onHideControls: () -> Unit, - onPausePlayback: () -> Unit, - onResumePlayback: () -> Unit, + onTogglePlayback: () -> Unit, onSeekRelative: (Long) -> Unit, onShowControls: () -> Unit, ): Boolean { @@ -504,7 +508,7 @@ internal fun handleTvPlayerRootKeyEvent( } Key.DirectionRight -> { - onSeekRelative(10_000) + onSeekRelative(30_000) true } @@ -517,11 +521,8 @@ internal fun handleTvPlayerRootKeyEvent( if (popupVisible) { // Do nothing because the focused component is not he root, but an overlay false - } else if (isPlaying) { - onPausePlayback() - true } else { - onResumePlayback() + onTogglePlayback() true } }