diff --git a/app/src/main/kotlin/com/example/myapplication/screens/VideoPlayerScreen.kt b/app/src/main/kotlin/com/example/myapplication/screens/VideoPlayerScreen.kt index 12d8543..1623404 100644 --- a/app/src/main/kotlin/com/example/myapplication/screens/VideoPlayerScreen.kt +++ b/app/src/main/kotlin/com/example/myapplication/screens/VideoPlayerScreen.kt @@ -114,6 +114,7 @@ import com.example.myapplication.ui.design.LumaGlassLevel import com.example.myapplication.ui.design.lumaGlass import com.example.myapplication.ui.design.LumaShapes import com.example.myapplication.ui.design.LumaColors +import com.example.myapplication.ui.design.LumaSpacing import com.example.myapplication.ui.LocalGlassBackdrop import kotlinx.coroutines.delay import kotlinx.coroutines.CoroutineScope @@ -250,6 +251,11 @@ private enum class PlayerDoubleTapFeedback { SeekForward, } +/** Consolidated menu state — replaces 5 separate boolean flags. */ +private enum class PlayerMenu { + None, Subtitle, Episode, Audio, PlaybackTools, NextEpisodeConfirmation, +} + private tailrec fun Context.findActivity(): Activity? = when (this) { is Activity -> this is ContextWrapper -> baseContext.findActivity() @@ -281,12 +287,12 @@ fun VideoPlayerScreen( val configuration = LocalConfiguration.current val cannotPlayMessage = stringResource(Res.string.player_cannot_play) val lifecycleOwner = LocalLifecycleOwner.current - var showSubtitleMenu by remember { mutableStateOf(false) } - var showEpisodeMenu by remember { mutableStateOf(false) } - var showAudioMenu by remember { mutableStateOf(false) } - var showPlaybackToolsMenu by remember { mutableStateOf(false) } + var activeMenu by remember { mutableStateOf(PlayerMenu.None) } var showNextEpisodeConfirmation by remember { mutableStateOf(false) } - var confirmBeforeContinuousPlayback by remember { mutableStateOf(true) } + var confirmBeforeContinuousPlayback by remember { + val prefs = context.getSharedPreferences("player_settings", Context.MODE_PRIVATE) + mutableStateOf(prefs.getBoolean("confirm_before_continuous", true)) + } var audioDisabledByUser by remember { mutableStateOf(false) } val speedMonitor = remember { SpeedMonitor() } val transferListener = remember { @@ -366,14 +372,10 @@ fun VideoPlayerScreen( controlsVisible, isPlaying, controlsInteractionLocked, - showSubtitleMenu, - showEpisodeMenu, - showAudioMenu, - showPlaybackToolsMenu, + activeMenu, showNextEpisodeConfirmation, ) { - val menuVisible = showSubtitleMenu || showEpisodeMenu || showAudioMenu || - showPlaybackToolsMenu || showNextEpisodeConfirmation + val menuVisible = activeMenu != PlayerMenu.None || showNextEpisodeConfirmation if (controlsVisible && isPlaying && !controlsInteractionLocked && !menuVisible) { delay(5000) controlsVisible = false @@ -496,10 +498,10 @@ fun VideoPlayerScreen( val nextEpisode = latestEpisodes.getOrNull(currentIndex + 1) ?: return if (latestConfirmBeforeContinuousPlayback) { controlsVisible = true - showSubtitleMenu = false - showEpisodeMenu = false - showAudioMenu = false - showPlaybackToolsMenu = false + + + + showNextEpisodeConfirmation = true } else { latestOnSelectEpisode(nextEpisode) @@ -543,16 +545,13 @@ fun VideoPlayerScreen( } BackHandler(enabled = true) { when { - showSubtitleMenu || showEpisodeMenu || showAudioMenu || showPlaybackToolsMenu || showNextEpisodeConfirmation -> { - showSubtitleMenu = false - showEpisodeMenu = false - showAudioMenu = false - showPlaybackToolsMenu = false + activeMenu != PlayerMenu.None || showNextEpisodeConfirmation -> { + activeMenu = PlayerMenu.None showNextEpisodeConfirmation = false } isScreenLocked -> { - isScreenLocked = false - controlsVisible = true + isScreenLocked = false + controlsVisible = true } else -> onClose() } @@ -849,10 +848,7 @@ fun VideoPlayerScreen( ) { isScreenLocked = !isScreenLocked controlsVisible = true - showSubtitleMenu = false - showEpisodeMenu = false - showAudioMenu = false - showPlaybackToolsMenu = false + activeMenu = PlayerMenu.None } if (!isScreenLocked) { @@ -910,7 +906,7 @@ fun VideoPlayerScreen( } } - Column(Modifier.fillMaxWidth().statusBarsPadding().padding(top = 16.dp)) { + Column(Modifier.fillMaxWidth().statusBarsPadding().padding(top = LumaSpacing.md)) { // P0: Delegated to extracted composable — only this component polls and recomposes PlayerStatusBar( playbackUrl = playbackUrl, @@ -948,46 +944,34 @@ fun VideoPlayerScreen( LiquidSurfaceButton( icon = LumaIcon.Subtitle, contentDescription = stringResource(Res.string.player_subtitle), - active = showSubtitleMenu, + active = activeMenu == PlayerMenu.Subtitle, size = 40.dp, ) { - showSubtitleMenu = !showSubtitleMenu - showEpisodeMenu = false - showAudioMenu = false - showPlaybackToolsMenu = false + activeMenu = if (activeMenu == PlayerMenu.Subtitle) PlayerMenu.None else PlayerMenu.Subtitle } LiquidSurfaceButton( icon = LumaIcon.Watch, contentDescription = stringResource(Res.string.player_episode_list), - active = showEpisodeMenu, + active = activeMenu == PlayerMenu.Episode, size = 40.dp, ) { - showEpisodeMenu = !showEpisodeMenu - showSubtitleMenu = false - showAudioMenu = false - showPlaybackToolsMenu = false + activeMenu = if (activeMenu == PlayerMenu.Episode) PlayerMenu.None else PlayerMenu.Episode } LiquidSurfaceButton( icon = LumaIcon.Headphones, contentDescription = stringResource(Res.string.player_audio_tracks), - active = showAudioMenu, + active = activeMenu == PlayerMenu.Audio, size = 40.dp, ) { - showAudioMenu = !showAudioMenu - showSubtitleMenu = false - showEpisodeMenu = false - showPlaybackToolsMenu = false + activeMenu = if (activeMenu == PlayerMenu.Audio) PlayerMenu.None else PlayerMenu.Audio } LiquidSurfaceButton( icon = LumaIcon.More, contentDescription = stringResource(Res.string.player_playback_tools), - active = showPlaybackToolsMenu, + active = activeMenu == PlayerMenu.PlaybackTools, size = 40.dp, ) { - showPlaybackToolsMenu = !showPlaybackToolsMenu - showSubtitleMenu = false - showEpisodeMenu = false - showAudioMenu = false + activeMenu = if (activeMenu == PlayerMenu.PlaybackTools) PlayerMenu.None else PlayerMenu.PlaybackTools } } } @@ -1155,25 +1139,25 @@ fun VideoPlayerScreen( ) } - if (showSubtitleMenu) { + if (activeMenu == PlayerMenu.Subtitle) { LiquidGlassPanel( title = stringResource(Res.string.player_subtitle), - onDismiss = { showSubtitleMenu = false }, + onDismiss = { activeMenu = PlayerMenu.None }, ) { SubtitleMenuContent( player = player, onTrackChanged = { eventName -> latestOnPlaybackProgress(player.currentPosition, !player.isPlaying, eventName, true) - showSubtitleMenu = false + }, ) } } - if (showEpisodeMenu) { + if (activeMenu == PlayerMenu.Episode) { LiquidGlassPanel( title = title.ifBlank { stringResource(Res.string.player_episode_list) }, titleLogoUrl = titleLogoUrl, - onDismiss = { showEpisodeMenu = false }, + onDismiss = { activeMenu = PlayerMenu.None }, ) { if (episodes.isEmpty()) { Text( @@ -1189,52 +1173,56 @@ fun VideoPlayerScreen( episode = ep, serverUrl = serverUrl, selected = ep.id == currentEpisodeId, - ) { showEpisodeMenu = false; onSelectEpisode(ep) } + ) { ; onSelectEpisode(ep) } } } } } } - if (showAudioMenu) { + if (activeMenu == PlayerMenu.Audio) { LiquidGlassPanel( title = stringResource(Res.string.player_audio_tracks), - onDismiss = { showAudioMenu = false }, + onDismiss = { activeMenu = PlayerMenu.None }, ) { AudioMenuContent( player = player, onAudioDisabledChange = { audioDisabledByUser = it }, onTrackChanged = { eventName -> latestOnPlaybackProgress(player.currentPosition, !player.isPlaying, eventName, true) - showAudioMenu = false + }, ) } } - if (showPlaybackToolsMenu) { + if (activeMenu == PlayerMenu.PlaybackTools) { LiquidGlassPanel( title = stringResource(Res.string.player_playback_tools), - onDismiss = { showPlaybackToolsMenu = false }, + onDismiss = { activeMenu = PlayerMenu.None }, ) { PlaybackToolsMenuContent( chapters = chapters, confirmBeforeContinuousPlayback = confirmBeforeContinuousPlayback, - onConfirmBeforeContinuousPlaybackChange = { confirmBeforeContinuousPlayback = it }, + onConfirmBeforeContinuousPlaybackChange = { newValue -> + confirmBeforeContinuousPlayback = newValue + context.getSharedPreferences("player_settings", Context.MODE_PRIVATE) + .edit().putBoolean("confirm_before_continuous", newValue).apply() + }, onSeek = { targetPositionMs -> player.seekTo(targetPositionMs) latestOnPlaybackProgress(targetPositionMs, !player.isPlaying, "TimeUpdate", true) - showPlaybackToolsMenu = false + }, onSkipOutro = { val currentIndex = episodes.indexOfFirst { it.id == currentEpisodeId } val nextEpisode = episodes.getOrNull(currentIndex + 1) if (nextEpisode != null) { - showPlaybackToolsMenu = false + onSelectEpisode(nextEpisode) } else { val targetPositionMs = (player.duration - 500L).coerceAtLeast(0L) player.seekTo(targetPositionMs) latestOnPlaybackProgress(targetPositionMs, !player.isPlaying, "TimeUpdate", true) - showPlaybackToolsMenu = false + } }, )