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
8 changed files with 7 additions and 115 deletions
Showing only changes of commit 0123c30867 - Show all commits
@@ -1,9 +1,11 @@
package com.flagship.abox.manager.ui.login package com.flagship.abox.manager.ui.login
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
@@ -29,7 +31,7 @@ class LoginScreenTest {
) )
@Test @Test
fun backControlsSwitchProviderBeforeLogin() { fun topBackControlSwitchesProviderBeforeLogin() {
var switches = 0 var switches = 0
composeRule.setContent { composeRule.setContent {
ABoxTheme { ABoxTheme {
@@ -49,8 +51,8 @@ class LoginScreenTest {
composeRule.onNodeWithContentDescription("back-to-providers") composeRule.onNodeWithContentDescription("back-to-providers")
.assertIsEnabled() .assertIsEnabled()
.performClick() .performClick()
composeRule.onNodeWithText("Back to providers").performClick() composeRule.onAllNodesWithText("Back to providers").assertCountEquals(0)
assertEquals(2, switches) assertEquals(1, switches)
} }
@Test @Test
@@ -94,6 +96,6 @@ class LoginScreenTest {
} }
composeRule.onNodeWithContentDescription("back-to-providers").assertIsNotEnabled() composeRule.onNodeWithContentDescription("back-to-providers").assertIsNotEnabled()
composeRule.onNodeWithText("Back to providers").assertIsNotEnabled() composeRule.onAllNodesWithText("Back to providers").assertCountEquals(0)
} }
} }
@@ -60,7 +60,6 @@ fun ABoxManagerApp(
is AppSessionUiState.Authenticated -> MainAppShell( is AppSessionUiState.Authenticated -> MainAppShell(
Review

入口缺失

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

**入口缺失** 认证态切换到 `MainAppShell` 后不再传递 `sessionActionsViewModel::switchProvider`,新壳层和账户底表也没有切换入口。已登录用户只能先退出登录,原先可直接切换提供方的功能被回退,也与本 PR 描述不符。请将切换回调贯穿到壳层和底表并提供对应操作。 <!-- pr-agent-dedup: f6b10d903f5d --> <!-- pr-agent-key-issue-location: 488326c83adb -->
userName = state.username.value, userName = state.username.value,
providerName = state.profile.displayName, providerName = state.profile.displayName,
onSwitchProvider = sessionActionsViewModel::switchProvider,
onLogout = sessionActionsViewModel::logout, onLogout = sessionActionsViewModel::logout,
actionInProgress = actionState.inProgress, actionInProgress = actionState.inProgress,
errorMessage = actionState.errorMessage, errorMessage = actionState.errorMessage,
@@ -1,79 +0,0 @@
package com.flagship.abox.manager.ui.authenticated
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.flagship.abox.manager.R
import com.flagship.abox.manager.designsystem.ABoxButton
import com.flagship.abox.manager.domain.ProviderProfile
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AuthenticatedPlaceholderScreen(
profile: ProviderProfile,
actionInProgress: Boolean,
errorMessage: String?,
onLogout: () -> Unit,
onSwitchProvider: () -> Unit,
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.authenticated_title, profile.displayName)) },
)
},
) { contentPadding ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(contentPadding),
contentAlignment = Alignment.Center,
) {
Column(
modifier = Modifier
.widthIn(max = 520.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(14.dp),
) {
Text(
text = stringResource(R.string.authenticated_description),
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
errorMessage?.let { Text(it, color = MaterialTheme.colorScheme.error) }
ABoxButton(
onClick = onLogout,
enabled = !actionInProgress,
modifier = Modifier.fillMaxWidth(),
) {
Text(stringResource(R.string.action_logout))
}
ABoxButton(
onClick = onSwitchProvider,
enabled = !actionInProgress,
modifier = Modifier.fillMaxWidth(),
) {
Text(stringResource(R.string.action_switch_provider))
}
}
}
}
}
@@ -184,13 +184,6 @@ fun LoginScreen(
} }
else Text(stringResource(R.string.action_login)) else Text(stringResource(R.string.action_login))
} }
ABoxButton(
onClick = onSwitchProvider,
enabled = navigationEnabled,
modifier = Modifier.fillMaxWidth(),
) {
Text(stringResource(R.string.login_back_to_providers))
}
} }
} }
} }
@@ -15,7 +15,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ExitToApp import androidx.compose.material.icons.automirrored.filled.ExitToApp
import androidx.compose.material.icons.filled.CloudDone import androidx.compose.material.icons.filled.CloudDone
import androidx.compose.material.icons.filled.SwapHoriz
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
@@ -50,7 +49,6 @@ fun AccountProviderBottomSheet(
userName: String, userName: String,
providerName: String, providerName: String,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onSwitchProvider: () -> Unit,
onLogout: () -> Unit, onLogout: () -> Unit,
actionInProgress: Boolean, actionInProgress: Boolean,
errorMessage: String?, errorMessage: String?,
@@ -146,22 +144,7 @@ fun AccountProviderBottomSheet(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// Action Buttons // Action button
OutlinedButton(
onClick = onSwitchProvider,
enabled = !actionInProgress,
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
) {
Icon(
imageVector = Icons.Default.SwapHoriz,
contentDescription = null,
modifier = Modifier.size(20.dp),
)
Spacer(modifier = Modifier.size(8.dp))
Text(stringResource(R.string.account_sheet_switch_provider))
}
ABoxButton( ABoxButton(
onClick = onLogout, onClick = onLogout,
enabled = !actionInProgress, enabled = !actionInProgress,
@@ -42,7 +42,6 @@ import com.flagship.abox.manager.designsystem.ABoxTopPillBar
fun MainAppShell( fun MainAppShell(
userName: String, userName: String,
providerName: String, providerName: String,
onSwitchProvider: () -> Unit,
onLogout: () -> Unit, onLogout: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
initialTab: MainTab = MainTab.DEVICES, initialTab: MainTab = MainTab.DEVICES,
@@ -125,7 +124,6 @@ fun MainAppShell(
userName = userName, userName = userName,
providerName = providerName, providerName = providerName,
onDismissRequest = { showAccountSheet = false }, onDismissRequest = { showAccountSheet = false },
onSwitchProvider = onSwitchProvider,
onLogout = onLogout, onLogout = onLogout,
actionInProgress = actionInProgress, actionInProgress = actionInProgress,
errorMessage = errorMessage, errorMessage = errorMessage,
@@ -8,7 +8,6 @@
<string name="provider_local_description">无需网络,通过本地模拟设备体验应用。</string> <string name="provider_local_description">无需网络,通过本地模拟设备体验应用。</string>
<string name="login_title">登录 %1$s</string> <string name="login_title">登录 %1$s</string>
<string name="login_provider_context">当前提供方:%1$s</string> <string name="login_provider_context">当前提供方:%1$s</string>
<string name="login_back_to_providers">返回提供方选择</string>
<string name="login_username">用户名</string> <string name="login_username">用户名</string>
<string name="login_password">密码</string> <string name="login_password">密码</string>
<string name="login_remember_username">记住用户名</string> <string name="login_remember_username">记住用户名</string>
@@ -35,7 +34,6 @@
<string name="account_sheet_title">账户与提供方</string> <string name="account_sheet_title">账户与提供方</string>
<string name="account_sheet_signed_in_as">当前登录为</string> <string name="account_sheet_signed_in_as">当前登录为</string>
<string name="account_sheet_provider">当前提供方</string> <string name="account_sheet_provider">当前提供方</string>
<string name="account_sheet_switch_provider">切换提供方</string>
<string name="account_sheet_logout">退出登录</string> <string name="account_sheet_logout">退出登录</string>
<string name="account_sheet_close">关闭</string> <string name="account_sheet_close">关闭</string>
<string name="devices_placeholder_title">设备列表</string> <string name="devices_placeholder_title">设备列表</string>
@@ -8,7 +8,6 @@
<string name="provider_local_description">Explore the app with simulated local devices and no network connection.</string> <string name="provider_local_description">Explore the app with simulated local devices and no network connection.</string>
<string name="login_title">Sign in to %1$s</string> <string name="login_title">Sign in to %1$s</string>
<string name="login_provider_context">Selected provider: %1$s</string> <string name="login_provider_context">Selected provider: %1$s</string>
<string name="login_back_to_providers">Back to providers</string>
<string name="login_username">Username</string> <string name="login_username">Username</string>
<string name="login_password">Password</string> <string name="login_password">Password</string>
<string name="login_remember_username">Remember username</string> <string name="login_remember_username">Remember username</string>
@@ -35,7 +34,6 @@
<string name="account_sheet_title">Account &amp; Provider</string> <string name="account_sheet_title">Account &amp; Provider</string>
<string name="account_sheet_signed_in_as">Signed in as</string> <string name="account_sheet_signed_in_as">Signed in as</string>
<string name="account_sheet_provider">Active Provider</string> <string name="account_sheet_provider">Active Provider</string>
<string name="account_sheet_switch_provider">Switch Provider</string>
<string name="account_sheet_logout">Sign Out</string> <string name="account_sheet_logout">Sign Out</string>
<string name="account_sheet_close">Close</string> <string name="account_sheet_close">Close</string>
<string name="devices_placeholder_title">Device List</string> <string name="devices_placeholder_title">Device List</string>