From 6e78b71d3128289331a73cd10bedafc25fb9e802 Mon Sep 17 00:00:00 2001 From: KremeCN <3265494849@qq.com> Date: Wed, 26 Aug 2026 19:04:57 +0800 Subject: [PATCH] feat: redesign account and provider dialog --- .../ui/shell/AccountProviderDialogTest.kt | 73 +++++++ .../abox/manager/ui/ABoxManagerApp.kt | 60 ++++-- .../ui/navigation/AuthenticatedNavigation.kt | 10 + .../abox/manager/ui/navigation/Routes.kt | 1 + .../ui/shell/AccountProviderBottomSheet.kt | 183 ---------------- .../manager/ui/shell/AccountProviderDialog.kt | 198 ++++++++++++++++++ 6 files changed, 321 insertions(+), 204 deletions(-) create mode 100644 android/app/src/androidTest/kotlin/com/flagship/abox/manager/ui/shell/AccountProviderDialogTest.kt delete mode 100644 android/app/src/main/kotlin/com/flagship/abox/manager/ui/shell/AccountProviderBottomSheet.kt create mode 100644 android/app/src/main/kotlin/com/flagship/abox/manager/ui/shell/AccountProviderDialog.kt diff --git a/android/app/src/androidTest/kotlin/com/flagship/abox/manager/ui/shell/AccountProviderDialogTest.kt b/android/app/src/androidTest/kotlin/com/flagship/abox/manager/ui/shell/AccountProviderDialogTest.kt new file mode 100644 index 0000000..c1581de --- /dev/null +++ b/android/app/src/androidTest/kotlin/com/flagship/abox/manager/ui/shell/AccountProviderDialogTest.kt @@ -0,0 +1,73 @@ +package com.flagship.abox.manager.ui.shell + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.flagship.abox.manager.designsystem.ABoxTheme +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class AccountProviderDialogTest { + @get:Rule + val composeRule = createAndroidComposeRule() + + @Test + fun dialogExposesAccountActionsAndClose() { + var closed = 0 + composeRule.setContent { + ABoxTheme { + AccountProviderDialog( + userName = "user", + providerName = "ABox", + onDismissRequest = { closed++ }, + onOpenSettings = {}, + onSwitchProvider = {}, + onEndSession = {}, + onForgetCredentials = {}, + ) + } + } + + composeRule.onNodeWithText("账户与提供方").assertIsDisplayed() + composeRule.onNodeWithTag("account_dialog_close").performClick() + + assertEquals(1, closed) + } + + @Test + fun destructiveActionsRequireSeparateConfirmation() { + var ended = 0 + var forgotten = 0 + composeRule.setContent { + ABoxTheme { + AccountProviderDialog( + userName = "user", + providerName = "ABox", + onDismissRequest = {}, + onOpenSettings = {}, + onSwitchProvider = {}, + onEndSession = { ended++ }, + onForgetCredentials = { forgotten++ }, + ) + } + } + + composeRule.onNodeWithTag("account_dialog_end_session").performClick() + composeRule.onNodeWithText("退出当前会话").assertIsDisplayed() + composeRule.onNodeWithTag("account_dialog_confirm").performClick() + assertEquals(1, ended) + assertEquals(0, forgotten) + + composeRule.onNodeWithTag("account_dialog_forget_credentials").performClick() + composeRule.onNodeWithText("清除保存的凭据").assertIsDisplayed() + composeRule.onNodeWithTag("account_dialog_confirm").performClick() + assertEquals(1, forgotten) + } +} diff --git a/android/app/src/main/kotlin/com/flagship/abox/manager/ui/ABoxManagerApp.kt b/android/app/src/main/kotlin/com/flagship/abox/manager/ui/ABoxManagerApp.kt index ae06c1f..2c8fd64 100644 --- a/android/app/src/main/kotlin/com/flagship/abox/manager/ui/ABoxManagerApp.kt +++ b/android/app/src/main/kotlin/com/flagship/abox/manager/ui/ABoxManagerApp.kt @@ -5,7 +5,7 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -24,12 +24,14 @@ import com.flagship.abox.manager.domain.Username import com.flagship.abox.manager.ui.login.LoginRoute import com.flagship.abox.manager.ui.login.LoginViewModel import com.flagship.abox.manager.ui.navigation.AuthenticatedNavigation +import com.flagship.abox.manager.ui.preferences.SettingsRoute import com.flagship.abox.manager.ui.preferences.SettingsViewModel import com.flagship.abox.manager.ui.provider.ProviderSelectionRoute import com.flagship.abox.manager.ui.session.AppSessionUiState import com.flagship.abox.manager.ui.session.AppSessionViewModel import com.flagship.abox.manager.ui.session.SessionActionsViewModel -import com.flagship.abox.manager.ui.shell.AccountProviderBottomSheet +import com.flagship.abox.manager.ui.shell.AccountProviderDialog +import com.flagship.abox.manager.ui.session.SessionUiAction @Composable fun ABoxManagerApp( @@ -74,42 +76,58 @@ fun ABoxManagerApp( is AppSessionUiState.Authenticated -> AuthenticatedNavHost( username = state.username, profile = state.profile, - actionInProgress = actionState.inProgress, - errorMessage = actionError, onLogout = sessionActionsViewModel::logout, + onSessionAction = sessionActionsViewModel::dispatch, ) } } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable private fun AuthenticatedNavHost( username: Username, profile: ProviderProfile, - actionInProgress: Boolean, - errorMessage: String?, onLogout: () -> Unit, + onSessionAction: (SessionUiAction) -> Unit, ) { - var showAccountSheet by remember { mutableStateOf(false) } + var showAccountDialog by remember { mutableStateOf(false) } + var showSettings by remember { mutableStateOf(false) } - AuthenticatedNavigation( - username = username, - profile = profile, - onLogout = onLogout, - onProviderClick = { showAccountSheet = true }, - onAvatarClick = { showAccountSheet = true }, - ) + if (showSettings) { + BackHandler { showSettings = false } + SettingsRoute() + } else { + AuthenticatedNavigation( + username = username, + profile = profile, + onLogout = onLogout, + onProviderClick = { showAccountDialog = true }, + onAvatarClick = { showAccountDialog = true }, + ) + } - if (showAccountSheet) { - AccountProviderBottomSheet( + if (showAccountDialog) { + AccountProviderDialog( userName = username.value, providerName = profile.displayName, - onDismissRequest = { showAccountSheet = false }, - onLogout = onLogout, - actionInProgress = actionInProgress, - errorMessage = errorMessage, + onDismissRequest = { showAccountDialog = false }, + onOpenSettings = { + showAccountDialog = false + showSettings = true + }, + onSwitchProvider = { + showAccountDialog = false + onSessionAction(SessionUiAction.SwitchProvider) + }, + onEndSession = { + showAccountDialog = false + onSessionAction(SessionUiAction.EndSession) + }, + onForgetCredentials = { + showAccountDialog = false + onSessionAction(SessionUiAction.ForgetSavedCredentials) + }, ) } } diff --git a/android/app/src/main/kotlin/com/flagship/abox/manager/ui/navigation/AuthenticatedNavigation.kt b/android/app/src/main/kotlin/com/flagship/abox/manager/ui/navigation/AuthenticatedNavigation.kt index 3ee8944..3b5abb9 100644 --- a/android/app/src/main/kotlin/com/flagship/abox/manager/ui/navigation/AuthenticatedNavigation.kt +++ b/android/app/src/main/kotlin/com/flagship/abox/manager/ui/navigation/AuthenticatedNavigation.kt @@ -42,6 +42,7 @@ import com.flagship.abox.manager.infrared.panel.RemotePanelRoute import com.flagship.abox.manager.infrared.ui.InfraredControlRoute import com.flagship.abox.manager.ui.devicecontrollist.DeviceListScreen import com.flagship.abox.manager.ui.scene.SceneListScreen +import com.flagship.abox.manager.ui.preferences.SettingsRoute import com.flagship.abox.manager.ui.scene.SceneRoutes import com.flagship.abox.manager.ui.scene.sceneEditorDestination import com.flagship.abox.manager.ui.scene.sceneExecutionDestination @@ -221,6 +222,7 @@ fun AuthenticatedNavigation( onLogout: () -> Unit, onProviderClick: () -> Unit, onAvatarClick: () -> Unit, + onOpenSettings: () -> Unit = {}, windowSizeClass: ABoxWindowSizeClass? = null, ) { val resolvedWindowSizeClass = windowSizeClass ?: widthClass(LocalConfiguration.current.screenWidthDp) @@ -262,6 +264,7 @@ fun AuthenticatedNavigation( onSelectDevice = onSelectDevice, onClearDeviceSelection = onClearDeviceSelection, onLogout = onLogout, + onOpenSettings = onOpenSettings, ) }, ) @@ -279,6 +282,7 @@ private fun AuthenticatedGraphContent( onSelectDevice: (DeviceName, DeviceType) -> Unit, onClearDeviceSelection: () -> Unit, onLogout: () -> Unit, + onOpenSettings: () -> Unit, ) { Box( modifier = Modifier @@ -294,6 +298,7 @@ private fun AuthenticatedGraphContent( onSelectDevice = onSelectDevice, onClearDeviceSelection = onClearDeviceSelection, onLogout = onLogout, + onOpenSettings = onOpenSettings, ) DeviceDetailPane( modifier = Modifier.weight(1f), @@ -309,6 +314,7 @@ private fun AuthenticatedGraphContent( onSelectDevice = onSelectDevice, onClearDeviceSelection = onClearDeviceSelection, onLogout = onLogout, + onOpenSettings = onOpenSettings, ) } } @@ -322,6 +328,7 @@ private fun AuthenticatedNavHost( onSelectDevice: (DeviceName, DeviceType) -> Unit, onClearDeviceSelection: () -> Unit, onLogout: () -> Unit, + onOpenSettings: () -> Unit, ) { NavHost( navController = navController, @@ -342,6 +349,9 @@ private fun AuthenticatedNavHost( composable(MainTab.ACTIVITY.route) { ActivityDestination() } + composable(Routes.SETTINGS) { + SettingsRoute() + } deviceDetailGraph( onBackClick = { onClearDeviceSelection() diff --git a/android/app/src/main/kotlin/com/flagship/abox/manager/ui/navigation/Routes.kt b/android/app/src/main/kotlin/com/flagship/abox/manager/ui/navigation/Routes.kt index cc1c4e0..c7b2adb 100644 --- a/android/app/src/main/kotlin/com/flagship/abox/manager/ui/navigation/Routes.kt +++ b/android/app/src/main/kotlin/com/flagship/abox/manager/ui/navigation/Routes.kt @@ -5,6 +5,7 @@ object Routes { const val PROVIDER_SELECTION = "provider_selection" const val LOGIN = "login" const val AUTHENTICATED = "authenticated" + const val SETTINGS = "settings" const val INFRARED_HUB = "infrared_hub" const val INFRARED_PANEL = "infrared_panel" diff --git a/android/app/src/main/kotlin/com/flagship/abox/manager/ui/shell/AccountProviderBottomSheet.kt b/android/app/src/main/kotlin/com/flagship/abox/manager/ui/shell/AccountProviderBottomSheet.kt deleted file mode 100644 index 106c288..0000000 --- a/android/app/src/main/kotlin/com/flagship/abox/manager/ui/shell/AccountProviderBottomSheet.kt +++ /dev/null @@ -1,183 +0,0 @@ -package com.flagship.abox.manager.ui.shell - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ExitToApp -import androidx.compose.material.icons.filled.CloudDone -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.SheetState -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import com.flagship.abox.manager.R -import com.flagship.abox.manager.designsystem.ABoxButton -import com.flagship.abox.manager.designsystem.ABoxUserAvatar - -/** - * Account and Provider management bottom sheet triggered by clicking the top bar user avatar. - * Allows viewing current session info and signing out. - */ -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AccountProviderBottomSheet( - userName: String, - providerName: String, - onDismissRequest: () -> Unit, - onLogout: () -> Unit, - actionInProgress: Boolean, - errorMessage: String?, - modifier: Modifier = Modifier, - sheetState: SheetState = rememberModalBottomSheetState(), -) { - ModalBottomSheet( - onDismissRequest = onDismissRequest, - sheetState = sheetState, - modifier = modifier, - containerColor = MaterialTheme.colorScheme.surface, - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()) - .padding(horizontal = 24.dp) - .padding(bottom = 32.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - // Header with large avatar & user info - ABoxUserAvatar( - name = userName, - onClick = {}, - size = 56.dp, - ) - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - Text( - text = userName.ifBlank { stringResource(R.string.account_sheet_title) }, - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold), - color = MaterialTheme.colorScheme.onSurface, - ) - Text( - text = stringResource(R.string.account_sheet_signed_in_as), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - - HorizontalDivider( - modifier = Modifier.padding(vertical = 4.dp), - color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f), - ) - - // Provider Info Card - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), - ), - shape = RoundedCornerShape(16.dp), - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - Box( - modifier = Modifier - .size(40.dp) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.primaryContainer), - contentAlignment = Alignment.Center, - ) { - Icon( - imageVector = Icons.Default.CloudDone, - contentDescription = null, - tint = MaterialTheme.colorScheme.onPrimaryContainer, - modifier = Modifier.size(22.dp), - ) - } - - Column(modifier = Modifier.weight(1f)) { - Text( - text = stringResource(R.string.account_sheet_provider), - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - Text( - text = providerName, - style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.SemiBold), - color = MaterialTheme.colorScheme.onSurface, - ) - } - } - } - - Spacer(modifier = Modifier.height(8.dp)) - - // Action button - ABoxButton( - onClick = onLogout, - enabled = !actionInProgress, - modifier = Modifier.fillMaxWidth(), - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ExitToApp, - contentDescription = null, - modifier = Modifier.size(20.dp), - ) - Spacer(modifier = Modifier.size(8.dp)) - Text(stringResource(R.string.account_sheet_logout)) - } - - errorMessage?.let { error -> - Text( - text = error, - color = MaterialTheme.colorScheme.error, - style = MaterialTheme.typography.bodyMedium, - ) - } - - TextButton( - onClick = onDismissRequest, - enabled = !actionInProgress, - modifier = Modifier.fillMaxWidth(), - ) { - Text( - text = stringResource(R.string.account_sheet_close), - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } - } -} diff --git a/android/app/src/main/kotlin/com/flagship/abox/manager/ui/shell/AccountProviderDialog.kt b/android/app/src/main/kotlin/com/flagship/abox/manager/ui/shell/AccountProviderDialog.kt new file mode 100644 index 0000000..c418397 --- /dev/null +++ b/android/app/src/main/kotlin/com/flagship/abox/manager/ui/shell/AccountProviderDialog.kt @@ -0,0 +1,198 @@ +package com.flagship.abox.manager.ui.shell + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.CloudDone +import androidx.compose.material.icons.outlined.Logout +import androidx.compose.material.icons.outlined.Refresh +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.unit.dp +import com.flagship.abox.manager.designsystem.ABoxButton +import com.flagship.abox.manager.designsystem.ABoxIconButton +import com.flagship.abox.manager.designsystem.ABoxUserAvatar +import com.flagship.abox.manager.designsystem.icon.ABoxIconKey + +private enum class DestructiveAction { + END_SESSION, + FORGET_CREDENTIALS, +} + +@Composable +fun AccountProviderDialog( + userName: String, + providerName: String, + onDismissRequest: () -> Unit, + onOpenSettings: () -> Unit, + onSwitchProvider: () -> Unit, + onEndSession: () -> Unit, + onForgetCredentials: () -> Unit, + modifier: Modifier = Modifier, +) { + var confirmation by remember { mutableStateOf(null) } + + if (confirmation == null) { + AlertDialog( + onDismissRequest = onDismissRequest, + modifier = modifier.testTag("account_dialog"), + title = { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text("账户与提供方") + ABoxIconButton( + key = ABoxIconKey.CLOSE, + contentDescription = "关闭账户对话框", + onClick = onDismissRequest, + modifier = Modifier.testTag("account_dialog_close"), + ) + } + }, + text = { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + ABoxUserAvatar(name = userName, onClick = {}, size = 64.dp) + Text( + text = userName.ifBlank { "当前账户" }, + style = MaterialTheme.typography.titleMedium, + ) + Text( + text = providerName, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Surface( + modifier = Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.55f), + shape = MaterialTheme.shapes.medium, + ) { + Row( + modifier = Modifier.fillMaxWidth().padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Icon( + imageVector = Icons.Outlined.CloudDone, + contentDescription = "连接方式", + modifier = Modifier.size(24.dp), + ) + Column(modifier = Modifier.weight(1f)) { + Text("当前连接方式", style = MaterialTheme.typography.labelMedium) + Text(providerName, style = MaterialTheme.typography.bodyLarge) + } + TextButton( + onClick = onSwitchProvider, + modifier = Modifier.testTag("account_dialog_switch_provider"), + ) { + Icon(Icons.Outlined.Refresh, "切换提供方") + Text("切换") + } + } + } + AccountActionButton( + icon = Icons.Outlined.Settings, + label = "界面设置", + description = "调整主题、渲染器和交互偏好", + tag = "account_dialog_open_settings", + onClick = onOpenSettings, + ) + AccountActionButton( + icon = Icons.Outlined.Logout, + label = "退出当前会话", + description = "结束当前提供方会话", + tag = "account_dialog_end_session", + onClick = { confirmation = DestructiveAction.END_SESSION }, + ) + TextButton( + onClick = { confirmation = DestructiveAction.FORGET_CREDENTIALS }, + modifier = Modifier.fillMaxWidth().testTag("account_dialog_forget_credentials"), + ) { + Text("清除保存的凭据", color = MaterialTheme.colorScheme.error) + } + } + }, + confirmButton = {}, + ) + } else { + val action = confirmation ?: return + val isForget = action == DestructiveAction.FORGET_CREDENTIALS + AlertDialog( + onDismissRequest = { confirmation = null }, + title = { Text(if (isForget) "清除保存的凭据" else "退出当前会话") }, + text = { + Text( + if (isForget) { + "确定清除当前提供方保存的登录凭据吗?" + } else { + "确定退出当前会话吗?" + }, + ) + }, + confirmButton = { + ABoxButton( + onClick = { + confirmation = null + if (isForget) onForgetCredentials() else onEndSession() + }, + modifier = Modifier.testTag("account_dialog_confirm"), + ) { + Text("确定") + } + }, + dismissButton = { + TextButton(onClick = { confirmation = null }) { Text("取消") } + }, + ) + } +} + +@Composable +private fun AccountActionButton( + icon: androidx.compose.ui.graphics.vector.ImageVector, + label: String, + description: String, + tag: String, + onClick: () -> Unit, +) { + TextButton( + onClick = onClick, + modifier = Modifier.fillMaxWidth().testTag(tag), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Icon(icon, contentDescription = label) + Column(modifier = Modifier.weight(1f)) { + Text(label) + Text(description, style = MaterialTheme.typography.bodySmall) + } + } + } +}