From 34e2ef6bd744c6dd88cc248788a7d81a683a75be Mon Sep 17 00:00:00 2001
From: beyond
Date: Sun, 6 Sep 2026 18:02:00 +0800
Subject: [PATCH] =?UTF-8?q?=E6=92=AD=E6=94=BE=E5=99=A8=E4=BC=98=E5=8C=96:?=
=?UTF-8?q?=20=E8=8F=9C=E5=8D=95=E7=8A=B6=E6=80=81=E5=90=88=E5=B9=B6?=
=?UTF-8?q?=E3=80=81=E8=BF=9E=E6=92=AD=E5=81=8F=E5=A5=BD=E6=8C=81=E4=B9=85?=
=?UTF-8?q?=E5=8C=96=E3=80=81=E9=97=B4=E8=B7=9D=E7=BB=9F=E4=B8=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增PlayerMenu枚举,替代5个独立boolean状态(showSubtitleMenu等)
- 菜单切换逻辑统一为activeMenu.toggle模式
- BackHandler简化为activeMenu != PlayerMenu.None
- 菜单onDismiss统一关闭activeMenu
- confirmBeforeContinuousPlayback持久化到SharedPreferences
- 顶部间距16.dp→LumaSpacing.md
- 清理sed替换产生的多余空行
Co-Authored-By: Claude Opus 5 (1M context)
---
.../screens/VideoPlayerScreen.kt | 112 ++++++++----------
1 file changed, 50 insertions(+), 62 deletions(-)
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
+
}
},
)