收敛玻璃降级并完善深色系统栏

This commit is contained in:
BoatingCloud committed 2026-08-27 22:45:50 +08:00
1 parent f24286d758
commit 3b322012cf
9 files changed
+69 -49

No files matched your search

@@ -1,5 +1,6 @@
package com.example.myapplication
import android.app.Activity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.fadeIn
@@ -15,6 +16,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
@@ -26,10 +28,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.core.view.WindowCompat
import com.example.myapplication.R as Res
import com.example.myapplication.data.EmbyRepository
import com.example.myapplication.data.EmbyServer
@@ -91,6 +95,15 @@ fun MainApp() {
if (!showStartScreen) {
MiuixTheme {
val view = LocalView.current
val isDark = AppTheme.isDark
SideEffect {
(view.context as? Activity)?.window?.let { window ->
val controller = WindowCompat.getInsetsController(window, view)
controller.isAppearanceLightStatusBars = !isDark
controller.isAppearanceLightNavigationBars = !isDark
}
}
val backStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = backStackEntry?.destination?.route
var lastRootTab by rememberSaveable { mutableIntStateOf(0) }
@@ -166,7 +166,7 @@ private fun ServerCard(server: EmbyServer, tint: Color, onClick: () -> Unit, onC
var clicked by remember { mutableStateOf(false) }
val pressBg by animateColorAsState(targetValue = if (clicked) LumaColors.glassHighlight.copy(alpha = 0.24f) else Color.Transparent, animationSpec = tween(durationMillis = if (clicked) 80 else 200), label = "pb")
LaunchedEffect(clicked) { if (clicked) { delay(150); clicked = false } }
GlassSurface(modifier = Modifier.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null) { clicked = true; onClick() }, fill = tint.copy(alpha = 0.50f), cornerRadius = 20.dp, padding = PaddingValues(0.dp)) {
GlassSurface(modifier = Modifier.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null) { clicked = true; onClick() }, cornerRadius = 20.dp, padding = PaddingValues(0.dp)) {
Row(modifier = Modifier.fillMaxWidth().background(pressBg).padding(14.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(1f)) {
if (server.iconUrl.isNotBlank()) {
@@ -222,7 +222,7 @@ private fun ServerGridCard(server: EmbyServer, tint: Color, onClick: () -> Unit,
var clicked by remember { mutableStateOf(false) }
val pressBg by animateColorAsState(targetValue = if (clicked) LumaColors.glassHighlight.copy(alpha = 0.24f) else Color.Transparent, animationSpec = tween(durationMillis = if (clicked) 80 else 200), label = "pb")
LaunchedEffect(clicked) { if (clicked) { delay(150); clicked = false } }
GlassSurface(modifier = Modifier.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null) { clicked = true; onClick() }, fill = tint.copy(alpha = 0.50f), cornerRadius = 20.dp, padding = PaddingValues(0.dp)) {
GlassSurface(modifier = Modifier.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null) { clicked = true; onClick() }, cornerRadius = 20.dp, padding = PaddingValues(0.dp)) {
Column(modifier = Modifier.fillMaxWidth().background(pressBg).padding(14.dp), horizontalAlignment = Alignment.Start) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (server.iconUrl.isNotBlank()) {
@@ -75,6 +75,7 @@ import com.example.myapplication.ui.design.LumaShapes
import com.example.myapplication.ui.design.LumaSpacing
import com.example.myapplication.ui.design.LumaTypography
import com.example.myapplication.ui.design.lumaGlass
import com.example.myapplication.ui.design.lumaFallbackGlass
import com.kyant.backdrop.Backdrop
import com.kyant.backdrop.backdrops.layerBackdrop
import com.kyant.backdrop.drawBackdrop
@@ -467,7 +468,7 @@ private fun LanguageMenuOverlay(
onDrawSurface = { drawRect(surface) },
)
} else {
Modifier.background(LumaColors.fallbackGlass)
Modifier.lumaFallbackGlass(shape, border = false)
},
)
.border(0.7.dp, border, shape)
@@ -505,6 +506,7 @@ private fun LanguageOption(
Row(
Modifier
.fillMaxWidth()
.heightIn(min = 48.dp)
.clip(RoundedCornerShape(18.dp))
.clickable(onClick = onClick)
.padding(horizontal = 10.dp, vertical = 8.dp),
@@ -98,6 +98,7 @@ import com.example.myapplication.ui.design.LumaShapes
import com.example.myapplication.ui.design.LumaTypography
import com.example.myapplication.ui.design.LumaGlassRuntime
import com.example.myapplication.ui.design.lumaGlass
import com.example.myapplication.ui.design.lumaFallbackGlass
internal data class NavTab(
val icon: LumaIcon,
@@ -209,9 +210,10 @@ fun SegmentedControl(
modifier: Modifier = Modifier,
) {
val backdrop = LocalGlassBackdrop.current
val containerShape = RoundedCornerShape(20.dp)
Row(
modifier
.clip(RoundedCornerShape(20.dp))
.clip(containerShape)
.then(
if (backdrop != null && LumaGlassRuntime.useNativeBackdrop) {
Modifier.drawBackdrop(
@@ -224,10 +226,10 @@ fun SegmentedControl(
onDrawSurface = { drawRect(GlassTokens.glassFillSoft) },
)
} else {
Modifier.background(GlassTokens.glassFillSoft)
Modifier.lumaFallbackGlass(containerShape, border = false)
}
)
.border(0.5.dp, GlassTokens.glassBorder, RoundedCornerShape(20.dp))
.border(0.5.dp, GlassTokens.glassBorder, containerShape)
.padding(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
@@ -276,7 +278,7 @@ fun SegmentedControl(
)
.then(
if (sel && (backdrop == null || !LumaGlassRuntime.useNativeBackdrop)) {
Modifier.background(selectedFill)
Modifier.lumaFallbackGlass(RoundedCornerShape(16.dp), selected = true, border = false)
} else {
Modifier
}
@@ -472,7 +474,7 @@ fun LiquidGlassPopupOverlay(
onDrawSurface = { drawRect(surface) },
)
} else {
Modifier.background(LumaColors.fallbackGlass)
Modifier.lumaFallbackGlass(shape, border = false)
}
)
.border(0.7.dp, border, shape)
@@ -537,23 +539,13 @@ fun LiquidGlassButton(
scaleY = scale
alpha = if (enabled) 1f else 0.52f
}
.clip(shape)
.then(
if (backdrop != null && LumaGlassRuntime.useNativeBackdrop) {
Modifier.drawBackdrop(
backdrop = backdrop,
shape = { RoundedRectangle(cornerRadius) },
effects = {
vibrancy()
blur(8.dp.toPx())
},
onDrawSurface = { drawRect(fill) },
)
} else {
Modifier.background(fill)
}
.lumaGlass(
backdrop = backdrop,
radius = cornerRadius,
level = LumaGlassLevel.Interactive,
tint = fill,
borderColor = GlassTokens.glassBorder,
)
.border(0.5.dp, GlassTokens.glassBorder, shape)
.clickable(enabled = enabled, indication = null, interactionSource = interactionSource, onClick = onClick)
.padding(horizontal = 16.dp, vertical = 11.dp),
verticalAlignment = Alignment.CenterVertically,
@@ -716,20 +708,15 @@ fun GlassInputField(
Text(label, fontSize = 13.sp, color = GlassTokens.subtext, fontWeight = FontWeight.Medium)
val shape = RoundedCornerShape(12.dp)
Box(
Modifier.fillMaxWidth().clip(shape)
.then(
if (backdrop != null && LumaGlassRuntime.useNativeBackdrop) {
Modifier.drawBackdrop(
backdrop = backdrop,
shape = { RoundedRectangle(12.dp) },
effects = { vibrancy(); blur(8.dp.toPx()) },
onDrawSurface = { drawRect(GlassTokens.glassFillSoft) },
)
} else {
Modifier.background(GlassTokens.glassFillSoft)
}
Modifier.fillMaxWidth()
.lumaGlass(
backdrop = backdrop,
radius = 12.dp,
level = LumaGlassLevel.Clear,
tint = GlassTokens.glassFillSoft,
border = false,
)
.border(1.dp, if (isError) GlassTokens.danger else GlassTokens.glassBorder, shape)
.border(0.7.dp, if (isError) GlassTokens.danger else GlassTokens.glassBorder, shape)
.padding(horizontal = 14.dp, vertical = 12.dp),
) {
BasicTextField(
@@ -106,12 +106,18 @@ fun LumaSectionTitle(
) {
Text(title, color = LumaColors.text, fontSize = LumaTypography.title, fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)
if (action != null) {
Text(
action,
color = LumaColors.accent,
fontSize = LumaTypography.secondary,
modifier = Modifier.clickable(enabled = onAction != null, indication = null, interactionSource = remember { MutableInteractionSource() }) { onAction?.invoke() },
)
Box(
Modifier
.heightIn(min = 48.dp)
.clickable(
enabled = onAction != null,
indication = null,
interactionSource = remember { MutableInteractionSource() },
) { onAction?.invoke() },
contentAlignment = Alignment.Center,
) {
Text(action, color = LumaColors.accent, fontSize = LumaTypography.secondary)
}
}
}
}
@@ -201,15 +201,13 @@ object LumaColors {
val glassShadow: Color
get() = if (isDark) Color.Black.copy(alpha = 0.32f) else Color(0xFF344054).copy(alpha = 0.10f)
/** Stable tint used when native backdrop sampling is disabled on an emulator or old API. */
val fallbackGlass: Color
get() = if (isDark) Color(0xFF1A2330).copy(alpha = 0.86f) else Color(0xFFE4E9F0).copy(alpha = 0.86f)
}
/** Stable frosted fallback for devices where Kyant Backdrop is disabled. */
fun Modifier.lumaFallbackGlass(
shape: Shape,
selected: Boolean = false,
border: Boolean = true,
): Modifier {
val light = !LumaColors.isDark
val top = if (selected) {
@@ -283,7 +281,7 @@ fun Modifier.lumaFallbackGlass(
Modifier
},
)
.border(0.6.dp, edge, shape)
.then(if (border) Modifier.border(0.6.dp, edge, shape) else Modifier)
.clip(shape)
}
@@ -331,7 +329,6 @@ fun Modifier.lumaGlass(
val surfaceTint = tint ?: spec.tint
val usesDefaultGlassTint = tint == null || tint == LumaColors.clearTint ||
tint == LumaColors.regularTint || tint == LumaColors.strongTint || tint == LumaColors.floatingTint
val fallbackSurface = if (usesDefaultGlassTint) LumaColors.fallbackGlass else surfaceTint
val edgeColor = borderColor ?: LumaColors.glassBorder
val safeBackdrop = backdrop.takeIf { LumaGlassRuntime.useNativeBackdrop }
return this
@@ -355,7 +352,14 @@ fun Modifier.lumaGlass(
onDrawSurface = { drawRect(surfaceTint) },
)
} else {
Modifier.background(fallbackSurface)
if (usesDefaultGlassTint) {
Modifier.lumaFallbackGlass(shape, border = false)
} else {
Modifier.background(
surfaceTint.copy(alpha = surfaceTint.alpha.coerceAtMost(0.34f)),
shape,
)
}
},
)
.then(if (border) Modifier.border(0.6.dp, edgeColor, shape) else Modifier)
+6
View File
@@ -118,3 +118,9 @@
文件变更: ui/AppBottomBar.kt、ui/design/LumaDesignSystem.kt、ui/liquidglass/LiquidBottomTabs.kt、navigation/AppNavHost.kt、Aurora 图标资源、memory/*。
教训: 模拟器仍不能安全启用 Kyant RenderEffect 采样,降级路径必须在最终尺寸上裁剪,原生路径只能在通过设备与帧率门控后启用。
标签: #lumaplayer #bottom-bar #aurora #liquid-glass #renderthread #build
[PROJECT:lumaplayer] 公共玻璃降级与深色系统栏收尾
结论: 分段控件、按钮、输入框、资源库菜单、主题菜单和语言菜单统一复用渐变毛玻璃降级,深色主题系统栏图标同步;Debug/Lint/Unit 构建及 API35 模拟器回归通过。
文件变更: MainApp.kt、screens/LibraryScreen.kt、screens/SettingsScreen.kt、ui/LiquidGlass.kt、ui/design/LumaComponents.kt、ui/design/LumaDesignSystem.kt、memory/*。
教训: 模拟器继续禁用 Kyant RenderEffect 采样,截图中的玻璃为安全降级态;真实 Backdrop 仅在设备安全门和帧率门控同时通过时启用。
标签: #lumaplayer #glass #fallback #dark-mode #verification
+1
View File
@@ -8,3 +8,4 @@
- 当前 UI:新增 `ui/design` 令牌、公共加载/空状态/按钮、Aurora 图标子集和授权说明;主要页面已接入统一颜色、间距、圆角和动效。
- 构建验证:Backdrop 层级收敛、模拟器安全降级、播放器采样隔离和 GlassSheet 收尾后 `assembleDebug`、`lintDebug`、`testDebugUnitTest` 成功;已在 `sdk_gphone64_arm64` API 35 模拟器安装启动并点击 Vision/Settings/语言菜单,未出现 `SIGSEGV/RenderThread`,尚未做真机录屏比对。
- 详细状态见 [projects.md](projects.md),踩坑见 [lessons.md](lessons.md)。
- 最新收尾:公共分段控件、按钮、输入框、主题/语言/资源库弹层统一走同一套降级毛玻璃;状态栏/导航栏图标随深色主题同步;Debug/Lint/Unit 与 API35 模拟器回归通过。
+1
View File
@@ -33,3 +33,4 @@
- 2026-08-27:播放器按 Emby Playback Check-in 约定补齐 `PlaySessionId`、`MediaSourceId`、`PositionTicks`、`CanSeek`、`QueueableMediaTypes` 和事件字段;播放开始/暂停/继续/拖动/播放中进度/停止统一进入串行队列,普通进度按 10 秒或位置变化阈值上报。
- 2026-08-27:首页增加按服务器身份隔离的进程内内容缓存,普通页面返回不重复请求,显式重试才刷新;玻璃运行时增加 Choreographer 帧率门控,底栏高帧率安全设备走本地 AndroidLiquidGlass-kmp 的 Lens,模拟器/低帧率使用连续胶囊毛玻璃和选中高光;`assembleDebug`、`lintDebug`、模拟器安装启动复核成功。
- 2026-08-27:底栏普通图标调整为 33dp、搜索图标 37dp;降级玻璃增加最终 Capsule 裁剪并减弱蓝色选中填充,原生路径空闲态保留轻微 Lens;详情/播放器/剧集加载和播放地址失败时回落到错误态;最终 APK 在 API 35 模拟器启动、Vision/Settings/语言菜单点击复核成功。
- 2026-08-27:收敛公共分段控件、弹出菜单、按钮、输入框及语言菜单的降级玻璃路径,移除纯色方框;补充深色主题系统栏图标同步和操作标题 48dp 点击区域。`assembleDebug`、`lintDebug`、`testDebugUnitTest` 成功,API 35 模拟器验证设置/语言/主题/关于/服务器添加链路无崩溃;本地参考素材仍未跟踪。