diff --git a/app/src/main/java/com/m57/hermescontrol/ui/achievements/AchievementsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/achievements/AchievementsScreen.kt index c3604b8a..b1f5ac1a 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/achievements/AchievementsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/achievements/AchievementsScreen.kt @@ -45,7 +45,7 @@ fun AchievementsScreen( } HermesScaffold( - title = "Agent Achievements", + title = { Text("Agent Achievements") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadAchievements() }, ) { paddingValues -> diff --git a/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsScreen.kt index ea3f9ec5..18d28b46 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsScreen.kt @@ -59,7 +59,7 @@ fun ChannelsScreen( } HermesScaffold( - title = "Messaging Channels", + title = { Text("Messaging Channels") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadPlatforms() }, ) { paddingValues -> diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt index 3967fe0d..ad4a10a7 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt @@ -40,7 +40,6 @@ import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.History -import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Stop import androidx.compose.material3.AlertDialog @@ -49,7 +48,6 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -57,14 +55,11 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Scaffold import androidx.compose.material3.Snackbar import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -88,8 +83,8 @@ import com.m57.hermescontrol.notification.NotificationHelper import com.m57.hermescontrol.theme.StatusGreen import com.m57.hermescontrol.theme.StatusRed import com.m57.hermescontrol.ui.common.EmptyState +import com.m57.hermescontrol.ui.common.HermesScaffold -@OptIn(ExperimentalMaterial3Api::class) @Composable fun ChatScreen( onNavigateToSettings: () -> Unit, @@ -165,132 +160,29 @@ fun ChatScreen( ), ) - Scaffold( - modifier = modifier.fillMaxSize(), - topBar = { - TopAppBar( - navigationIcon = { - if (onOpenDrawer != null) { - IconButton(onClick = onOpenDrawer) { - Icon( - imageVector = androidx.compose.material.icons.Icons.Filled.Menu, - contentDescription = "Open Drawer", - ) - } - } - }, - title = { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = "Hermes", - style = - MaterialTheme.typography.titleLarge.copy( - fontWeight = FontWeight.Bold, - ), - ) - Spacer(modifier = Modifier.width(8.dp)) - // Connection status dot - Box( - modifier = - Modifier - .size(10.dp) - .clip(CircleShape) - .background(if (state.isConnected) StatusGreen else StatusRed), - ) - } - }, - actions = { - IconButton(onClick = { viewModel.createNewSession() }) { - Icon( - imageVector = Icons.Filled.Add, - contentDescription = "New Chat", - ) - } - - // Session picker - Box { - IconButton(onClick = { - viewModel.loadSessions() - viewModel.toggleSessionPicker() - }) { - Icon( - imageVector = Icons.Filled.History, - contentDescription = "Sessions", - ) - } - - DropdownMenu( - expanded = state.showSessionPicker, - onDismissRequest = { viewModel.toggleSessionPicker() }, - ) { - DropdownMenuItem( - text = { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - Icons.Filled.Add, - contentDescription = null, - modifier = Modifier.size(18.dp), - ) - Spacer(modifier = Modifier.width(8.dp)) - Text("New Session") - } - }, - onClick = { - viewModel.createNewSession() - viewModel.toggleSessionPicker() - }, - ) - if (state.sessions.isNotEmpty()) { - HorizontalDivider() - } - state.sessions.forEach { session -> - DropdownMenuItem( - text = { - Column { - Text( - text = session.title, - style = MaterialTheme.typography.bodyMedium, - maxLines = 1, - ) - Text( - text = "${session.messageCount} messages", - style = - MaterialTheme.typography.labelSmall.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant, - ), - ) - } - }, - onClick = { viewModel.switchSession(session.id) }, - trailingIcon = { - if (session.id == state.currentSessionId) { - Icon( - Icons.Filled.ArrowDropDown, - contentDescription = "Current", - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(18.dp), - ) - } - }, - ) - } - } - } - - // Settings - IconButton(onClick = onNavigateToSettings) { - Icon( - imageVector = Icons.Filled.Settings, - contentDescription = "Settings", - ) - } - }, - colors = - TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - ), - ) + HermesScaffold( + modifier = modifier, + title = { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "Hermes", + style = + MaterialTheme.typography.titleLarge.copy( + fontWeight = FontWeight.Bold, + ), + ) + Spacer(modifier = Modifier.width(8.dp)) + // Connection status dot + Box( + modifier = + Modifier + .size(10.dp) + .clip(CircleShape) + .background(if (state.isConnected) StatusGreen else StatusRed), + ) + } }, + onOpenDrawer = onOpenDrawer, snackbarHost = { SnackbarHost(snackbarHostState) { data -> Snackbar( @@ -300,6 +192,92 @@ fun ChatScreen( ) } }, + actions = { + IconButton(onClick = { viewModel.createNewSession() }) { + Icon( + imageVector = Icons.Filled.Add, + contentDescription = "New Chat", + ) + } + + // Session picker + Box { + IconButton(onClick = { + viewModel.loadSessions() + viewModel.toggleSessionPicker() + }) { + Icon( + imageVector = Icons.Filled.History, + contentDescription = "Sessions", + ) + } + + DropdownMenu( + expanded = state.showSessionPicker, + onDismissRequest = { viewModel.toggleSessionPicker() }, + ) { + DropdownMenuItem( + text = { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + Icons.Filled.Add, + contentDescription = null, + modifier = Modifier.size(18.dp), + ) + Spacer(modifier = Modifier.width(8.dp)) + Text("New Session") + } + }, + onClick = { + viewModel.createNewSession() + viewModel.toggleSessionPicker() + }, + ) + if (state.sessions.isNotEmpty()) { + HorizontalDivider() + } + state.sessions.forEach { session -> + DropdownMenuItem( + text = { + Column { + Text( + text = session.title, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + ) + Text( + text = "${session.messageCount} messages", + style = + MaterialTheme.typography.labelSmall.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant, + ), + ) + } + }, + onClick = { viewModel.switchSession(session.id) }, + trailingIcon = { + if (session.id == state.currentSessionId) { + Icon( + Icons.Filled.ArrowDropDown, + contentDescription = "Current", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(18.dp), + ) + } + }, + ) + } + } + } + + // Settings + IconButton(onClick = onNavigateToSettings) { + Icon( + imageVector = Icons.Filled.Settings, + contentDescription = "Settings", + ) + } + }, ) { paddingValues -> Column( modifier = diff --git a/app/src/main/java/com/m57/hermescontrol/ui/common/HermesScaffold.kt b/app/src/main/java/com/m57/hermescontrol/ui/common/HermesScaffold.kt index 73e219f3..bebc5850 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/common/HermesScaffold.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/common/HermesScaffold.kt @@ -13,7 +13,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -35,21 +34,24 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll @OptIn(ExperimentalMaterial3Api::class) @Composable fun HermesScaffold( - title: String, + modifier: Modifier = Modifier, + title: @Composable () -> Unit, onOpenDrawer: (() -> Unit)? = null, onRefresh: (() -> Unit)? = null, onBack: (() -> Unit)? = null, showBack: Boolean = false, + snackbarHost: @Composable () -> Unit = {}, actions: @Composable () -> Unit = {}, content: @Composable (PaddingValues) -> Unit, ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + snackbarHost = { snackbarHost() }, topBar = { TopAppBar( - title = { Text(title) }, + title = title, navigationIcon = { when { showBack && onBack != null -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt index 878739fe..9b5e6baa 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt @@ -57,7 +57,7 @@ fun ConfigScreen( } HermesScaffold( - title = "Configuration", + title = { Text("Configuration") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadRawConfig() }, ) { paddingValues -> diff --git a/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsScreen.kt index ba228f6a..cefa7164 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsScreen.kt @@ -59,7 +59,7 @@ fun CronJobsScreen( } HermesScaffold( - title = "Cron Jobs", + title = { Text("Cron Jobs") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadCronJobs() }, ) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/gateway/GatewayScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/gateway/GatewayScreen.kt index c7837ca4..0b1d528c 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/gateway/GatewayScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/gateway/GatewayScreen.kt @@ -62,7 +62,7 @@ fun GatewayScreen( } HermesScaffold( - title = "Gateway Control", + title = { Text("Gateway Control") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadStatus() }, ) { paddingValues -> diff --git a/app/src/main/java/com/m57/hermescontrol/ui/kanban/KanbanScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/kanban/KanbanScreen.kt index e0cd3564..640965aa 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/kanban/KanbanScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/kanban/KanbanScreen.kt @@ -71,7 +71,7 @@ fun KanbanScreen( } HermesScaffold( - title = "Kanban Board", + title = { Text("Kanban Board") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadBoards() }, ) { paddingValues -> diff --git a/app/src/main/java/com/m57/hermescontrol/ui/keys/KeysScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/keys/KeysScreen.kt index a889e81e..1f68167a 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/keys/KeysScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/keys/KeysScreen.kt @@ -66,7 +66,7 @@ fun KeysScreen( } HermesScaffold( - title = "Keys & Credentials", + title = { Text("Keys & Credentials") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadKeys() }, ) { paddingValues -> diff --git a/app/src/main/java/com/m57/hermescontrol/ui/logs/LogsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/logs/LogsScreen.kt index 74a542dd..f26401c6 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/logs/LogsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/logs/LogsScreen.kt @@ -59,7 +59,7 @@ fun LogsScreen( } HermesScaffold( - title = "Logs", + title = { Text("Logs") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadLogs() }, ) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/mcp/McpServersScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/mcp/McpServersScreen.kt index 120228bd..acbb3d1f 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/mcp/McpServersScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/mcp/McpServersScreen.kt @@ -59,7 +59,7 @@ fun McpServersScreen( } HermesScaffold( - title = "MCP Servers", + title = { Text("MCP Servers") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadServers() }, ) { paddingValues -> diff --git a/app/src/main/java/com/m57/hermescontrol/ui/model/ModelScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/model/ModelScreen.kt index a28f2f6b..9a6f7742 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/model/ModelScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/model/ModelScreen.kt @@ -66,7 +66,7 @@ fun ModelScreen( } HermesScaffold( - title = "Models", + title = { Text("Models") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadModelOptions() }, ) { paddingValues -> diff --git a/app/src/main/java/com/m57/hermescontrol/ui/pairing/PairingScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/pairing/PairingScreen.kt index 41bb4a86..2eb36e88 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/pairing/PairingScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/pairing/PairingScreen.kt @@ -58,7 +58,7 @@ fun PairingScreen( } HermesScaffold( - title = "Device Pairing", + title = { Text("Device Pairing") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadPairing() }, ) { paddingValues -> diff --git a/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt index c20a766c..51a2aaf4 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt @@ -57,7 +57,7 @@ fun PluginsScreen( } HermesScaffold( - title = "Plugins", + title = { Text("Plugins") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadPlugins() }, ) { paddingValues -> diff --git a/app/src/main/java/com/m57/hermescontrol/ui/profiles/ProfilesScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/profiles/ProfilesScreen.kt index 834d47e8..f830bd76 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/profiles/ProfilesScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/profiles/ProfilesScreen.kt @@ -70,7 +70,7 @@ fun ProfilesScreen( } HermesScaffold( - title = "Profiles", + title = { Text("Profiles") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadProfiles() }, ) { paddingValues -> diff --git a/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsScreen.kt index 5ec81b30..04fc9ab4 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsScreen.kt @@ -67,7 +67,7 @@ fun SettingsScreen( BackHandler(onBack = onBack) HermesScaffold( - title = "Settings", + title = { Text("Settings") }, showBack = true, onBack = onBack, ) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsScreen.kt index d4e3fc97..9cc9f98c 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsScreen.kt @@ -72,7 +72,7 @@ fun SkillsScreen( } HermesScaffold( - title = "Skills", + title = { Text("Skills") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadSkills() }, ) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt index fea37600..96f8bfe9 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt @@ -65,7 +65,7 @@ fun SystemScreen( } HermesScaffold( - title = "System", + title = { Text("System") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadSystemData() }, ) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/toolsets/ToolsetsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/toolsets/ToolsetsScreen.kt index b72a4f38..2288d150 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/toolsets/ToolsetsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/toolsets/ToolsetsScreen.kt @@ -62,7 +62,7 @@ fun ToolsetsScreen( } HermesScaffold( - title = "Toolsets", + title = { Text("Toolsets") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadToolsets() }, ) { paddingValues -> diff --git a/app/src/main/java/com/m57/hermescontrol/ui/webhooks/WebhooksScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/webhooks/WebhooksScreen.kt index 6458324f..c9124cad 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/webhooks/WebhooksScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/webhooks/WebhooksScreen.kt @@ -60,7 +60,7 @@ fun WebhooksScreen( } HermesScaffold( - title = "Webhooks", + title = { Text("Webhooks") }, onOpenDrawer = onOpenDrawer, onRefresh = { viewModel.loadWebhooks() }, ) { paddingValues ->