Refactor: Consolidate navigation items into ScreenRegistry (#310)

* Refactor: Consolidate navigation items into ScreenRegistry

* fix(#310): remove unused imports, fix ktlint import ordering

* feat(#289): add ScreenRegistry for centralized screen configuration

* fix(#310): fix ktlint when-condition blank lines in Navigation.kt

* fix(#289): derive availableNavItems from ScreenRegistry

* fix(#310): use addEntryProvider instead of reified entry for dynamic screen registration

* fix(#310): remove double top padding from kanban, channels, achievements screens

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57
2026-06-24 18:19:38 +03:00
committed by GitHub
co-authored by M57
parent c1af989220
commit 740b4fe601
6 changed files with 225 additions and 323 deletions
@@ -1,6 +1,5 @@
package com.m57.hermescontrol package com.m57.hermescontrol
import androidx.annotation.StringRes
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row 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.rememberScrollState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll 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.DrawerValue
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -58,7 +38,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight 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.data.ws.HermesWsClient
import com.m57.hermescontrol.theme.BottomNavDisplayMode import com.m57.hermescontrol.theme.BottomNavDisplayMode
import kotlinx.coroutines.launch 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.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.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<String>): List<ScreenDefinition> =
names.mapNotNull { name -> ScreenRegistry.ALL_SCREENS.firstOrNull { it.key::class.simpleName == name } }
private data class BottomNavItem( private val DRAWER_GESTURE_SCREENS: Set<NavKey> = ScreenRegistry.ALL_SCREENS.mapTo(mutableSetOf()) { it.key }
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<BottomNavItem> =
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<String, NavKey> =
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<String>): List<BottomNavItem> =
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<NavKey> = ALL_NAV_ITEMS.mapTo(mutableSetOf()) { it.key }
private fun appEntryProvider( private fun appEntryProvider(
sessionId: String?, sessionId: String?,
@@ -215,133 +85,10 @@ private fun appEntryProvider(
) )
} }
entry<ChatScreen> { ScreenRegistry.ALL_SCREENS.forEach { screen ->
ChatScreenContent( addEntryProvider(clazz = screen.key::class) {
onOpenDrawer = openDrawer, screen.content(sessionId, openDrawer)
sessionId = sessionId, }
)
}
entry<HistoryScreen> {
HistoryScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<SettingsScreen> {
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<SkillsScreen> {
SkillsScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<CronJobsScreen> {
CronJobsScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<ProfilesScreen> {
ProfilesScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<ToolsetsScreen> {
ToolsetsScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<AchievementsScreen> {
AchievementsScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<PairingScreen> {
PairingScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<ConfigScreen> {
ConfigScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<McpServersScreen> {
McpServersScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<WebhooksScreen> {
WebhooksScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<ModelScreen> {
ModelScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<GatewayScreen> {
GatewayScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<LogsScreen> {
LogsScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<PluginsScreen> {
PluginsScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<ChannelsScreen> {
ChannelsScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<KeysScreen> {
KeysScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<SystemScreen> {
SystemScreenContent(
onOpenDrawer = openDrawer,
)
}
entry<KanbanScreen> {
KanbanScreenContent(
onOpenDrawer = openDrawer,
)
} }
} }
@@ -353,8 +100,6 @@ fun MainNavigation(sessionId: String? = null) {
val backStack = remember { NavBackStack(startScreen) } val backStack = remember { NavBackStack(startScreen) }
LaunchedEffect(startScreen) { LaunchedEffect(startScreen) {
// When start screen changes (e.g., logout → LandingScreen, or
// fresh login → ChatScreen), reset the backstack atomically.
if (backStack.lastOrNull() != startScreen) { if (backStack.lastOrNull() != startScreen) {
backStack.clear() backStack.clear()
backStack.add(startScreen) backStack.add(startScreen)
@@ -366,18 +111,11 @@ fun MainNavigation(sessionId: String? = null) {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope() 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 bottomNavItemsState by AuthManager.bottomNavItemsFlow.collectAsState()
val bottomNavDisplayMode by AuthManager.bottomNavDisplayModeFlow.collectAsState() val bottomNavDisplayMode by AuthManager.bottomNavDisplayModeFlow.collectAsState()
val bottomNavItems = resolveBottomNavItems(bottomNavItemsState) val bottomNavItems = resolveBottomNavItems(bottomNavItemsState)
val bottomNavKeys = remember(bottomNavItems) { bottomNavItems.mapTo(mutableSetOf()) { it.key } } val bottomNavKeys = remember(bottomNavItems) { bottomNavItems.mapTo(mutableSetOf()) { it.key } }
val drawerEntriesBySection = remember { DRAWER_ENTRIES.groupBy { it.section } }
// Sync primary screens to NavigationController
LaunchedEffect(bottomNavKeys) { LaunchedEffect(bottomNavKeys) {
NavigationController.updatePrimaryScreens(bottomNavKeys) NavigationController.updatePrimaryScreens(bottomNavKeys)
} }
@@ -396,19 +134,16 @@ fun MainNavigation(sessionId: String? = null) {
ModalDrawerSheet( ModalDrawerSheet(
modifier = Modifier.verticalScroll(rememberScrollState()), modifier = Modifier.verticalScroll(rememberScrollState()),
) { ) {
// Brand header with connection status
val connectionStatus by HermesWsClient.connectionStatus.collectAsState() val connectionStatus by HermesWsClient.connectionStatus.collectAsState()
val statusColor = val statusColor =
when (connectionStatus) { when (connectionStatus) {
ConnectionStatus.CONNECTED -> Color(0xFF4CAF50) ConnectionStatus.CONNECTED -> Color(0xFF4CAF50)
// green
ConnectionStatus.CONNECTING, ConnectionStatus.CONNECTING,
ConnectionStatus.RECONNECTING, ConnectionStatus.RECONNECTING,
-> Color(0xFFFFC107) -> Color(0xFFFFC107)
// yellow ConnectionStatus.DISCONNECTED -> Color(0xFFF44336)
ConnectionStatus.DISCONNECTED -> Color(0xFFF44336) // red
} }
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -452,31 +187,25 @@ fun MainNavigation(sessionId: String? = null) {
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
) )
drawerEntriesBySection[section]?.forEach { entry -> ScreenRegistry.ALL_SCREENS
NavigationDrawerItem( .filter { it.drawerSection == section }
icon = { Icon(entry.icon, contentDescription = null) }, .forEach { entry ->
label = { Text(stringResource(entry.labelRes)) }, NavigationDrawerItem(
selected = currentScreen == entry.key, icon = { Icon(entry.icon, contentDescription = null) },
onClick = { label = { Text(stringResource(entry.labelRes)) },
scope.launch { drawerState.close() } selected = currentScreen == entry.key,
NavigationController.navigateTo(entry.key) onClick = {
}, scope.launch { drawerState.close() }
colors = NavigationController.navigateTo(entry.key)
NavigationDrawerItemDefaults.colors( },
selectedContainerColor = MaterialTheme.colorScheme.primaryContainer, colors =
selectedTextColor = MaterialTheme.colorScheme.onPrimaryContainer, NavigationDrawerItemDefaults.colors(
selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer, selectedContainerColor = MaterialTheme.colorScheme.primaryContainer,
), selectedTextColor = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
Modifier
.padding(horizontal = 8.dp, vertical = 1.dp)
.testTag(
"drawer_${entry.key::class.simpleName?.lowercase()?.removeSuffix(
"screen",
) ?: ""}",
), ),
) )
} }
} }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@@ -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()
})
},
)
}
@@ -90,7 +90,7 @@ fun AchievementsScreen(
else -> { else -> {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize().padding(paddingValues), modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp), contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
@@ -93,7 +93,7 @@ fun ChannelsScreen(
else -> { else -> {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize().padding(paddingValues), modifier = Modifier.fillMaxSize(),
contentPadding = listContentPadding, contentPadding = listContentPadding,
verticalArrangement = listItemSpacing, verticalArrangement = listItemSpacing,
) { ) {
@@ -116,7 +116,7 @@ fun KanbanScreen(
) )
} else { } else {
Column( Column(
modifier = Modifier.fillMaxSize().padding(paddingValues), modifier = Modifier.fillMaxSize(),
) { ) {
SearchBar( SearchBar(
query = query, query = query,
@@ -2,7 +2,7 @@ package com.m57.hermescontrol.ui.settings
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope 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.local.AuthManager
import com.m57.hermescontrol.data.remote.ApiClient import com.m57.hermescontrol.data.remote.ApiClient
import com.m57.hermescontrol.data.remote.NetworkResult import com.m57.hermescontrol.data.remote.NetworkResult
@@ -163,28 +163,9 @@ class SettingsViewModel : ViewModel() {
/** All screens available for bottom-nav selection (name → display label). */ /** All screens available for bottom-nav selection (name → display label). */
val availableNavItems: List<Pair<String, Int>> = val availableNavItems: List<Pair<String, Int>> =
listOf( ScreenRegistry.ALL_SCREENS.map { screen ->
"ChatScreen" to R.string.screen_chat, screen.key::class.simpleName!! to screen.labelRes
"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,
)
/** Add a nav item to the bottom bar (max 5). Auto-saves. */ /** Add a nav item to the bottom bar (max 5). Auto-saves. */
fun addNavItem(name: String) { fun addNavItem(name: String) {