diff --git a/app/src/main/java/com/m57/hermescontrol/Navigation.kt b/app/src/main/java/com/m57/hermescontrol/Navigation.kt index cc2cf976..6ef91ddc 100644 --- a/app/src/main/java/com/m57/hermescontrol/Navigation.kt +++ b/app/src/main/java/com/m57/hermescontrol/Navigation.kt @@ -1,6 +1,5 @@ package com.m57.hermescontrol -import androidx.annotation.StringRes import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -16,25 +15,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.Chat -import androidx.compose.material.icons.automirrored.filled.ListAlt -import androidx.compose.material.icons.filled.AccountCircle -import androidx.compose.material.icons.filled.Bolt -import androidx.compose.material.icons.filled.Build -import androidx.compose.material.icons.filled.Code -import androidx.compose.material.icons.filled.Dashboard -import androidx.compose.material.icons.filled.Devices -import androidx.compose.material.icons.filled.Extension -import androidx.compose.material.icons.filled.History -import androidx.compose.material.icons.filled.HistoryEdu -import androidx.compose.material.icons.filled.Info -import androidx.compose.material.icons.filled.Key -import androidx.compose.material.icons.filled.Memory -import androidx.compose.material.icons.filled.Psychology -import androidx.compose.material.icons.filled.Schedule -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material.icons.filled.Webhook import androidx.compose.material3.DrawerValue import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -58,7 +38,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -72,122 +51,13 @@ import com.m57.hermescontrol.data.ws.ConnectionStatus import com.m57.hermescontrol.data.ws.HermesWsClient import com.m57.hermescontrol.theme.BottomNavDisplayMode import kotlinx.coroutines.launch -import com.m57.hermescontrol.ui.achievements.AchievementsScreen as AchievementsScreenContent import com.m57.hermescontrol.ui.authlogin.AuthLoginScreen as AuthLoginScreenContent -import com.m57.hermescontrol.ui.channels.ChannelsScreen as ChannelsScreenContent -import com.m57.hermescontrol.ui.chat.ChatScreen as ChatScreenContent -import com.m57.hermescontrol.ui.config.ConfigScreen as ConfigScreenContent -import com.m57.hermescontrol.ui.cron.CronJobsScreen as CronJobsScreenContent -import com.m57.hermescontrol.ui.gateway.GatewayScreen as GatewayScreenContent -import com.m57.hermescontrol.ui.kanban.KanbanScreen as KanbanScreenContent -import com.m57.hermescontrol.ui.keys.KeysScreen as KeysScreenContent import com.m57.hermescontrol.ui.landing.LandingScreen as LandingScreenContent -import com.m57.hermescontrol.ui.logs.LogsScreen as LogsScreenContent -import com.m57.hermescontrol.ui.mcp.McpServersScreen as McpServersScreenContent -import com.m57.hermescontrol.ui.model.ModelScreen as ModelScreenContent -import com.m57.hermescontrol.ui.pairing.PairingScreen as PairingScreenContent -import com.m57.hermescontrol.ui.plugins.PluginsScreen as PluginsScreenContent -import com.m57.hermescontrol.ui.profiles.ProfilesScreen as ProfilesScreenContent -import com.m57.hermescontrol.ui.sessions.SessionsScreen as HistoryScreenContent -import com.m57.hermescontrol.ui.settings.SettingsScreen as SettingsScreenContent -import com.m57.hermescontrol.ui.skills.SkillsScreen as SkillsScreenContent -import com.m57.hermescontrol.ui.system.SystemScreen as SystemScreenContent -import com.m57.hermescontrol.ui.toolsets.ToolsetsScreen as ToolsetsScreenContent -import com.m57.hermescontrol.ui.webhooks.WebhooksScreen as WebhooksScreenContent -// ── Bottom-nav item model ────────────────────────────────────────────── +private fun resolveBottomNavItems(names: List): List = + names.mapNotNull { name -> ScreenRegistry.ALL_SCREENS.firstOrNull { it.key::class.simpleName == name } } -private data class BottomNavItem( - val key: NavKey, - @param:StringRes val labelRes: Int, - val icon: ImageVector, -) - -/** Master list of ALL screens available for bottom-nav selection. */ -private val ALL_NAV_ITEMS: List = - listOf( - BottomNavItem(ChatScreen, R.string.screen_chat, Icons.AutoMirrored.Filled.Chat), - BottomNavItem(SkillsScreen, R.string.screen_skills, Icons.Filled.Extension), - BottomNavItem(CronJobsScreen, R.string.screen_cron, Icons.Filled.Schedule), - BottomNavItem(SystemScreen, R.string.screen_system, Icons.Filled.Info), - BottomNavItem(SettingsScreen, R.string.screen_settings, Icons.Filled.Settings), - BottomNavItem(ProfilesScreen, R.string.screen_profiles, Icons.Filled.AccountCircle), - BottomNavItem(WebhooksScreen, R.string.screen_webhooks, Icons.Filled.Webhook), - BottomNavItem(GatewayScreen, R.string.screen_gateway, Icons.Filled.Bolt), - BottomNavItem(ToolsetsScreen, R.string.screen_toolsets, Icons.Filled.Build), - BottomNavItem(PluginsScreen, R.string.screen_plugins, Icons.Filled.Memory), - BottomNavItem(ConfigScreen, R.string.screen_config, Icons.Filled.Code), - BottomNavItem(McpServersScreen, R.string.screen_mcp_servers, Icons.Filled.Dashboard), - BottomNavItem(ModelScreen, R.string.screen_models, Icons.Filled.Psychology), - BottomNavItem(PairingScreen, R.string.screen_pairing, Icons.Filled.Devices), - BottomNavItem(KeysScreen, R.string.screen_keys, Icons.Filled.Key), - BottomNavItem(ChannelsScreen, R.string.screen_channels, Icons.AutoMirrored.Filled.ListAlt), - BottomNavItem(LogsScreen, R.string.screen_logs, Icons.Filled.HistoryEdu), - BottomNavItem(KanbanScreen, R.string.screen_kanban, Icons.Filled.Dashboard), - BottomNavItem(AchievementsScreen, R.string.screen_achievements, Icons.Filled.Info), - BottomNavItem(HistoryScreen, R.string.screen_history, Icons.Filled.History), - ) - -/** Lookup: data-object simple name → NavKey (used by bottom-nav config). */ -private val NAV_KEY_BY_NAME: Map = - ALL_NAV_ITEMS.mapNotNull { it.key::class.simpleName?.let { name -> name to it.key } }.toMap() - -/** Resolve a persisted list of nav-item names to BottomNavItem instances. */ -private fun resolveBottomNavItems(names: List): List = - names.mapNotNull { name -> NAV_KEY_BY_NAME[name]?.let { key -> ALL_NAV_ITEMS.first { it.key == key } } } - -// ── Drawer sections ──────────────────────────────────────────────────── - -private enum class DrawerSection( - @param:StringRes val titleRes: Int, -) { - CONVERSE(R.string.nav_drawer_section_converse), - AUTOMATE(R.string.nav_drawer_section_automate), - CONFIGURE(R.string.nav_drawer_section_configure), - INSPECT(R.string.nav_drawer_section_inspect), -} - -private data class DrawerEntry( - val key: NavKey, - @param:StringRes val labelRes: Int, - val icon: ImageVector, - val section: DrawerSection, -) - -private val DRAWER_ENTRIES = - listOf( - // Converse - DrawerEntry(ChatScreen, R.string.screen_chat, Icons.AutoMirrored.Filled.Chat, DrawerSection.CONVERSE), - DrawerEntry(HistoryScreen, R.string.screen_history, Icons.Filled.History, DrawerSection.CONVERSE), - DrawerEntry(ProfilesScreen, R.string.screen_profiles, Icons.Filled.AccountCircle, DrawerSection.CONVERSE), - // Automate - DrawerEntry(CronJobsScreen, R.string.screen_cron, Icons.Filled.Schedule, DrawerSection.AUTOMATE), - DrawerEntry(WebhooksScreen, R.string.screen_webhooks, Icons.Filled.Webhook, DrawerSection.AUTOMATE), - DrawerEntry(GatewayScreen, R.string.screen_gateway, Icons.Filled.Bolt, DrawerSection.AUTOMATE), - // Configure - DrawerEntry(SkillsScreen, R.string.screen_skills, Icons.Filled.Extension, DrawerSection.CONFIGURE), - DrawerEntry(ToolsetsScreen, R.string.screen_toolsets, Icons.Filled.Build, DrawerSection.CONFIGURE), - DrawerEntry(PluginsScreen, R.string.screen_plugins, Icons.Filled.Memory, DrawerSection.CONFIGURE), - DrawerEntry(ConfigScreen, R.string.screen_config, Icons.Filled.Code, DrawerSection.CONFIGURE), - DrawerEntry(McpServersScreen, R.string.screen_mcp_servers, Icons.Filled.Dashboard, DrawerSection.CONFIGURE), - DrawerEntry(ModelScreen, R.string.screen_models, Icons.Filled.Psychology, DrawerSection.CONFIGURE), - DrawerEntry(PairingScreen, R.string.screen_pairing, Icons.Filled.Devices, DrawerSection.CONFIGURE), - DrawerEntry(KeysScreen, R.string.screen_keys, Icons.Filled.Key, DrawerSection.CONFIGURE), - DrawerEntry( - ChannelsScreen, - R.string.screen_channels, - Icons.AutoMirrored.Filled.ListAlt, - DrawerSection.CONFIGURE, - ), - // Inspect - DrawerEntry(SystemScreen, R.string.screen_system, Icons.Filled.Info, DrawerSection.INSPECT), - DrawerEntry(LogsScreen, R.string.screen_logs, Icons.Filled.HistoryEdu, DrawerSection.INSPECT), - DrawerEntry(KanbanScreen, R.string.screen_kanban, Icons.Filled.Dashboard, DrawerSection.INSPECT), - DrawerEntry(AchievementsScreen, R.string.screen_achievements, Icons.Filled.Info, DrawerSection.INSPECT), - DrawerEntry(SettingsScreen, R.string.screen_settings, Icons.Filled.Settings, DrawerSection.INSPECT), - ) - -private val DRAWER_GESTURE_SCREENS: Set = ALL_NAV_ITEMS.mapTo(mutableSetOf()) { it.key } +private val DRAWER_GESTURE_SCREENS: Set = ScreenRegistry.ALL_SCREENS.mapTo(mutableSetOf()) { it.key } private fun appEntryProvider( sessionId: String?, @@ -215,133 +85,10 @@ private fun appEntryProvider( ) } - entry { - ChatScreenContent( - onOpenDrawer = openDrawer, - sessionId = sessionId, - ) - } - - entry { - HistoryScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - SettingsScreenContent( - onBack = { NavigationController.goBack() }, - onLogout = { - // First clear auth state to trigger token flow - // then reset navigation to landing screen - NavigationController.backStack?.let { stack -> - stack.clear() - stack.add(LandingScreen) - } - }, - ) - } - - entry { - SkillsScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - CronJobsScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - ProfilesScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - ToolsetsScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - AchievementsScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - PairingScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - ConfigScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - McpServersScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - WebhooksScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - ModelScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - GatewayScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - LogsScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - PluginsScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - ChannelsScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - KeysScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - SystemScreenContent( - onOpenDrawer = openDrawer, - ) - } - - entry { - KanbanScreenContent( - onOpenDrawer = openDrawer, - ) + ScreenRegistry.ALL_SCREENS.forEach { screen -> + addEntryProvider(clazz = screen.key::class) { + screen.content(sessionId, openDrawer) + } } } @@ -353,8 +100,6 @@ fun MainNavigation(sessionId: String? = null) { val backStack = remember { NavBackStack(startScreen) } LaunchedEffect(startScreen) { - // When start screen changes (e.g., logout → LandingScreen, or - // fresh login → ChatScreen), reset the backstack atomically. if (backStack.lastOrNull() != startScreen) { backStack.clear() backStack.add(startScreen) @@ -366,18 +111,11 @@ fun MainNavigation(sessionId: String? = null) { val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) val scope = rememberCoroutineScope() - // Read dynamic bottom-nav config. - // AuthManager.bottomNavItemsFlow is a StateFlow that emits updates when the user - // customises items in Settings. Using collectAsState() ensures the NavigationBar - // recomposes and reflects choices instantly. val bottomNavItemsState by AuthManager.bottomNavItemsFlow.collectAsState() val bottomNavDisplayMode by AuthManager.bottomNavDisplayModeFlow.collectAsState() val bottomNavItems = resolveBottomNavItems(bottomNavItemsState) val bottomNavKeys = remember(bottomNavItems) { bottomNavItems.mapTo(mutableSetOf()) { it.key } } - val drawerEntriesBySection = remember { DRAWER_ENTRIES.groupBy { it.section } } - - // Sync primary screens to NavigationController LaunchedEffect(bottomNavKeys) { NavigationController.updatePrimaryScreens(bottomNavKeys) } @@ -396,19 +134,16 @@ fun MainNavigation(sessionId: String? = null) { ModalDrawerSheet( modifier = Modifier.verticalScroll(rememberScrollState()), ) { - // Brand header with connection status val connectionStatus by HermesWsClient.connectionStatus.collectAsState() val statusColor = when (connectionStatus) { ConnectionStatus.CONNECTED -> Color(0xFF4CAF50) - // green ConnectionStatus.CONNECTING, ConnectionStatus.RECONNECTING, -> Color(0xFFFFC107) - // yellow - ConnectionStatus.DISCONNECTED -> Color(0xFFF44336) // red + ConnectionStatus.DISCONNECTED -> Color(0xFFF44336) } Row( verticalAlignment = Alignment.CenterVertically, @@ -452,31 +187,25 @@ fun MainNavigation(sessionId: String? = null) { color = MaterialTheme.colorScheme.onSurfaceVariant, fontWeight = FontWeight.SemiBold, ) - drawerEntriesBySection[section]?.forEach { entry -> - NavigationDrawerItem( - icon = { Icon(entry.icon, contentDescription = null) }, - label = { Text(stringResource(entry.labelRes)) }, - selected = currentScreen == entry.key, - onClick = { - scope.launch { drawerState.close() } - NavigationController.navigateTo(entry.key) - }, - colors = - NavigationDrawerItemDefaults.colors( - selectedContainerColor = MaterialTheme.colorScheme.primaryContainer, - selectedTextColor = MaterialTheme.colorScheme.onPrimaryContainer, - selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer, - ), - modifier = - Modifier - .padding(horizontal = 8.dp, vertical = 1.dp) - .testTag( - "drawer_${entry.key::class.simpleName?.lowercase()?.removeSuffix( - "screen", - ) ?: ""}", + ScreenRegistry.ALL_SCREENS + .filter { it.drawerSection == section } + .forEach { entry -> + NavigationDrawerItem( + icon = { Icon(entry.icon, contentDescription = null) }, + label = { Text(stringResource(entry.labelRes)) }, + selected = currentScreen == entry.key, + onClick = { + scope.launch { drawerState.close() } + NavigationController.navigateTo(entry.key) + }, + colors = + NavigationDrawerItemDefaults.colors( + selectedContainerColor = MaterialTheme.colorScheme.primaryContainer, + selectedTextColor = MaterialTheme.colorScheme.onPrimaryContainer, + selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer, ), - ) - } + ) + } } Spacer(modifier = Modifier.height(8.dp)) diff --git a/app/src/main/java/com/m57/hermescontrol/ScreenRegistry.kt b/app/src/main/java/com/m57/hermescontrol/ScreenRegistry.kt new file mode 100644 index 00000000..acee994d --- /dev/null +++ b/app/src/main/java/com/m57/hermescontrol/ScreenRegistry.kt @@ -0,0 +1,192 @@ +package com.m57.hermescontrol + +import androidx.annotation.StringRes +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Chat +import androidx.compose.material.icons.automirrored.filled.ListAlt +import androidx.compose.material.icons.filled.AccountCircle +import androidx.compose.material.icons.filled.Bolt +import androidx.compose.material.icons.filled.Build +import androidx.compose.material.icons.filled.Code +import androidx.compose.material.icons.filled.Dashboard +import androidx.compose.material.icons.filled.Devices +import androidx.compose.material.icons.filled.Extension +import androidx.compose.material.icons.filled.History +import androidx.compose.material.icons.filled.HistoryEdu +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.Key +import androidx.compose.material.icons.filled.Memory +import androidx.compose.material.icons.filled.Psychology +import androidx.compose.material.icons.filled.Schedule +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.Webhook +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.navigation3.runtime.NavKey +import com.m57.hermescontrol.ui.achievements.AchievementsScreen as AchievementsScreenContent +import com.m57.hermescontrol.ui.channels.ChannelsScreen as ChannelsScreenContent +import com.m57.hermescontrol.ui.chat.ChatScreen as ChatScreenContent +import com.m57.hermescontrol.ui.config.ConfigScreen as ConfigScreenContent +import com.m57.hermescontrol.ui.cron.CronJobsScreen as CronJobsScreenContent +import com.m57.hermescontrol.ui.gateway.GatewayScreen as GatewayScreenContent +import com.m57.hermescontrol.ui.kanban.KanbanScreen as KanbanScreenContent +import com.m57.hermescontrol.ui.keys.KeysScreen as KeysScreenContent +import com.m57.hermescontrol.ui.logs.LogsScreen as LogsScreenContent +import com.m57.hermescontrol.ui.mcp.McpServersScreen as McpServersScreenContent +import com.m57.hermescontrol.ui.model.ModelScreen as ModelScreenContent +import com.m57.hermescontrol.ui.pairing.PairingScreen as PairingScreenContent +import com.m57.hermescontrol.ui.plugins.PluginsScreen as PluginsScreenContent +import com.m57.hermescontrol.ui.profiles.ProfilesScreen as ProfilesScreenContent +import com.m57.hermescontrol.ui.sessions.SessionsScreen as HistoryScreenContent +import com.m57.hermescontrol.ui.settings.SettingsScreen as SettingsScreenContent +import com.m57.hermescontrol.ui.skills.SkillsScreen as SkillsScreenContent +import com.m57.hermescontrol.ui.system.SystemScreen as SystemScreenContent +import com.m57.hermescontrol.ui.toolsets.ToolsetsScreen as ToolsetsScreenContent +import com.m57.hermescontrol.ui.webhooks.WebhooksScreen as WebhooksScreenContent + +enum class DrawerSection( + @param:StringRes val titleRes: Int, +) { + CONVERSE(R.string.nav_drawer_section_converse), + AUTOMATE(R.string.nav_drawer_section_automate), + CONFIGURE(R.string.nav_drawer_section_configure), + INSPECT(R.string.nav_drawer_section_inspect), +} + +data class ScreenDefinition( + val key: NavKey, + @param:StringRes val labelRes: Int, + val icon: ImageVector, + val drawerSection: DrawerSection?, + val content: @Composable (sessionId: String?, openDrawer: () -> Unit) -> Unit, +) + +object ScreenRegistry { + val ALL_SCREENS = + listOf( + ScreenDefinition( + ChatScreen, + R.string.screen_chat, + Icons.AutoMirrored.Filled.Chat, + DrawerSection.CONVERSE, + ) { sessionId, openDrawer -> ChatScreenContent(onOpenDrawer = openDrawer, sessionId = sessionId) }, + ScreenDefinition( + HistoryScreen, + R.string.screen_history, + Icons.Filled.History, + DrawerSection.CONVERSE, + ) { sessionId, openDrawer -> HistoryScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + ProfilesScreen, + R.string.screen_profiles, + Icons.Filled.AccountCircle, + DrawerSection.CONVERSE, + ) { sessionId, openDrawer -> ProfilesScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + CronJobsScreen, + R.string.screen_cron, + Icons.Filled.Schedule, + DrawerSection.AUTOMATE, + ) { sessionId, openDrawer -> CronJobsScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + WebhooksScreen, + R.string.screen_webhooks, + Icons.Filled.Webhook, + DrawerSection.AUTOMATE, + ) { sessionId, openDrawer -> WebhooksScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + GatewayScreen, + R.string.screen_gateway, + Icons.Filled.Bolt, + DrawerSection.AUTOMATE, + ) { sessionId, openDrawer -> GatewayScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + SkillsScreen, + R.string.screen_skills, + Icons.Filled.Extension, + DrawerSection.CONFIGURE, + ) { sessionId, openDrawer -> SkillsScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + ToolsetsScreen, + R.string.screen_toolsets, + Icons.Filled.Build, + DrawerSection.CONFIGURE, + ) { sessionId, openDrawer -> ToolsetsScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + PluginsScreen, + R.string.screen_plugins, + Icons.Filled.Memory, + DrawerSection.CONFIGURE, + ) { sessionId, openDrawer -> PluginsScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + ConfigScreen, + R.string.screen_config, + Icons.Filled.Code, + DrawerSection.CONFIGURE, + ) { sessionId, openDrawer -> ConfigScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + McpServersScreen, + R.string.screen_mcp_servers, + Icons.Filled.Dashboard, + DrawerSection.CONFIGURE, + ) { sessionId, openDrawer -> McpServersScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + ModelScreen, + R.string.screen_models, + Icons.Filled.Psychology, + DrawerSection.CONFIGURE, + ) { sessionId, openDrawer -> ModelScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + PairingScreen, + R.string.screen_pairing, + Icons.Filled.Devices, + DrawerSection.CONFIGURE, + ) { sessionId, openDrawer -> PairingScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + KeysScreen, + R.string.screen_keys, + Icons.Filled.Key, + DrawerSection.CONFIGURE, + ) { sessionId, openDrawer -> KeysScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + ChannelsScreen, + R.string.screen_channels, + Icons.AutoMirrored.Filled.ListAlt, + DrawerSection.CONFIGURE, + ) { sessionId, openDrawer -> ChannelsScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + SystemScreen, + R.string.screen_system, + Icons.Filled.Info, + DrawerSection.INSPECT, + ) { sessionId, openDrawer -> SystemScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + LogsScreen, + R.string.screen_logs, + Icons.Filled.HistoryEdu, + DrawerSection.INSPECT, + ) { sessionId, openDrawer -> LogsScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + KanbanScreen, + R.string.screen_kanban, + Icons.Filled.Dashboard, + DrawerSection.INSPECT, + ) { sessionId, openDrawer -> KanbanScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + AchievementsScreen, + R.string.screen_achievements, + Icons.Filled.Info, + DrawerSection.INSPECT, + ) { sessionId, openDrawer -> AchievementsScreenContent(onOpenDrawer = openDrawer) }, + ScreenDefinition( + SettingsScreen, + R.string.screen_settings, + Icons.Filled.Settings, + DrawerSection.INSPECT, + ) { sessionId, openDrawer -> + SettingsScreenContent(onBack = { + NavigationController.goBack() + }) + }, + ) +} 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 1ff57fb2..2812b541 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 @@ -90,7 +90,7 @@ fun AchievementsScreen( else -> { LazyColumn( - modifier = Modifier.fillMaxSize().padding(paddingValues), + modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp), ) { 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 7885d926..a89d688c 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 @@ -93,7 +93,7 @@ fun ChannelsScreen( else -> { LazyColumn( - modifier = Modifier.fillMaxSize().padding(paddingValues), + modifier = Modifier.fillMaxSize(), contentPadding = listContentPadding, verticalArrangement = listItemSpacing, ) { 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 26c43828..88b990fb 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 @@ -116,7 +116,7 @@ fun KanbanScreen( ) } else { Column( - modifier = Modifier.fillMaxSize().padding(paddingValues), + modifier = Modifier.fillMaxSize(), ) { SearchBar( query = query, diff --git a/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt index 2437ad90..dffa5dfd 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt @@ -2,7 +2,7 @@ package com.m57.hermescontrol.ui.settings import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.m57.hermescontrol.R +import com.m57.hermescontrol.ScreenRegistry import com.m57.hermescontrol.data.local.AuthManager import com.m57.hermescontrol.data.remote.ApiClient import com.m57.hermescontrol.data.remote.NetworkResult @@ -163,28 +163,9 @@ class SettingsViewModel : ViewModel() { /** All screens available for bottom-nav selection (name → display label). */ val availableNavItems: List> = - listOf( - "ChatScreen" to R.string.screen_chat, - "SkillsScreen" to R.string.screen_skills, - "CronJobsScreen" to R.string.screen_cron, - "SystemScreen" to R.string.screen_system, - "SettingsScreen" to R.string.screen_settings, - "ProfilesScreen" to R.string.screen_profiles, - "WebhooksScreen" to R.string.screen_webhooks, - "GatewayScreen" to R.string.screen_gateway, - "HistoryScreen" to R.string.screen_history, - "ToolsetsScreen" to R.string.screen_toolsets, - "PluginsScreen" to R.string.screen_plugins, - "ConfigScreen" to R.string.screen_config, - "McpServersScreen" to R.string.screen_mcp_servers, - "ModelScreen" to R.string.screen_models, - "PairingScreen" to R.string.screen_pairing, - "KeysScreen" to R.string.screen_keys, - "ChannelsScreen" to R.string.screen_channels, - "LogsScreen" to R.string.screen_logs, - "KanbanScreen" to R.string.screen_kanban, - "AchievementsScreen" to R.string.screen_achievements, - ) + ScreenRegistry.ALL_SCREENS.map { screen -> + screen.key::class.simpleName!! to screen.labelRes + } /** Add a nav item to the bottom bar (max 5). Auto-saves. */ fun addNavItem(name: String) {