feat(#11): make HermesScaffold title a @Composable slot, convert ChatScreen (#12)

- HermesScaffold: title now @Composable () -> Unit (M3-idiomatic)
- HermesScaffold: added modifier and snackbarHost parameters
- Updated all 18 callers from title="X" to title={Text("X")}
- ChatScreen: converted from raw Scaffold to HermesScaffold
- Removed unused Menu icon import (ktlint fix)
This commit is contained in:
M57 authored and GitHub committed 2026-06-18 18:43:45 +03:00
1 parent 7c27ca624c
commit d37de1d223
20 files changed
+133 -153

No files matched your search

@@ -45,7 +45,7 @@ fun AchievementsScreen(
}
HermesScaffold(
title = "Agent Achievements",
title = { Text("Agent Achievements") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadAchievements() },
) { paddingValues ->
@@ -59,7 +59,7 @@ fun ChannelsScreen(
}
HermesScaffold(
title = "Messaging Channels",
title = { Text("Messaging Channels") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadPlatforms() },
) { paddingValues ->
@@ -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 =
@@ -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 -> {
@@ -57,7 +57,7 @@ fun ConfigScreen(
}
HermesScaffold(
title = "Configuration",
title = { Text("Configuration") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadRawConfig() },
) { paddingValues ->
@@ -59,7 +59,7 @@ fun CronJobsScreen(
}
HermesScaffold(
title = "Cron Jobs",
title = { Text("Cron Jobs") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadCronJobs() },
) {
@@ -62,7 +62,7 @@ fun GatewayScreen(
}
HermesScaffold(
title = "Gateway Control",
title = { Text("Gateway Control") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadStatus() },
) { paddingValues ->
@@ -71,7 +71,7 @@ fun KanbanScreen(
}
HermesScaffold(
title = "Kanban Board",
title = { Text("Kanban Board") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadBoards() },
) { paddingValues ->
@@ -66,7 +66,7 @@ fun KeysScreen(
}
HermesScaffold(
title = "Keys & Credentials",
title = { Text("Keys & Credentials") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadKeys() },
) { paddingValues ->
@@ -59,7 +59,7 @@ fun LogsScreen(
}
HermesScaffold(
title = "Logs",
title = { Text("Logs") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadLogs() },
) {
@@ -59,7 +59,7 @@ fun McpServersScreen(
}
HermesScaffold(
title = "MCP Servers",
title = { Text("MCP Servers") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadServers() },
) { paddingValues ->
@@ -66,7 +66,7 @@ fun ModelScreen(
}
HermesScaffold(
title = "Models",
title = { Text("Models") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadModelOptions() },
) { paddingValues ->
@@ -58,7 +58,7 @@ fun PairingScreen(
}
HermesScaffold(
title = "Device Pairing",
title = { Text("Device Pairing") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadPairing() },
) { paddingValues ->
@@ -57,7 +57,7 @@ fun PluginsScreen(
}
HermesScaffold(
title = "Plugins",
title = { Text("Plugins") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadPlugins() },
) { paddingValues ->
@@ -70,7 +70,7 @@ fun ProfilesScreen(
}
HermesScaffold(
title = "Profiles",
title = { Text("Profiles") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadProfiles() },
) { paddingValues ->
@@ -67,7 +67,7 @@ fun SettingsScreen(
BackHandler(onBack = onBack)
HermesScaffold(
title = "Settings",
title = { Text("Settings") },
showBack = true,
onBack = onBack,
) {
@@ -72,7 +72,7 @@ fun SkillsScreen(
}
HermesScaffold(
title = "Skills",
title = { Text("Skills") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadSkills() },
) {
@@ -65,7 +65,7 @@ fun SystemScreen(
}
HermesScaffold(
title = "System",
title = { Text("System") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadSystemData() },
) {
@@ -62,7 +62,7 @@ fun ToolsetsScreen(
}
HermesScaffold(
title = "Toolsets",
title = { Text("Toolsets") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadToolsets() },
) { paddingValues ->
@@ -60,7 +60,7 @@ fun WebhooksScreen(
}
HermesScaffold(
title = "Webhooks",
title = { Text("Webhooks") },
onOpenDrawer = onOpenDrawer,
onRefresh = { viewModel.loadWebhooks() },
) { paddingValues ->