diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/V2CommandPalette.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/V2CommandPalette.kt index 3c5fb58160d..597353a8a91 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/V2CommandPalette.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/V2CommandPalette.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn @@ -188,13 +189,14 @@ private fun V2CommandActionRow(row: V2CommandItem) { modifier = Modifier .fillMaxWidth() + .heightIn(min = 52.dp) .clip(RoundedCornerShape(ClawTheme.radii.row)) .clickable(onClick = row.onClick) - .padding(vertical = 5.dp), + .padding(horizontal = 2.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(9.dp), ) { - Icon(imageVector = row.icon, contentDescription = null, modifier = Modifier.size(15.dp), tint = ClawTheme.colors.text) + Icon(imageVector = row.icon, contentDescription = null, modifier = Modifier.size(19.dp), tint = ClawTheme.colors.text) Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) { Text(text = row.title, style = ClawTheme.type.body, color = ClawTheme.colors.text, maxLines = 1, overflow = TextOverflow.Ellipsis) Text(text = row.subtitle, style = ClawTheme.type.caption, color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis) @@ -202,7 +204,7 @@ private fun V2CommandActionRow(row: V2CommandItem) { Icon( imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = "Open ${row.title}", - modifier = Modifier.size(14.dp), + modifier = Modifier.size(17.dp), tint = ClawTheme.colors.textMuted, ) } @@ -236,20 +238,21 @@ private fun V2CommandSessionListRow( modifier = Modifier .fillMaxWidth() + .heightIn(min = 58.dp) .clip(RoundedCornerShape(ClawTheme.radii.row)) .clickable(onClick = onClick) - .padding(vertical = 5.dp), + .padding(horizontal = 2.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { Surface( - modifier = Modifier.size(24.dp), + modifier = Modifier.size(30.dp), shape = CircleShape, color = ClawTheme.colors.canvas, border = BorderStroke(1.dp, ClawTheme.colors.borderStrong), ) { Box(contentAlignment = Alignment.Center) { - Icon(imageVector = Icons.Outlined.ChatBubbleOutline, contentDescription = null, modifier = Modifier.size(12.dp), tint = ClawTheme.colors.text) + Icon(imageVector = Icons.Outlined.ChatBubbleOutline, contentDescription = null, modifier = Modifier.size(15.dp), tint = ClawTheme.colors.text) } } Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) { @@ -260,7 +263,7 @@ private fun V2CommandSessionListRow( Icon( imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = "Open session", - modifier = Modifier.size(14.dp), + modifier = Modifier.size(17.dp), tint = ClawTheme.colors.textMuted, ) } @@ -273,7 +276,7 @@ private fun V2CommandIconButton( contentDescription: String, onClick: () -> Unit, ) { - Surface(onClick = onClick, modifier = Modifier.size(30.dp), shape = CircleShape, color = Color.Transparent, contentColor = ClawTheme.colors.text) { + Surface(onClick = onClick, modifier = Modifier.size(ClawTheme.spacing.touchTarget), shape = CircleShape, color = Color.Transparent, contentColor = ClawTheme.colors.text) { Box(contentAlignment = Alignment.Center) { Icon(imageVector = icon, contentDescription = contentDescription, modifier = Modifier.size(18.dp)) } @@ -283,7 +286,7 @@ private fun V2CommandIconButton( @Composable private fun V2CommandAvatar(text: String) { Surface( - modifier = Modifier.size(28.dp), + modifier = Modifier.size(34.dp), shape = CircleShape, color = ClawTheme.colors.surfaceRaised, contentColor = ClawTheme.colors.text, diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/V2ShellScreen.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/V2ShellScreen.kt index 6c4b51cc58f..c2c7006f275 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/V2ShellScreen.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/V2ShellScreen.kt @@ -90,6 +90,8 @@ fun V2ShellScreen( ) { ClawDesignTheme { var activeTab by rememberSaveable { mutableStateOf(V2Tab.Overview) } + var settingsRoute by rememberSaveable { mutableStateOf(V2SettingsRoute.Home) } + var returnToOverviewFromSettings by rememberSaveable { mutableStateOf(false) } var commandOpen by rememberSaveable { mutableStateOf(false) } val requestedHomeDestination by viewModel.requestedHomeDestination.collectAsState() @@ -103,6 +105,10 @@ fun V2ShellScreen( HomeDestination.Screen -> V2Tab.Chat HomeDestination.Settings -> V2Tab.Settings } + if (destination == HomeDestination.Settings) { + settingsRoute = V2SettingsRoute.Home + returnToOverviewFromSettings = false + } viewModel.clearRequestedHomeDestination() } @@ -124,6 +130,11 @@ fun V2ShellScreen( V2OverviewScreen( viewModel = viewModel, onSelectTab = { activeTab = it }, + onOpenSettingsRoute = { + settingsRoute = it + returnToOverviewFromSettings = true + activeTab = V2Tab.Settings + }, onOpenCommand = { commandOpen = true }, ) V2Tab.Chat -> @@ -137,7 +148,11 @@ fun V2ShellScreen( V2ProvidersModelsScreen( viewModel = viewModel, onBack = { activeTab = V2Tab.Overview }, - onAddProvider = { activeTab = V2Tab.Settings }, + onAddProvider = { + settingsRoute = V2SettingsRoute.Gateway + returnToOverviewFromSettings = false + activeTab = V2Tab.Settings + }, ) V2Tab.Sessions -> V2SessionsScreen( @@ -145,7 +160,23 @@ fun V2ShellScreen( onOpenCommand = { commandOpen = true }, onOpenChat = { activeTab = V2Tab.Chat }, ) - V2Tab.Settings -> V2SettingsShellScreen(viewModel = viewModel, onOpenCommand = { commandOpen = true }) + V2Tab.Settings -> + V2SettingsShellScreen( + viewModel = viewModel, + route = settingsRoute, + onRouteChange = { + settingsRoute = it + returnToOverviewFromSettings = false + }, + onRouteBack = { + settingsRoute = V2SettingsRoute.Home + if (returnToOverviewFromSettings) { + returnToOverviewFromSettings = false + activeTab = V2Tab.Overview + } + }, + onOpenCommand = { commandOpen = true }, + ) } if (commandOpen) { @@ -169,6 +200,8 @@ fun V2ShellScreen( commandOpen = false }, onOpenSettings = { + settingsRoute = V2SettingsRoute.Home + returnToOverviewFromSettings = false activeTab = V2Tab.Settings commandOpen = false }, @@ -187,6 +220,7 @@ fun V2ShellScreen( private fun V2OverviewScreen( viewModel: MainViewModel, onSelectTab: (V2Tab) -> Unit, + onOpenSettingsRoute: (V2SettingsRoute) -> Unit, onOpenCommand: () -> Unit, ) { val isConnected by viewModel.isConnected.collectAsState() @@ -260,16 +294,17 @@ private fun V2OverviewScreen( icon = Icons.Outlined.Inventory2, tab = V2Tab.ProvidersModels, ), - V2ModuleRow("Channels", null, channelsSummaryText(channelsSummary), Icons.Default.Notifications, V2Tab.Settings), - V2ModuleRow("Agents", null, if (agents.isEmpty()) "Load" else "${agents.size} ready", Icons.Default.Person, V2Tab.Settings), - V2ModuleRow("Approvals", null, approvalsSummary(pendingToolCalls.size), Icons.Default.Lock, V2Tab.Settings), - V2ModuleRow("Cron Jobs", null, cronJobsSummary(cronStatus.jobs), Icons.Outlined.AccessTime, V2Tab.Settings), - V2ModuleRow("Skills", null, skillsSummaryText(skillsSummary.skills), Icons.Default.Settings, V2Tab.Settings), - V2ModuleRow("Nodes & Devices", null, nodesDevicesSummaryText(nodesDevicesSummary), Icons.Default.Cloud, V2Tab.Settings), - V2ModuleRow("Usage", null, usageSummaryText(usageSummary.providers.size), Icons.Default.Storage, V2Tab.Settings), - V2ModuleRow("Settings", null, null, Icons.Outlined.Settings, V2Tab.Settings), + V2ModuleRow("Channels", null, channelsSummaryText(channelsSummary), Icons.Default.Notifications, V2Tab.Settings, V2SettingsRoute.Channels), + V2ModuleRow("Agents", null, if (agents.isEmpty()) "Load" else "${agents.size} ready", Icons.Default.Person, V2Tab.Settings, V2SettingsRoute.Agents), + V2ModuleRow("Approvals", null, approvalsSummary(pendingToolCalls.size), Icons.Default.Lock, V2Tab.Settings, V2SettingsRoute.Approvals), + V2ModuleRow("Cron Jobs", null, cronJobsSummary(cronStatus.jobs), Icons.Outlined.AccessTime, V2Tab.Settings, V2SettingsRoute.CronJobs), + V2ModuleRow("Skills", null, skillsSummaryText(skillsSummary.skills), Icons.Default.Settings, V2Tab.Settings, V2SettingsRoute.Skills), + V2ModuleRow("Nodes & Devices", null, nodesDevicesSummaryText(nodesDevicesSummary), Icons.Default.Cloud, V2Tab.Settings, V2SettingsRoute.NodesDevices), + V2ModuleRow("Usage", null, usageSummaryText(usageSummary.providers.size), Icons.Default.Storage, V2Tab.Settings, V2SettingsRoute.Usage), + V2ModuleRow("Settings", null, null, Icons.Outlined.Settings, V2Tab.Settings, V2SettingsRoute.Home), ), onSelectTab = onSelectTab, + onOpenSettingsRoute = onOpenSettingsRoute, ) } @@ -321,6 +356,7 @@ private data class V2ModuleRow( val metadata: String?, val icon: ImageVector, val tab: V2Tab, + val settingsRoute: V2SettingsRoute? = null, ) @Composable @@ -380,11 +416,22 @@ private fun V2SectionLabel( private fun V2ModuleList( rows: List, onSelectTab: (V2Tab) -> Unit, + onOpenSettingsRoute: (V2SettingsRoute) -> Unit, ) { ClawPanel(contentPadding = PaddingValues(horizontal = 8.dp, vertical = 0.dp)) { Column(verticalArrangement = Arrangement.spacedBy(0.dp)) { rows.forEachIndexed { index, row -> - V2ModuleListRow(row = row, onClick = { onSelectTab(row.tab) }) + V2ModuleListRow( + row = row, + onClick = { + val route = row.settingsRoute + if (route == null) { + onSelectTab(row.tab) + } else { + onOpenSettingsRoute(route) + } + }, + ) if (index != rows.lastIndex) { HorizontalDivider(color = ClawTheme.colors.border, thickness = 1.dp) } @@ -539,6 +586,9 @@ private fun V2VoiceShellScreen(viewModel: MainViewModel) { @Composable private fun V2SettingsShellScreen( viewModel: MainViewModel, + route: V2SettingsRoute, + onRouteChange: (V2SettingsRoute) -> Unit, + onRouteBack: () -> Unit, onOpenCommand: () -> Unit, ) { val displayName by viewModel.displayName.collectAsState() @@ -555,7 +605,6 @@ private fun V2SettingsShellScreen( val nodesDevicesSummary by viewModel.nodesDevicesSummary.collectAsState() val channelsSummary by viewModel.channelsSummary.collectAsState() val dreamingSummary by viewModel.dreamingSummary.collectAsState() - var route by rememberSaveable { mutableStateOf(V2SettingsRoute.Home) } LaunchedEffect(isConnected) { if (isConnected) { @@ -570,11 +619,11 @@ private fun V2SettingsShellScreen( } BackHandler(enabled = route != V2SettingsRoute.Home) { - route = V2SettingsRoute.Home + onRouteBack() } if (route != V2SettingsRoute.Home) { - V2SettingsDetailScreen(viewModel = viewModel, route = route, onBack = { route = V2SettingsRoute.Home }) + V2SettingsDetailScreen(viewModel = viewModel, route = route, onBack = onRouteBack) return } @@ -617,7 +666,7 @@ private fun V2SettingsShellScreen( V2SettingsRow("Health", "Diagnostics", Icons.Default.Settings, status = isConnected, route = V2SettingsRoute.Health), V2SettingsRow("About", "Version and update", Icons.Default.Storage, route = V2SettingsRoute.About), ), - onOpen = { route = it }, + onOpen = onRouteChange, ) }