收敛玻璃边缘并清理图标库旧界面

This commit is contained in:
BoatingCloud committed 2026-08-28 11:30:24 +08:00
1 parent 594f47bf3b
commit 8f5923556d
7 files changed
+65 -217

No files matched your search

@@ -166,7 +166,15 @@ 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() }, cornerRadius = 20.dp, padding = PaddingValues(0.dp)) {
GlassSurface(
modifier = Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
role = Role.Button,
) { 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()) {
@@ -204,7 +212,11 @@ private fun ServerCard(server: EmbyServer, tint: Color, onClick: () -> Unit, onC
Modifier
.size(48.dp)
.onGloballyPositioned(onMorePositioned)
.clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { onMoreClick() },
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
role = Role.Button,
) { onMoreClick() },
contentAlignment = Alignment.Center,
) {
LumaIconView(LumaIcon.More, contentDescription = stringResource(Res.string.server_actions), tint = GlassTokens.subtext, modifier = Modifier.size(24.dp))
@@ -222,7 +234,15 @@ 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() }, cornerRadius = 20.dp, padding = PaddingValues(0.dp)) {
GlassSurface(
modifier = Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
role = Role.Button,
) { 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()) {
@@ -265,7 +285,11 @@ private fun ServerGridCard(server: EmbyServer, tint: Color, onClick: () -> Unit,
Modifier
.size(48.dp)
.onGloballyPositioned(onMorePositioned)
.clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { onMoreClick() },
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
role = Role.Button,
) { onMoreClick() },
contentAlignment = Alignment.Center,
) {
LumaIconView(LumaIcon.More, contentDescription = stringResource(Res.string.server_actions), tint = GlassTokens.subtext, modifier = Modifier.size(24.dp))
@@ -518,7 +542,11 @@ internal fun IconLibraryManagerScreenContent(
Box(Modifier.width(86.dp)) {
Box(
Modifier.size(48.dp)
.clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { onBack() },
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
role = Role.Button,
) { onBack() },
contentAlignment = Alignment.Center,
) {
Box(
@@ -538,7 +566,11 @@ internal fun IconLibraryManagerScreenContent(
Modifier
.heightIn(min = 48.dp)
.lumaGlass(radius = 20.dp, level = com.example.myapplication.ui.design.LumaGlassLevel.Interactive)
.clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { showAddDialog = true }
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
role = Role.Button,
) { showAddDialog = true }
.padding(horizontal = 10.dp, vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(3.dp),
@@ -631,7 +663,12 @@ private fun IconLibraryManagerCard(library: IconLibrary, onClick: () -> Unit) {
Box(
Modifier.fillMaxWidth()
.lumaGlass(radius = LumaShapes.large, level = com.example.myapplication.ui.design.LumaGlassLevel.Regular)
.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = onClick)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
role = Role.Button,
onClick = onClick,
)
.padding(14.dp),
) {
Column {
@@ -708,204 +745,6 @@ private fun IconLibraryManagerCard(library: IconLibrary, onClick: () -> Unit) {
}
}
@Composable
private fun IconLibrarySection(
libraries: List<IconLibrary>,
isGrid: Boolean,
onClick: (IconLibrary) -> Unit,
onManage: (IconLibrary) -> Unit,
) {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(stringResource(Res.string.icon_lib_section_title), fontSize = 15.sp, fontWeight = FontWeight.Bold, color = GlassTokens.text)
Text(stringResource(Res.string.icon_count_unit, libraries.size), fontSize = 13.sp, color = GlassTokens.subtext)
}
if (isGrid) {
// Grid layout for icon libraries — use Column+Row to avoid nested scrolling
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
libraries.chunked(2).forEach { row ->
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
row.forEach { lib ->
IconLibraryCard(
library = lib,
modifier = Modifier.weight(1f),
onClick = { onClick(lib) },
onLongClick = { onManage(lib) },
)
}
// Fill remaining space if odd number
if (row.size < 2) {
Spacer(Modifier.weight(1f))
}
}
}
}
} else {
// List layout for icon libraries — use Row to avoid nested scrolling
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
libraries.forEach { lib ->
IconLibraryCard(
library = lib,
modifier = Modifier.width(140.dp),
onClick = { onClick(lib) },
onLongClick = { onManage(lib) },
)
}
}
}
}
}
@Composable
private fun IconLibraryCard(
library: IconLibrary,
modifier: Modifier = Modifier,
onClick: () -> Unit,
onLongClick: () -> Unit,
) {
Box(
modifier = modifier
.lumaGlass(radius = 18.dp, level = LumaGlassLevel.Regular)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onClick,
)
.padding(12.dp),
) {
Column {
// Icon preview row
Row(
horizontalArrangement = Arrangement.spacedBy((-6).dp),
verticalAlignment = Alignment.CenterVertically,
) {
library.icons.take(3).forEach { icon ->
Box(
Modifier.size(30.dp).clip(RoundedCornerShape(8.dp))
.lumaGlass(radius = 8.dp, level = LumaGlassLevel.Clear, shadowElevation = 0.dp, tint = Color(0xFFFF8A65).copy(alpha = 0.18f)),
contentAlignment = Alignment.Center,
) {
LumaAsyncImage(
model = ImageRequest.Builder(LocalContext.current).data(icon.url).crossfade(true).build(),
contentDescription = icon.name,
contentScale = ContentScale.Crop,
modifier = Modifier.size(24.dp).clip(RoundedCornerShape(6.dp)),
)
}
}
}
Spacer(Modifier.height(8.dp))
Text(library.name, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = GlassTokens.text, maxLines = 1, overflow = TextOverflow.Ellipsis)
Text(stringResource(Res.string.icon_count_suffix, library.iconCount), fontSize = 11.sp, color = GlassTokens.subtext)
Spacer(Modifier.height(6.dp))
// Manage button
Box(
modifier = Modifier.fillMaxWidth()
.lumaGlass(radius = 10.dp, level = LumaGlassLevel.Interactive, tint = LumaColors.accentSoft, borderColor = GlassTokens.accent.copy(alpha = 0.2f))
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onLongClick,
)
.padding(vertical = 6.dp),
contentAlignment = Alignment.Center,
) {
Text(stringResource(Res.string.icon_lib_manage), fontSize = 12.sp, fontWeight = FontWeight.Medium, color = GlassTokens.accent)
}
}
}
}
/* ------------------------------------------------------------------ */
/* Icon Library Detail Dialog — browse icons */
/* ------------------------------------------------------------------ */
@Composable
private fun IconLibraryDetailDialog(library: IconLibrary, backdrop: Backdrop? = null, onDismiss: () -> Unit) {
GlassDialog(backdrop = backdrop, onDismissRequest = onDismiss, modifier = Modifier.width(340.dp)) {
Box(Modifier.fillMaxWidth()) {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
// Header
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
Modifier.size(36.dp).clip(RoundedCornerShape(10.dp))
.background(Brush.linearGradient(listOf(Color(0xFFFF6B9D), Color(0xFFFF8A65)))),
contentAlignment = Alignment.Center,
) {
LumaIconView(LumaIcon.Library, contentDescription = null, tint = Color.White, modifier = Modifier.size(20.dp))
}
Spacer(Modifier.width(12.dp))
Column(Modifier.weight(1f)) {
Text(library.name, fontWeight = FontWeight.Bold, fontSize = 18.sp, color = GlassTokens.text)
Text(stringResource(Res.string.icon_count_suffix, library.iconCount), fontSize = 12.sp, color = GlassTokens.subtext)
}
}
if (library.iconCount > 0 && library.icons.isEmpty()) {
Box(
modifier = Modifier.fillMaxWidth()
.lumaGlass(radius = 16.dp, level = LumaGlassLevel.Clear)
.padding(vertical = 36.dp),
contentAlignment = Alignment.Center,
) { Text(stringResource(Res.string.icon_lib_not_loaded), color = GlassTokens.subtext, fontSize = 14.sp) }
} else if (library.icons.isEmpty()) {
Box(
modifier = Modifier.fillMaxWidth()
.lumaGlass(radius = 16.dp, level = LumaGlassLevel.Clear)
.padding(vertical = 36.dp),
contentAlignment = Alignment.Center,
) { Text(stringResource(Res.string.icon_lib_no_icons), color = GlassTokens.subtext, fontSize = 14.sp) }
} else {
Box(
Modifier.fillMaxWidth()
.lumaGlass(radius = 16.dp, level = LumaGlassLevel.Clear)
.padding(8.dp)
) {
LazyVerticalGrid(
columns = GridCells.Fixed(5),
modifier = Modifier.heightIn(min = 120.dp, max = 400.dp),
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
items(library.icons) { icon ->
Box(
modifier = Modifier.aspectRatio(1f)
.lumaGlass(radius = 10.dp, level = LumaGlassLevel.Clear, shadowElevation = 0.dp)
.padding(4.dp),
contentAlignment = Alignment.Center,
) {
LumaAsyncImage(
model = ImageRequest.Builder(LocalContext.current).data(icon.url).crossfade(true).build(),
contentDescription = icon.name,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize().clip(RoundedCornerShape(8.dp)),
)
}
}
}
}
}
// Close button
Box(
modifier = Modifier.fillMaxWidth()
.lumaGlass(radius = 16.dp, level = LumaGlassLevel.Interactive)
.clickable { onDismiss() }
.padding(vertical = 14.dp),
contentAlignment = Alignment.Center,
) { Text(stringResource(Res.string.icon_lib_close), color = GlassTokens.subtext, fontSize = 15.sp, fontWeight = FontWeight.Medium) }
}
}
}
}
/* ------------------------------------------------------------------ */
/* Manage Icon Library Dialog — rename, refresh, delete */
/* ------------------------------------------------------------------ */
@@ -440,7 +440,7 @@ fun LiquidGlassPopupOverlay(
} else {
Color.White.copy(alpha = 0.20f)
}
val border = if (GlassTokens.isDark) Color.White.copy(alpha = 0.24f) else Color.White.copy(alpha = 0.62f)
val border = GlassTokens.glassBorder
Box(modifier.fillMaxSize().zIndex(20f)) {
Box(
@@ -476,7 +476,7 @@ fun LiquidGlassPopupOverlay(
depthEffect = true,
)
},
highlight = { Highlight.Plain },
highlight = { Highlight.Plain.copy(alpha = 0.58f) },
shadow = { Shadow(alpha = 0.18f) },
onDrawSurface = { drawRect(surface) },
)
@@ -129,7 +129,7 @@ object LumaColors {
val floatingTint: Color
get() = if (isDark) Color(0xFF1B2430).copy(alpha = 0.62f) else Color.White.copy(alpha = 0.30f)
val glassBorder: Color
get() = if (isDark) Color.White.copy(alpha = 0.16f) else Color(0xFFB7C3D0).copy(alpha = 0.42f)
get() = if (isDark) Color.White.copy(alpha = 0.16f) else Color(0xFFB7C3D0).copy(alpha = 0.30f)
val glassHighlight: Color
get() = if (isDark) Color.White.copy(alpha = 0.07f) else Color.White.copy(alpha = 0.24f)
val glassShadow: Color
@@ -203,10 +203,10 @@ fun Modifier.lumaFallbackGlass(
val edge = if (light) {
Color.White.copy(
alpha = when {
liquidFallback && selected -> 0.86f
liquidFallback -> 0.70f
selected -> 0.78f
else -> 0.64f
liquidFallback && selected -> 0.62f
liquidFallback -> 0.48f
selected -> 0.64f
else -> 0.52f
},
)
} else {
@@ -222,10 +222,10 @@ fun Modifier.lumaFallbackGlass(
val topHighlight = if (light) {
Color.White.copy(
alpha = when {
liquidFallback && selected -> 0.42f
liquidFallback -> 0.30f
selected -> 0.32f
else -> 0.22f
liquidFallback && selected -> 0.34f
liquidFallback -> 0.24f
selected -> 0.28f
else -> 0.18f
},
)
} else {
@@ -337,7 +337,7 @@ fun Modifier.lumaGlass(
)
}
},
highlight = if (highlight) ({ Highlight.Plain.copy(alpha = 0.75f) }) else null,
highlight = if (highlight) ({ Highlight.Plain.copy(alpha = 0.58f) }) else null,
shadow = { Shadow(alpha = spec.shadowAlpha) },
onDrawSurface = { drawRect(surfaceTint) },
)
+6
View File
@@ -21,3 +21,9 @@
文件变更: ui/design/LumaDesignSystem.kt、ui/design/LumaComponents.kt、ui/design/LumaIcons.kt、screens/SettingsScreen.kt、screens/SettingsChildScreens.kt、values/strings.xml、values-en/strings.xml。
教训: 全局材质状态必须进入 remember key;真实液态玻璃仍需在实体 GPU 上验证。
标签: #lumaplayer #glass-mode #design-system #accessibility
[PROJECT:lumaplayer] 玻璃边缘与资源库遗留 UI 清理
结论: 降低原生/降级玻璃的白边和高光强度,删除无引用的旧图标库展示实现,保留当前独立管理页;完整构建和资源库操作回归通过。
文件变更: ui/design/LumaDesignSystem.kt、ui/LiquidGlass.kt、screens/LibraryScreen.kt。
教训: 清理前以全仓库引用结果确认目标确实未被活动页面使用。
标签: #lumaplayer #liquid-glass #library #cleanup
+1
View File
@@ -12,3 +12,4 @@
- 最新改动:播放器进度条改为自绘可访问 SeekBar,媒体首页、Vision 收藏和搜索加载态改为统一骨架占位;移除未使用的 Material 图标依赖,完整 Debug/Lint/Unit 通过。
- 最新校验:修复降级玻璃高光覆盖内容、首页滚动状态恢复与重复详情/播放器导航;Debug/Lint/Unit 和 API35 模拟器启动回归成功。
- 最新校验:修复玻璃模式切换后的 Backdrop/source 与材质缓存刷新;设置页行组件、分组标题和子页返回栏统一;Debug/Lint/Unit 与 API35 模拟器液态↔毛玻璃切换回归成功。
- 最新收敛:降低统一玻璃边缘高光/白边,清理 `LibraryScreen` 无引用的旧图标库 UI;资源库列表/网格切换回归无崩溃。
+1
View File
@@ -22,3 +22,4 @@
- 中:降级玻璃的边缘高光应在 `drawContent()` 之前绘制;否则公共玻璃容器中的文字和图标会被高光层洗白。
- 高:用户明确要求真实液态玻璃时,不能继续保留设备/模拟器安全门;当前仅保留 API 31+ 能力判断,底栏原生 Backdrop 必须先绘制容器与选中胶囊,再绘制清晰图标/文字,避免内容被二次采样覆盖。该取舍需在实体机 GPU 上继续验证 RenderThread 稳定性。
- 中:玻璃模式是全局 Compose 状态;涉及 `rememberLayerBackdrop` 或玻璃规格的缓存必须把 `AppTheme.glassMode` 纳入重建边界,否则设置副标题会变但材质不变。
- 低:清理页面遗留实现前必须用全仓库引用检索确认;私有且无调用的旧 UI 可以删除,活动页面保留原数据/弹窗实现。
+1
View File
@@ -40,3 +40,4 @@
- 2026-08-28:继续收尾降级 UI;修复 fallback 高光绘制顺序,首页接入可保存滚动状态,详情/播放器导航启用 `launchSingleTop` 防重复 Push,详情刷新和剧集加载异常进入已有错误态;`assembleDebug`、`lintDebug`、`testDebugUnitTest` 及 API35 模拟器启动回归成功。
- 2026-08-28:按需求取消液态玻璃设备/模拟器安全门,API 31+ 液态模式直接进入 Kyant Backdrop 原生路径;底栏改为玻璃底层、选中胶囊、清晰内容的正确绘制顺序,主图标 40dp、搜索图标 44dp、文字 8sp;补充媒体卡片加载/成功/失败三态和统一按压反馈。`assembleDebug`、`lintDebug`、`testDebugUnitTest` 及 API35 模拟器液态/毛玻璃切换回归成功,未发现 RenderThread 崩溃。
- 2026-08-28:修复玻璃模式切换时 `LayerBackdrop` 与 `GlassSpec` 缓存未刷新问题;新增 `LumaSettingRow`、`LumaPageTopBar`、设置分组标题并补齐公共按钮语义。完整 Debug/Lint/Unit 成功,API35 模拟器切换液态→毛玻璃→液态成功且无 RenderThread 崩溃;参考素材仍未提交。
- 2026-08-28:收敛原生/降级玻璃边缘高光与白边,清理 `LibraryScreen` 中确认无引用的旧图标库 Section/Card/DetailDialog,并为资源库主要操作补充按钮语义;Debug/Lint/Unit 成功,模拟器资源库列表/网格切换无崩溃。