播放器: 时间/网速/电量布局调整

- PlayerStatusBar拆分为PlayerTimeDisplay和PlayerNetworkInfo两个独立组件
- 时间显示在关闭按钮上方(左侧)
- 网速+电量显示在播放工具按钮上方(右侧)
- 减少间距从8dp到2dp

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
beyondandClaude Opus 5 committed 2026-09-06 18:47:45 +08:00
1 parent 7c991c04be
commit 702e9810d0
1 file changed
+47 -46
@@ -905,15 +905,22 @@ fun VideoPlayerScreen(
}
Column(Modifier.fillMaxWidth().statusBarsPadding().padding(top = LumaSpacing.md)) {
// P0: Delegated to extracted composable — only this component polls and recomposes
PlayerStatusBar(
playbackUrl = playbackUrl,
networkSpeed = networkSpeed,
isWifi = isWifi,
batteryPercent = batteryPercent,
isCharging = isCharging,
)
Spacer(Modifier.height(8.dp))
// 第一排:时间(左)| 网速+电量(右)
Row(
Modifier.fillMaxWidth().padding(horizontal = capsuleInset),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
PlayerTimeDisplay(modifier = Modifier)
PlayerNetworkInfo(
playbackUrl = playbackUrl,
networkSpeed = networkSpeed,
isWifi = isWifi,
batteryPercent = batteryPercent,
isCharging = isCharging,
)
}
Spacer(Modifier.height(2.dp))
// 第二排:关闭按钮(左)|Emby 艺术字(紧跟关闭按钮右侧)|字幕/剧集按钮(右)
Row(
Modifier.fillMaxWidth().padding(horizontal = capsuleInset),
@@ -974,9 +981,8 @@ fun VideoPlayerScreen(
}
}
if (serverName.isNotBlank()) {
Spacer(Modifier.height(8.dp))
// 第三排:服务器图标 + 服务器名字
Row(Modifier.fillMaxWidth().padding(start = capsuleInset + 52.dp, end = capsuleInset), verticalAlignment = Alignment.CenterVertically) {
Row(Modifier.fillMaxWidth().padding(start = capsuleInset + 52.dp, end = capsuleInset, top = 2.dp), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.size(30.dp).clip(CircleShape), contentAlignment = Alignment.Center) {
if (serverIconUrl.isNotBlank()) {
AsyncImage(
@@ -1624,51 +1630,46 @@ private fun LiquidMenuItem(text: String, selected: Boolean, onClick: () -> Unit)
}
}
// P0: Extract status bar into its own composable so polling states (time/wifi/battery/speed)
// only recompose this small component instead of the entire VideoPlayerScreen.
// Extract status bar into separate composables so polling states only recompose their own component.
@Composable
private fun PlayerStatusBar(
playbackUrl: String?,
networkSpeed: Long,
isWifi: Boolean,
batteryPercent: Int,
isCharging: Boolean,
capsuleInset: Dp = 18.dp,
) {
private fun PlayerTimeDisplay(modifier: Modifier = Modifier) {
val topTimeText = remember { mutableStateOf("") }
val localNetworkSpeed by rememberUpdatedState(networkSpeed)
val localIsWifi by rememberUpdatedState(isWifi)
val localBatteryPercent by rememberUpdatedState(batteryPercent)
val localIsCharging by rememberUpdatedState(isCharging)
// Clock update — only this composable recomposes
LaunchedEffect(Unit) {
while (true) {
topTimeText.value = java.text.SimpleDateFormat("HH:mm", java.util.Locale.getDefault()).format(java.util.Date())
delay(1000)
}
}
Text(topTimeText.value, color = Color.White, fontSize = 13.sp, modifier = modifier)
}
Row(
Modifier.fillMaxWidth().padding(horizontal = capsuleInset),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(topTimeText.value, color = Color.White, fontSize = 13.sp)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp)) {
if (playbackUrl != null) {
Row(verticalAlignment = Alignment.CenterVertically) {
LumaIconView(if (localIsWifi) LumaIcon.Wifi else LumaIcon.Signal, contentDescription = null, tint = Color.White, modifier = Modifier.size(16.dp))
Spacer(Modifier.width(4.dp))
Text(formatNetworkSpeed(localNetworkSpeed), color = Color.White, fontSize = 13.sp)
}
@Composable
private fun PlayerNetworkInfo(
playbackUrl: String?,
networkSpeed: Long,
isWifi: Boolean,
batteryPercent: Int,
isCharging: Boolean,
modifier: Modifier = Modifier,
) {
val localNetworkSpeed by rememberUpdatedState(networkSpeed)
val localIsWifi by rememberUpdatedState(isWifi)
val localBatteryPercent by rememberUpdatedState(batteryPercent)
val localIsCharging by rememberUpdatedState(isCharging)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = modifier) {
if (playbackUrl != null) {
Row(verticalAlignment = Alignment.CenterVertically) {
LumaIconView(if (localIsWifi) LumaIcon.Wifi else LumaIcon.Signal, contentDescription = null, tint = Color.White, modifier = Modifier.size(16.dp))
Spacer(Modifier.width(4.dp))
Text(formatNetworkSpeed(localNetworkSpeed), color = Color.White, fontSize = 13.sp)
}
if (localBatteryPercent >= 0) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("$localBatteryPercent%", color = Color.White, fontSize = 13.sp)
Spacer(Modifier.width(4.dp))
BatteryIcon(level = localBatteryPercent, charging = localIsCharging, modifier = Modifier.size(22.dp, 12.dp))
}
}
if (localBatteryPercent >= 0) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("$localBatteryPercent%", color = Color.White, fontSize = 13.sp)
Spacer(Modifier.width(4.dp))
BatteryIcon(level = localBatteryPercent, charging = localIsCharging, modifier = Modifier.size(22.dp, 12.dp))
}
}
}