收敛降级玻璃与页面状态恢复

This commit is contained in:
BoatingCloud committed 2026-08-28 09:46:02 +08:00
1 parent e76637c562
commit 485538f296
8 files changed
+56 -20

No files matched your search

@@ -77,7 +77,9 @@ fun AppNavHost(
if (currentEntry?.destination?.route == AppRoute.MediaDetail.pattern &&
currentEntry.arguments?.getString("itemId") == itemId
) return@let
navController.navigate(AppRoute.MediaDetail.create(serverId, itemId))
navController.navigate(AppRoute.MediaDetail.create(serverId, itemId)) {
launchSingleTop = true
}
}
}
@@ -180,7 +182,9 @@ fun AppNavHost(
val alreadyPlaying = currentEntry?.destination?.route == AppRoute.VideoPlayer.pattern &&
currentEntry.arguments?.getString("itemId") == itemId
if (!alreadyPlaying) {
navController.navigate(AppRoute.VideoPlayer.create(serverId, itemId, seriesId, seasonId))
navController.navigate(AppRoute.VideoPlayer.create(serverId, itemId, seriesId, seasonId)) {
launchSingleTop = true
}
}
},
)
@@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.LocalTextStyle
@@ -584,6 +585,7 @@ fun HomeScreen(
val heroItems = remember(latestItems) { latestItems.take(8) }
val heroPagerState = rememberPagerState(pageCount = { heroItems.size })
val homeListState = rememberLazyListState()
val activeHeroItem = heroItems.getOrNull(heroPagerState.currentPage)
val immersiveBackground = rememberPosterImmersiveBackground(heroImageUrl(activeHeroItem, activeServer?.fullUrl.orEmpty()))
@@ -690,7 +692,8 @@ fun HomeScreen(
} else {
val server = activeServer
LazyColumn(
Modifier.fillMaxSize(),
state = homeListState,
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = bottomBarContentPadding(90.dp).calculateBottomPadding())
) {
if (heroItems.isNotEmpty()) {
@@ -80,6 +80,7 @@ import com.example.myapplication.data.EmbyServer
import com.example.myapplication.R as Res
import com.kyant.backdrop.Backdrop
import top.yukonga.miuix.kmp.basic.Text
import kotlinx.coroutines.CancellationException
import java.util.Locale
private val LocalDetailText = staticCompositionLocalOf { Color.White }
@@ -117,22 +118,32 @@ fun MediaDetailScreen(
LaunchedEffect(item.id, server?.id, server?.apiKey) {
if (server == null || server.apiKey.isBlank()) return@LaunchedEffect
refreshing = true
val fullItem = EmbyRepository.getItem(server, item.id)
if (fullItem == null) {
loadError = detailLoadFailed
} else {
detail = fullItem
loadError = null
}
refreshing = false
try {
val fullItem = EmbyRepository.getItem(server, item.id)
if (fullItem == null) {
loadError = detailLoadFailed
} else {
detail = fullItem
loadError = null
}
if (detail.type == "Series") {
val loadedSeasons = EmbyRepository.getSeasons(server, item.id)
seasons = loadedSeasons
selectedSeasonId = loadedSeasons.firstOrNull()?.id
} else {
seasons = emptyList()
selectedSeasonId = null
if (detail.type == "Series") {
val loadedSeasons = EmbyRepository.getSeasons(server, item.id)
seasons = loadedSeasons
selectedSeasonId = loadedSeasons
.firstOrNull { it.id == selectedSeasonId }
?.id
?: loadedSeasons.firstOrNull()?.id
} else {
seasons = emptyList()
selectedSeasonId = null
}
} catch (error: CancellationException) {
throw error
} catch (_: Exception) {
loadError = detailLoadFailed
} finally {
refreshing = false
}
}
@@ -141,7 +152,13 @@ fun MediaDetailScreen(
val seriesId = item.id
val seasonId = selectedSeasonId ?: return@LaunchedEffect
if (detail.type != "Series" || seasonId.isBlank()) return@LaunchedEffect
episodes = EmbyRepository.getEpisodes(currentServer, seriesId, seasonId)
episodes = try {
EmbyRepository.getEpisodes(currentServer, seriesId, seasonId)
} catch (error: CancellationException) {
throw error
} catch (_: Exception) {
emptyList()
}
}
val seriesFallbackImageUrl = EmbyRepository.buildImageUrl(server?.fullUrl.orEmpty(), detail, maxWidth = 640)
@@ -224,8 +224,11 @@ fun Modifier.lumaFallbackGlass(
endY = size.height * 0.72f,
)
onDrawWithContent {
drawContent()
// The fallback surface is a material layer. Its highlight must
// sit behind the content; drawing it after drawContent makes
// labels and icons look washed out on devices without Backdrop.
drawRect(highlight)
drawContent()
}
}
.then(if (border) Modifier.border(0.6.dp, edge, shape) else Modifier)
+6
View File
@@ -3,3 +3,9 @@
文件变更: MainActivity.kt、AppTheme.kt、LumaDesignSystem.kt、LiquidBottomTabs.kt、AppBottomBar.kt、SettingsScreen.kt、strings.xml、values-en/strings.xml 及既有 UI 收尾文件。
教训: 远端有新提交时先暂存跟踪文件再合并;模式手动选择与模拟器安全硬门需同时保留。
标签: #lumaplayer #fallback-ui #glass-mode #renderthread #remote-merge
[PROJECT:lumaplayer] 页面状态与导航收尾
结论: 修复降级高光覆盖、首页滚动恢复及详情/播放器重复导航与异常态,构建和 API35 启动回归成功。
文件变更: ui/design/LumaDesignSystem.kt、screens/HomeScreen.kt、navigation/AppNavHost.kt、screens/MediaDetailScreen.kt。
教训: 页面请求异常需保留取消异常;可保存滚动状态和 launchSingleTop 能减少返回闪烁与重复页面。
标签: #lumaplayer #navigation #state-restoration #fallback-ui
+1
View File
@@ -10,3 +10,4 @@
- 详细状态见 [projects.md](projects.md),踩坑见 [lessons.md](lessons.md)。
- 最新收尾:公共分段控件、按钮、输入框、主题/语言/资源库弹层统一走同一套降级毛玻璃;底栏降级选中胶囊改为内容下层绘制;新增玻璃模式持久化设置;状态栏/导航栏图标随深色主题同步;Debug/Lint/Unit 与 API35 模拟器回归通过。
- 最新改动:播放器进度条改为自绘可访问 SeekBar,媒体首页、Vision 收藏和搜索加载态改为统一骨架占位;移除未使用的 Material 图标依赖,完整 Debug/Lint/Unit 通过。
- 最新校验:修复降级玻璃高光覆盖内容、首页滚动状态恢复与重复详情/播放器导航;Debug/Lint/Unit 和 API35 模拟器启动回归成功。
+1
View File
@@ -19,3 +19,4 @@
- 中:Emby 播放回报必须沿用 PlaybackInfo 返回的 `PlaySessionId`/`MediaSourceId`,并将开始、进度、停止事件串行化;把回报协程绑定到播放器 Composable 生命周期会在返回页面时丢失停止事件。
- 高:液态玻璃不能以“API 版本高”作为唯一启用条件;Kyant Backdrop 还需通过设备安全门和运行帧率门控,低帧率/不安全 GPU 必须在创建 LayerBackdrop 前降级,避免 RenderThread 崩溃。
- 高:玻璃模式已改为用户手动选择,不能再用 Choreographer 帧率变化自动切换;但模拟器/低版本仍必须在创建 Kyant LayerBackdrop 前保留硬件安全门,避免 RenderThread SIGSEGV。
- 中:降级玻璃的边缘高光应在 `drawContent()` 之前绘制;否则公共玻璃容器中的文字和图标会被高光层洗白。
+1
View File
@@ -37,3 +37,4 @@
- 2026-08-27:播放器进度条替换为自绘拖拽控件并补充无障碍进度调节,Home/Vision/Search 的媒体加载态改为统一骨架占位;移除未使用的 Material 图标依赖。`assembleDebug`、`lintDebug`、`testDebugUnitTest` 成功。
- 2026-08-27:底栏图标调整为 36dp、搜索图标 41dp,资源库无图标服务器使用正确的服务器图标;搜索清除与图标库返回补足 48dp 触控范围。完整 Debug/Lint/Unit 构建和 API35 模拟器回归通过。
- 2026-08-28:完整读取本地 `luna/README.txt`、`AI_PROMPT.txt` 与降级参考 contact sheet;修复降级底栏选中胶囊覆盖内容的问题,统一为稳定渐变毛玻璃;移除 Choreographer 帧率自动切换,设置页新增液态玻璃/毛玻璃手动选择并持久化;合并远端 3 个播放器提交后 `assembleDebug`、`lintDebug` 与 API35 模拟器启动回归通过。
- 2026-08-28:继续收尾降级 UI;修复 fallback 高光绘制顺序,首页接入可保存滚动状态,详情/播放器导航启用 `launchSingleTop` 防重复 Push,详情刷新和剧集加载异常进入已有错误态;`assembleDebug`、`lintDebug`、`testDebugUnitTest` 及 API35 模拟器启动回归成功。