docs+nav: sync AGENTS.md 导航过时内容 + guardedPop 围栏方案
- AGENTS.md: AnimatedContent → when(pane) 原子切换 - AGENTS.md: 新增 Compact guardedPop 机制说明 + 已知连点空页面 Bug - RainyTokenNavHost.kt: Compact 用 guardedPop(150ms) + popExitTransition=None - Expanded 局部 NavHost 同步加 popExitTransition=None(预防性)
This commit is contained in:
parent
ea9f6f7e72
commit
2ab5f2f342
13
AGENTS.md
13
AGENTS.md
@ -68,12 +68,21 @@ Compact(手机):
|
|||||||
→ CCGO: CCGO_USAGE_DETAIL(图表) → CCGO_USAGE_OVERVIEW(总统计)
|
→ CCGO: CCGO_USAGE_DETAIL(图表) → CCGO_USAGE_OVERVIEW(总统计)
|
||||||
↘ CCGO_USAGE_DATA(原始数据)
|
↘ CCGO_USAGE_DATA(原始数据)
|
||||||
|
|
||||||
|
返回用 guardedPop()(150ms 时间戳围栏)+ popExitTransition=None。
|
||||||
|
围栏防同一帧/连续帧的第二次 pop,popExitTransition 消旧 composable 残留窗口。
|
||||||
|
其余 transition 保留默认动画(enter/exit/popEnter)。
|
||||||
|
|
||||||
|
⚠️ 已知未修复 Bug:快速连点左上角返回 → 仍然会返回到空页面。
|
||||||
|
根因尚未定位(已排除:动画残留、回调竞态、目标路由 no-op)。
|
||||||
|
待查方向:UsageDetailScreen 内部返回链路 + NavController backQueue 状态。
|
||||||
|
|
||||||
Expanded(平板,≥840dp):
|
Expanded(平板,≥840dp):
|
||||||
┌─ 左侧 35%: Dashboard(固定) ─┐ ┌─ 右侧 65%: AnimatedContent ─────────┐
|
┌─ 左侧 35%: Dashboard(固定) ─┐ ┌─ 右侧 65%: when(pane) 原子切换 ─────┐
|
||||||
│ │ │ ServiceDetail / OCGOUsage / CCGOUsage │
|
│ │ │ ServiceDetail / OCGOUsage / CCGOUsage │
|
||||||
│ │ │ Settings(内嵌 NavHost) │
|
│ │ │ Settings(内嵌 NavHost) │
|
||||||
└────────────────────────────────┘ └────────────────────────────────────────┘
|
└────────────────────────────────┘ └────────────────────────────────────────┘
|
||||||
右侧用量详情支持内部子路由:图表 → 总览 / 原始数据(通过 UsageSubRoute 状态)
|
右侧用量详情内部子路由:图表 → 总览 / 原始数据(OCGO/CCGO/Settings 各自用局部 NavHost)
|
||||||
|
面板切换用 when(pane) 分支(同一帧原子重组,零穿透),子路由由局部 NavHost 的 popBackStack() 内置防护。
|
||||||
```
|
```
|
||||||
|
|
||||||
**桌面小组件(Widget)**:
|
**桌面小组件(Widget)**:
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
package com.rainy.token.ui
|
package com.rainy.token.ui
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.ExitTransition
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.animation.togetherWith
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -51,10 +49,10 @@ import com.rainy.token.ui.webview.WebViewLoginScreen
|
|||||||
* 应用导航图。
|
* 应用导航图。
|
||||||
*
|
*
|
||||||
* 自适应策略:
|
* 自适应策略:
|
||||||
* - **Compact**(< 600dp,手机):单栈 NavHost,与原来完全一致。
|
* - **Compact**(< 600dp,手机):单栈 NavHost。guardedPop 时间戳围栏(150ms)防连点栈损坏,
|
||||||
* - **Expanded**(≥ 840dp,平板横屏):左侧 Dashboard 面板 + 右侧详情双窗格。
|
* popExitTransition=None 消除旧 composable 残留,其余 transition 保留默认动画。
|
||||||
* - 右侧详情通过 AnimatedContent 切换,支持用量详情内的子路由(图表/总览/数据)和设置子路由。
|
* - **Expanded**(≥ 840dp,平板横屏):左侧 Dashboard + 右侧 when 分支原子切换,
|
||||||
* - 设置 → 凭据编辑 → WebView 登录 在右侧内嵌 NavHost 中处理。
|
* 用量子路由用局部 NavHost。
|
||||||
*/
|
*/
|
||||||
object Routes {
|
object Routes {
|
||||||
const val DASHBOARD = "dashboard"
|
const val DASHBOARD = "dashboard"
|
||||||
@ -78,10 +76,6 @@ private fun parseServiceType(typeName: String?): ServiceType =
|
|||||||
ServiceType.fromStorageKey(it) ?: runCatching { ServiceType.valueOf(it) }.getOrNull()
|
ServiceType.fromStorageKey(it) ?: runCatching { ServiceType.valueOf(it) }.getOrNull()
|
||||||
} ?: ServiceType.DEEPSEEK
|
} ?: ServiceType.DEEPSEEK
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════
|
|
||||||
// 双窗格路由状态
|
|
||||||
// ═══════════════════════════════════════════════
|
|
||||||
|
|
||||||
private sealed class DetailPane {
|
private sealed class DetailPane {
|
||||||
object Empty : DetailPane()
|
object Empty : DetailPane()
|
||||||
data class ServiceDetail(val type: ServiceType) : DetailPane()
|
data class ServiceDetail(val type: ServiceType) : DetailPane()
|
||||||
@ -90,10 +84,6 @@ private sealed class DetailPane {
|
|||||||
object Settings : DetailPane()
|
object Settings : DetailPane()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// 入口:根据 WindowSizeClass 分派
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RainyTokenNavHost() {
|
fun RainyTokenNavHost() {
|
||||||
val windowSize = rememberWindowSizeClass()
|
val windowSize = rememberWindowSizeClass()
|
||||||
@ -107,14 +97,27 @@ fun RainyTokenNavHost() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ═══════════════════════════════════════════════
|
||||||
// Compact:单栈 NavHost(原逻辑,一字未改)
|
// Compact:单栈 NavHost —— guardedPop 围栏 + popExitTransition=None
|
||||||
// ---------------------------------------------------------------------------
|
// ═══════════════════════════════════════════════
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CompactNavHost() {
|
private fun CompactNavHost() {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
NavHost(navController = navController, startDestination = Routes.DASHBOARD) {
|
// 时间戳围栏:150ms 内只允许一次 pop,防止连点时旧 composable 残留接收触摸
|
||||||
|
var lastPopTime by remember { mutableStateOf(0L) }
|
||||||
|
val guardedPop: () -> Unit = {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (now - lastPopTime >= 150) {
|
||||||
|
lastPopTime = now
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NavHost(
|
||||||
|
navController = navController,
|
||||||
|
startDestination = Routes.DASHBOARD,
|
||||||
|
popExitTransition = { ExitTransition.None }
|
||||||
|
) {
|
||||||
composable(Routes.DASHBOARD) {
|
composable(Routes.DASHBOARD) {
|
||||||
DashboardScreen(
|
DashboardScreen(
|
||||||
onOpenSettings = { navController.navigate(Routes.SETTINGS) },
|
onOpenSettings = { navController.navigate(Routes.SETTINGS) },
|
||||||
@ -125,16 +128,20 @@ private fun CompactNavHost() {
|
|||||||
}
|
}
|
||||||
composable(Routes.USAGE_DETAIL) {
|
composable(Routes.USAGE_DETAIL) {
|
||||||
UsageDetailScreen(
|
UsageDetailScreen(
|
||||||
onBack = { navController.popBackStack(Routes.DASHBOARD, inclusive = false) },
|
onBack = guardedPop,
|
||||||
onOpenOverview = { navController.navigate(Routes.USAGE_OVERVIEW) },
|
onOpenOverview = { navController.navigate(Routes.USAGE_OVERVIEW) },
|
||||||
onOpenData = { navController.navigate(Routes.USAGE_DATA) }
|
onOpenData = { navController.navigate(Routes.USAGE_DATA) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(Routes.USAGE_OVERVIEW) {
|
composable(Routes.USAGE_OVERVIEW) {
|
||||||
UsageOverviewScreen(onBack = { navController.popBackStack() })
|
UsageOverviewScreen(
|
||||||
|
onBack = guardedPop
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composable(Routes.USAGE_DATA) {
|
composable(Routes.USAGE_DATA) {
|
||||||
UsageDataScreen(onBack = { navController.popBackStack() })
|
UsageDataScreen(
|
||||||
|
onBack = guardedPop
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composable(Routes.CCGO_USAGE_DETAIL) {
|
composable(Routes.CCGO_USAGE_DETAIL) {
|
||||||
val wid = CommandCodeUsageRepository.CCGO_WORKSPACE_ID
|
val wid = CommandCodeUsageRepository.CCGO_WORKSPACE_ID
|
||||||
@ -145,7 +152,7 @@ private fun CompactNavHost() {
|
|||||||
chartVm.setWorkspace(wid)
|
chartVm.setWorkspace(wid)
|
||||||
}
|
}
|
||||||
UsageDetailScreen(
|
UsageDetailScreen(
|
||||||
onBack = { navController.popBackStack(Routes.DASHBOARD, inclusive = false) },
|
onBack = guardedPop,
|
||||||
onOpenOverview = { navController.navigate(Routes.CCGO_USAGE_OVERVIEW) },
|
onOpenOverview = { navController.navigate(Routes.CCGO_USAGE_OVERVIEW) },
|
||||||
onOpenData = { navController.navigate(Routes.CCGO_USAGE_DATA) },
|
onOpenData = { navController.navigate(Routes.CCGO_USAGE_DATA) },
|
||||||
viewModel = chartVm,
|
viewModel = chartVm,
|
||||||
@ -156,17 +163,25 @@ private fun CompactNavHost() {
|
|||||||
val key = "ccgo_${CommandCodeUsageRepository.CCGO_WORKSPACE_ID}"
|
val key = "ccgo_${CommandCodeUsageRepository.CCGO_WORKSPACE_ID}"
|
||||||
val ovVm: UsageViewModel = hiltViewModel(key = key)
|
val ovVm: UsageViewModel = hiltViewModel(key = key)
|
||||||
LaunchedEffect(Unit) { ovVm.setWorkspace(CommandCodeUsageRepository.CCGO_WORKSPACE_ID) }
|
LaunchedEffect(Unit) { ovVm.setWorkspace(CommandCodeUsageRepository.CCGO_WORKSPACE_ID) }
|
||||||
UsageOverviewScreen(onBack = { navController.popBackStack() }, viewModel = ovVm, autoLoad = false)
|
UsageOverviewScreen(
|
||||||
|
onBack = guardedPop,
|
||||||
|
viewModel = ovVm,
|
||||||
|
autoLoad = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composable(Routes.CCGO_USAGE_DATA) {
|
composable(Routes.CCGO_USAGE_DATA) {
|
||||||
val key = "ccgo_${CommandCodeUsageRepository.CCGO_WORKSPACE_ID}"
|
val key = "ccgo_${CommandCodeUsageRepository.CCGO_WORKSPACE_ID}"
|
||||||
val dataVm: UsageDataViewModel = hiltViewModel(key = key)
|
val dataVm: UsageDataViewModel = hiltViewModel(key = key)
|
||||||
LaunchedEffect(Unit) { dataVm.setWorkspace(CommandCodeUsageRepository.CCGO_WORKSPACE_ID) }
|
LaunchedEffect(Unit) { dataVm.setWorkspace(CommandCodeUsageRepository.CCGO_WORKSPACE_ID) }
|
||||||
UsageDataScreen(onBack = { navController.popBackStack() }, viewModel = dataVm, autoLoad = false)
|
UsageDataScreen(
|
||||||
|
onBack = guardedPop,
|
||||||
|
viewModel = dataVm,
|
||||||
|
autoLoad = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composable(Routes.SETTINGS) {
|
composable(Routes.SETTINGS) {
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
onBack = { navController.popBackStack(Routes.DASHBOARD, inclusive = false) },
|
onBack = guardedPop,
|
||||||
onEditCredential = { type -> navController.navigate(Routes.credentialEdit(type)) }
|
onEditCredential = { type -> navController.navigate(Routes.credentialEdit(type)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -177,7 +192,7 @@ private fun CompactNavHost() {
|
|||||||
val type = parseServiceType(backStackEntry.arguments?.getString("type"))
|
val type = parseServiceType(backStackEntry.arguments?.getString("type"))
|
||||||
CredentialEditScreen(
|
CredentialEditScreen(
|
||||||
service = type,
|
service = type,
|
||||||
onBack = { navController.popBackStack() },
|
onBack = guardedPop,
|
||||||
onStartWebViewLogin = { service -> navController.navigate(Routes.webviewLogin(service)) },
|
onStartWebViewLogin = { service -> navController.navigate(Routes.webviewLogin(service)) },
|
||||||
onWebViewLoginSuccess = { }
|
onWebViewLoginSuccess = { }
|
||||||
)
|
)
|
||||||
@ -189,8 +204,8 @@ private fun CompactNavHost() {
|
|||||||
val type = parseServiceType(backStackEntry.arguments?.getString("type"))
|
val type = parseServiceType(backStackEntry.arguments?.getString("type"))
|
||||||
WebViewLoginScreen(
|
WebViewLoginScreen(
|
||||||
service = type,
|
service = type,
|
||||||
onBack = { navController.popBackStack() },
|
onBack = guardedPop,
|
||||||
onLoginSucceeded = { navController.popBackStack() }
|
onLoginSucceeded = { guardedPop() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(
|
composable(
|
||||||
@ -200,7 +215,7 @@ private fun CompactNavHost() {
|
|||||||
val type = parseServiceType(backStackEntry.arguments?.getString("type"))
|
val type = parseServiceType(backStackEntry.arguments?.getString("type"))
|
||||||
ServiceDetailScreen(
|
ServiceDetailScreen(
|
||||||
service = type,
|
service = type,
|
||||||
onBack = { navController.popBackStack(Routes.DASHBOARD, inclusive = false) },
|
onBack = guardedPop,
|
||||||
onConfigureCredential = { svc -> navController.navigate(Routes.credentialEdit(svc)) },
|
onConfigureCredential = { svc -> navController.navigate(Routes.credentialEdit(svc)) },
|
||||||
onStartWebViewLogin = { svc -> navController.navigate(Routes.webviewLogin(svc)) }
|
onStartWebViewLogin = { svc -> navController.navigate(Routes.webviewLogin(svc)) }
|
||||||
)
|
)
|
||||||
@ -208,16 +223,15 @@ private fun CompactNavHost() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ═══════════════════════════════════════════════
|
||||||
// Expanded:手动双窗格
|
// Expanded:手动双窗格
|
||||||
// ---------------------------------------------------------------------------
|
// ═══════════════════════════════════════════════
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExpandedLayout() {
|
private fun ExpandedLayout() {
|
||||||
var detailPane by remember { mutableStateOf<DetailPane>(DetailPane.Empty) }
|
var detailPane by remember { mutableStateOf<DetailPane>(DetailPane.Empty) }
|
||||||
|
|
||||||
Row(modifier = Modifier.fillMaxSize()) {
|
Row(modifier = Modifier.fillMaxSize()) {
|
||||||
// ── 左侧 Dashboard 面板(35%) ──
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(0.35f)
|
.weight(0.35f)
|
||||||
@ -231,48 +245,25 @@ private fun ExpandedLayout() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分隔线
|
|
||||||
VerticalDivider(
|
VerticalDivider(
|
||||||
thickness = 1.dp,
|
thickness = 1.dp,
|
||||||
color = MaterialTheme.colorScheme.outlineVariant
|
color = MaterialTheme.colorScheme.outlineVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── 右侧详情面板(65%) ──
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(0.65f)
|
.weight(0.65f)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
) {
|
) {
|
||||||
AnimatedContent(
|
|
||||||
targetState = detailPane,
|
|
||||||
transitionSpec = { fadeIn() togetherWith fadeOut() }
|
|
||||||
) { pane ->
|
|
||||||
ExpandedDetailPane(
|
ExpandedDetailPane(
|
||||||
pane = pane,
|
pane = detailPane,
|
||||||
onClose = { detailPane = DetailPane.Empty }
|
onClose = { detailPane = DetailPane.Empty }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Expanded 右侧面板路由
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用量详情内部的子路由状态(仅 Expanded 下使用)。
|
|
||||||
* 右侧面板内从 UsageDetail → Overview / Data 的切换通过此状态管理。
|
|
||||||
*/
|
|
||||||
private sealed class UsageSubRoute {
|
|
||||||
object Chart : UsageSubRoute()
|
|
||||||
object Overview : UsageSubRoute()
|
|
||||||
object Data : UsageSubRoute()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun backToEmpty(usageSub: UsageSubRoute?): Boolean =
|
|
||||||
usageSub != null && usageSub !is UsageSubRoute.Chart
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExpandedDetailPane(
|
private fun ExpandedDetailPane(
|
||||||
pane: DetailPane,
|
pane: DetailPane,
|
||||||
@ -291,32 +282,34 @@ private fun ExpandedDetailPane(
|
|||||||
ServiceDetailScreen(
|
ServiceDetailScreen(
|
||||||
service = pane.type,
|
service = pane.type,
|
||||||
onBack = onClose,
|
onBack = onClose,
|
||||||
onConfigureCredential = { svc ->
|
onConfigureCredential = { onClose() },
|
||||||
// 在右侧内部子路由跳转到 CredentialEdit
|
onStartWebViewLogin = { }
|
||||||
onClose() // 先清右侧再切 Settings(简化:直接切 Settings)
|
|
||||||
},
|
|
||||||
onStartWebViewLogin = { svc -> }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is DetailPane.OCGOUsage -> {
|
is DetailPane.OCGOUsage -> {
|
||||||
var subRoute by remember { mutableStateOf<UsageSubRoute>(UsageSubRoute.Chart) }
|
val navController = rememberNavController()
|
||||||
when (subRoute) {
|
NavHost(
|
||||||
is UsageSubRoute.Chart -> {
|
navController = navController,
|
||||||
|
startDestination = "chart",
|
||||||
|
popExitTransition = { ExitTransition.None }
|
||||||
|
) {
|
||||||
|
composable("chart") {
|
||||||
UsageDetailScreen(
|
UsageDetailScreen(
|
||||||
onBack = onClose,
|
onBack = onClose,
|
||||||
onOpenOverview = { subRoute = UsageSubRoute.Overview },
|
onOpenOverview = { navController.navigate("overview") },
|
||||||
onOpenData = { subRoute = UsageSubRoute.Data }
|
onOpenData = { navController.navigate("data") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is UsageSubRoute.Overview -> {
|
composable("overview") {
|
||||||
UsageOverviewScreen(onBack = { subRoute = UsageSubRoute.Chart }, autoLoad = true)
|
UsageOverviewScreen(onBack = { navController.popBackStack() }, autoLoad = true)
|
||||||
}
|
}
|
||||||
is UsageSubRoute.Data -> {
|
composable("data") {
|
||||||
UsageDataScreen(onBack = { subRoute = UsageSubRoute.Chart }, autoLoad = true)
|
UsageDataScreen(onBack = { navController.popBackStack() }, autoLoad = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is DetailPane.CCGOUsage -> {
|
is DetailPane.CCGOUsage -> {
|
||||||
|
val navController = rememberNavController()
|
||||||
val wid = CommandCodeUsageRepository.CCGO_WORKSPACE_ID
|
val wid = CommandCodeUsageRepository.CCGO_WORKSPACE_ID
|
||||||
val chartVm: UsageChartViewModel = hiltViewModel(key = "ccgo_chart_$wid")
|
val chartVm: UsageChartViewModel = hiltViewModel(key = "ccgo_chart_$wid")
|
||||||
val usageVm: UsageViewModel = hiltViewModel(key = "ccgo_$wid")
|
val usageVm: UsageViewModel = hiltViewModel(key = "ccgo_$wid")
|
||||||
@ -324,37 +317,40 @@ private fun ExpandedDetailPane(
|
|||||||
usageVm.setWorkspace(wid)
|
usageVm.setWorkspace(wid)
|
||||||
chartVm.setWorkspace(wid)
|
chartVm.setWorkspace(wid)
|
||||||
}
|
}
|
||||||
var subRoute by remember { mutableStateOf<UsageSubRoute>(UsageSubRoute.Chart) }
|
NavHost(
|
||||||
when (subRoute) {
|
navController = navController,
|
||||||
is UsageSubRoute.Chart -> {
|
startDestination = "chart",
|
||||||
|
popExitTransition = { ExitTransition.None }
|
||||||
|
) {
|
||||||
|
composable("chart") {
|
||||||
UsageDetailScreen(
|
UsageDetailScreen(
|
||||||
onBack = onClose,
|
onBack = onClose,
|
||||||
onOpenOverview = { subRoute = UsageSubRoute.Overview },
|
onOpenOverview = { navController.navigate("overview") },
|
||||||
onOpenData = { subRoute = UsageSubRoute.Data },
|
onOpenData = { navController.navigate("data") },
|
||||||
viewModel = chartVm,
|
viewModel = chartVm,
|
||||||
clearViewModel = usageVm
|
clearViewModel = usageVm
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is UsageSubRoute.Overview -> {
|
composable("overview") {
|
||||||
val key = "ccgo_$wid"
|
val key = "ccgo_$wid"
|
||||||
val ovVm: UsageViewModel = hiltViewModel(key = key)
|
val ovVm: UsageViewModel = hiltViewModel(key = key)
|
||||||
LaunchedEffect(Unit) { ovVm.setWorkspace(wid) }
|
LaunchedEffect(Unit) { ovVm.setWorkspace(wid) }
|
||||||
UsageOverviewScreen(onBack = { subRoute = UsageSubRoute.Chart }, viewModel = ovVm, autoLoad = false)
|
UsageOverviewScreen(onBack = { navController.popBackStack() }, viewModel = ovVm, autoLoad = false)
|
||||||
}
|
}
|
||||||
is UsageSubRoute.Data -> {
|
composable("data") {
|
||||||
val key = "ccgo_$wid"
|
val key = "ccgo_$wid"
|
||||||
val dataVm: UsageDataViewModel = hiltViewModel(key = key)
|
val dataVm: UsageDataViewModel = hiltViewModel(key = key)
|
||||||
LaunchedEffect(Unit) { dataVm.setWorkspace(wid) }
|
LaunchedEffect(Unit) { dataVm.setWorkspace(wid) }
|
||||||
UsageDataScreen(onBack = { subRoute = UsageSubRoute.Chart }, viewModel = dataVm, autoLoad = false)
|
UsageDataScreen(onBack = { navController.popBackStack() }, viewModel = dataVm, autoLoad = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is DetailPane.Settings -> {
|
is DetailPane.Settings -> {
|
||||||
// Settings 内部子路由用局部 NavHost
|
|
||||||
val settingsNavController = rememberNavController()
|
val settingsNavController = rememberNavController()
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = settingsNavController,
|
navController = settingsNavController,
|
||||||
startDestination = "settings_main"
|
startDestination = "settings_main",
|
||||||
|
popExitTransition = { ExitTransition.None }
|
||||||
) {
|
) {
|
||||||
composable("settings_main") {
|
composable("settings_main") {
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user