refactor(#78): extract navigation and drawer labels to string resources

This commit is contained in:
Hy4ri
2026-06-21 11:09:44 +03:00
parent 8efbe02c27
commit c34f6f1e47
2 changed files with 100 additions and 55 deletions
@@ -1,5 +1,6 @@
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
@@ -58,12 +59,14 @@ 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.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.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay import androidx.navigation3.ui.NavDisplay
import com.m57.hermescontrol.R
import com.m57.hermescontrol.data.local.AuthManager import com.m57.hermescontrol.data.local.AuthManager
import com.m57.hermescontrol.data.ws.ConnectionStatus import com.m57.hermescontrol.data.ws.ConnectionStatus
import com.m57.hermescontrol.data.ws.HermesWsClient import com.m57.hermescontrol.data.ws.HermesWsClient
@@ -94,33 +97,33 @@ import com.m57.hermescontrol.ui.webhooks.WebhooksScreen as WebhooksScreenContent
private data class BottomNavItem( private data class BottomNavItem(
val key: NavKey, val key: NavKey,
val label: String, @param:StringRes val labelRes: Int,
val icon: ImageVector, val icon: ImageVector,
) )
/** Master list of ALL screens available for bottom-nav selection. */ /** Master list of ALL screens available for bottom-nav selection. */
private val ALL_NAV_ITEMS: List<BottomNavItem> = private val ALL_NAV_ITEMS: List<BottomNavItem> =
listOf( listOf(
BottomNavItem(ChatScreen, "Chat", Icons.AutoMirrored.Filled.Chat), BottomNavItem(ChatScreen, R.string.screen_chat, Icons.AutoMirrored.Filled.Chat),
BottomNavItem(SkillsScreen, "Skills", Icons.Filled.Extension), BottomNavItem(SkillsScreen, R.string.screen_skills, Icons.Filled.Extension),
BottomNavItem(CronJobsScreen, "Cron", Icons.Filled.Schedule), BottomNavItem(CronJobsScreen, R.string.screen_cron, Icons.Filled.Schedule),
BottomNavItem(SystemScreen, "System", Icons.Filled.Info), BottomNavItem(SystemScreen, R.string.screen_system, Icons.Filled.Info),
BottomNavItem(SettingsScreen, "Settings", Icons.Filled.Settings), BottomNavItem(SettingsScreen, R.string.screen_settings, Icons.Filled.Settings),
BottomNavItem(ProfilesScreen, "Profiles", Icons.Filled.AccountCircle), BottomNavItem(ProfilesScreen, R.string.screen_profiles, Icons.Filled.AccountCircle),
BottomNavItem(WebhooksScreen, "Webhooks", Icons.Filled.Webhook), BottomNavItem(WebhooksScreen, R.string.screen_webhooks, Icons.Filled.Webhook),
BottomNavItem(GatewayScreen, "Gateway", Icons.Filled.Bolt), BottomNavItem(GatewayScreen, R.string.screen_gateway, Icons.Filled.Bolt),
BottomNavItem(ToolsetsScreen, "Toolsets", Icons.Filled.Build), BottomNavItem(ToolsetsScreen, R.string.screen_toolsets, Icons.Filled.Build),
BottomNavItem(PluginsScreen, "Plugins", Icons.Filled.Memory), BottomNavItem(PluginsScreen, R.string.screen_plugins, Icons.Filled.Memory),
BottomNavItem(ConfigScreen, "Config", Icons.Filled.Code), BottomNavItem(ConfigScreen, R.string.screen_config, Icons.Filled.Code),
BottomNavItem(McpServersScreen, "MCP Servers", Icons.Filled.Dashboard), BottomNavItem(McpServersScreen, R.string.screen_mcp_servers, Icons.Filled.Dashboard),
BottomNavItem(ModelScreen, "Models", Icons.Filled.Settings), BottomNavItem(ModelScreen, R.string.screen_models, Icons.Filled.Settings),
BottomNavItem(PairingScreen, "Pairing", Icons.Filled.Devices), BottomNavItem(PairingScreen, R.string.screen_pairing, Icons.Filled.Devices),
BottomNavItem(KeysScreen, "Keys", Icons.Filled.Key), BottomNavItem(KeysScreen, R.string.screen_keys, Icons.Filled.Key),
BottomNavItem(ChannelsScreen, "Channels", Icons.AutoMirrored.Filled.ListAlt), BottomNavItem(ChannelsScreen, R.string.screen_channels, Icons.AutoMirrored.Filled.ListAlt),
BottomNavItem(LogsScreen, "Logs", Icons.Filled.HistoryEdu), BottomNavItem(LogsScreen, R.string.screen_logs, Icons.Filled.HistoryEdu),
BottomNavItem(KanbanScreen, "Kanban", Icons.Filled.Dashboard), BottomNavItem(KanbanScreen, R.string.screen_kanban, Icons.Filled.Dashboard),
BottomNavItem(AchievementsScreen, "Achievements", Icons.Filled.Info), BottomNavItem(AchievementsScreen, R.string.screen_achievements, Icons.Filled.Info),
BottomNavItem(HistoryScreen, "History", Icons.Filled.History), BottomNavItem(HistoryScreen, R.string.screen_history, Icons.Filled.History),
) )
/** Lookup: data-object simple name → NavKey (used by bottom-nav config). */ /** Lookup: data-object simple name → NavKey (used by bottom-nav config). */
@@ -134,17 +137,17 @@ private fun resolveBottomNavItems(names: List<String>): List<BottomNavItem> =
// ── Drawer sections ──────────────────────────────────────────────────── // ── Drawer sections ────────────────────────────────────────────────────
private enum class DrawerSection( private enum class DrawerSection(
val title: String, @param:StringRes val titleRes: Int,
) { ) {
CONVERSE("Converse"), CONVERSE(R.string.nav_drawer_section_converse),
AUTOMATE("Automate"), AUTOMATE(R.string.nav_drawer_section_automate),
CONFIGURE("Configure"), CONFIGURE(R.string.nav_drawer_section_configure),
INSPECT("Inspect"), INSPECT(R.string.nav_drawer_section_inspect),
} }
private data class DrawerEntry( private data class DrawerEntry(
val key: NavKey, val key: NavKey,
val label: String, @param:StringRes val labelRes: Int,
val icon: ImageVector, val icon: ImageVector,
val section: DrawerSection, val section: DrawerSection,
) )
@@ -152,29 +155,34 @@ private data class DrawerEntry(
private val DRAWER_ENTRIES = private val DRAWER_ENTRIES =
listOf( listOf(
// Converse // Converse
DrawerEntry(ChatScreen, "Chat", Icons.AutoMirrored.Filled.Chat, DrawerSection.CONVERSE), DrawerEntry(ChatScreen, R.string.screen_chat, Icons.AutoMirrored.Filled.Chat, DrawerSection.CONVERSE),
DrawerEntry(HistoryScreen, "History", Icons.Filled.History, DrawerSection.CONVERSE), DrawerEntry(HistoryScreen, R.string.screen_history, Icons.Filled.History, DrawerSection.CONVERSE),
DrawerEntry(ProfilesScreen, "Profiles", Icons.Filled.AccountCircle, DrawerSection.CONVERSE), DrawerEntry(ProfilesScreen, R.string.screen_profiles, Icons.Filled.AccountCircle, DrawerSection.CONVERSE),
// Automate // Automate
DrawerEntry(CronJobsScreen, "Cron", Icons.Filled.Schedule, DrawerSection.AUTOMATE), DrawerEntry(CronJobsScreen, R.string.screen_cron, Icons.Filled.Schedule, DrawerSection.AUTOMATE),
DrawerEntry(WebhooksScreen, "Webhooks", Icons.Filled.Webhook, DrawerSection.AUTOMATE), DrawerEntry(WebhooksScreen, R.string.screen_webhooks, Icons.Filled.Webhook, DrawerSection.AUTOMATE),
DrawerEntry(GatewayScreen, "Gateway", Icons.Filled.Bolt, DrawerSection.AUTOMATE), DrawerEntry(GatewayScreen, R.string.screen_gateway, Icons.Filled.Bolt, DrawerSection.AUTOMATE),
// Configure // Configure
DrawerEntry(SkillsScreen, "Skills", Icons.Filled.Extension, DrawerSection.CONFIGURE), DrawerEntry(SkillsScreen, R.string.screen_skills, Icons.Filled.Extension, DrawerSection.CONFIGURE),
DrawerEntry(SettingsScreen, "Settings", Icons.Filled.Settings, DrawerSection.CONFIGURE), DrawerEntry(SettingsScreen, R.string.screen_settings, Icons.Filled.Settings, DrawerSection.CONFIGURE),
DrawerEntry(ToolsetsScreen, "Toolsets", Icons.Filled.Build, DrawerSection.CONFIGURE), DrawerEntry(ToolsetsScreen, R.string.screen_toolsets, Icons.Filled.Build, DrawerSection.CONFIGURE),
DrawerEntry(PluginsScreen, "Plugins", Icons.Filled.Memory, DrawerSection.CONFIGURE), DrawerEntry(PluginsScreen, R.string.screen_plugins, Icons.Filled.Memory, DrawerSection.CONFIGURE),
DrawerEntry(ConfigScreen, "Config", Icons.Filled.Code, DrawerSection.CONFIGURE), DrawerEntry(ConfigScreen, R.string.screen_config, Icons.Filled.Code, DrawerSection.CONFIGURE),
DrawerEntry(McpServersScreen, "MCP Servers", Icons.Filled.Dashboard, DrawerSection.CONFIGURE), DrawerEntry(McpServersScreen, R.string.screen_mcp_servers, Icons.Filled.Dashboard, DrawerSection.CONFIGURE),
DrawerEntry(ModelScreen, "Models", Icons.Filled.Settings, DrawerSection.CONFIGURE), DrawerEntry(ModelScreen, R.string.screen_models, Icons.Filled.Settings, DrawerSection.CONFIGURE),
DrawerEntry(PairingScreen, "Pairing", Icons.Filled.Devices, DrawerSection.CONFIGURE), DrawerEntry(PairingScreen, R.string.screen_pairing, Icons.Filled.Devices, DrawerSection.CONFIGURE),
DrawerEntry(KeysScreen, "Keys", Icons.Filled.Key, DrawerSection.CONFIGURE), DrawerEntry(KeysScreen, R.string.screen_keys, Icons.Filled.Key, DrawerSection.CONFIGURE),
DrawerEntry(ChannelsScreen, "Channels", Icons.AutoMirrored.Filled.ListAlt, DrawerSection.CONFIGURE), DrawerEntry(
ChannelsScreen,
R.string.screen_channels,
Icons.AutoMirrored.Filled.ListAlt,
DrawerSection.CONFIGURE,
),
// Inspect // Inspect
DrawerEntry(SystemScreen, "System", Icons.Filled.Info, DrawerSection.INSPECT), DrawerEntry(SystemScreen, R.string.screen_system, Icons.Filled.Info, DrawerSection.INSPECT),
DrawerEntry(LogsScreen, "Logs", Icons.Filled.HistoryEdu, DrawerSection.INSPECT), DrawerEntry(LogsScreen, R.string.screen_logs, Icons.Filled.HistoryEdu, DrawerSection.INSPECT),
DrawerEntry(KanbanScreen, "Kanban", Icons.Filled.Dashboard, DrawerSection.INSPECT), DrawerEntry(KanbanScreen, R.string.screen_kanban, Icons.Filled.Dashboard, DrawerSection.INSPECT),
DrawerEntry(AchievementsScreen, "Achievements", Icons.Filled.Info, DrawerSection.INSPECT), DrawerEntry(AchievementsScreen, R.string.screen_achievements, Icons.Filled.Info, DrawerSection.INSPECT),
) )
private val DRAWER_GESTURE_SCREENS: Set<NavKey> = ALL_NAV_ITEMS.mapTo(mutableSetOf()) { it.key } private val DRAWER_GESTURE_SCREENS: Set<NavKey> = ALL_NAV_ITEMS.mapTo(mutableSetOf()) { it.key }
@@ -370,7 +378,7 @@ fun MainNavigation(sessionId: String? = null) {
modifier = Modifier.padding(start = 20.dp, top = 20.dp, end = 16.dp, bottom = 4.dp), modifier = Modifier.padding(start = 20.dp, top = 20.dp, end = 16.dp, bottom = 4.dp),
) { ) {
Text( Text(
text = "⚡ Hermes", text = stringResource(R.string.nav_drawer_title),
style = style =
MaterialTheme.typography.headlineSmall.copy( MaterialTheme.typography.headlineSmall.copy(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
@@ -386,7 +394,7 @@ fun MainNavigation(sessionId: String? = null) {
) )
} }
Text( Text(
text = "AI Agent Control", text = stringResource(R.string.nav_drawer_subtitle),
modifier = Modifier.padding(start = 20.dp, bottom = 12.dp, end = 16.dp), modifier = Modifier.padding(start = 20.dp, bottom = 12.dp, end = 16.dp),
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -395,7 +403,7 @@ fun MainNavigation(sessionId: String? = null) {
for (section in DrawerSection.entries) { for (section in DrawerSection.entries) {
Text( Text(
text = section.title.uppercase(), text = stringResource(section.titleRes).uppercase(),
modifier = modifier =
Modifier.padding( Modifier.padding(
start = 20.dp, start = 20.dp,
@@ -412,7 +420,7 @@ fun MainNavigation(sessionId: String? = null) {
.forEach { entry -> .forEach { entry ->
NavigationDrawerItem( NavigationDrawerItem(
icon = { Icon(entry.icon, contentDescription = null) }, icon = { Icon(entry.icon, contentDescription = null) },
label = { Text(entry.label) }, label = { Text(stringResource(entry.labelRes)) },
selected = currentScreen == entry.key, selected = currentScreen == entry.key,
onClick = { onClick = {
scope.launch { drawerState.close() } scope.launch { drawerState.close() }
@@ -427,7 +435,11 @@ fun MainNavigation(sessionId: String? = null) {
modifier = modifier =
Modifier Modifier
.padding(horizontal = 12.dp, vertical = 2.dp) .padding(horizontal = 12.dp, vertical = 2.dp)
.testTag("drawer_${entry.label.lowercase().replace(' ', '_')}"), .testTag(
"drawer_${entry.key::class.simpleName?.lowercase()?.removeSuffix(
"screen",
) ?: ""}",
),
) )
} }
} }
@@ -448,9 +460,12 @@ fun MainNavigation(sessionId: String? = null) {
NavigationBarItem( NavigationBarItem(
selected = currentScreen == item.key, selected = currentScreen == item.key,
onClick = { NavigationController.navigateTo(item.key) }, onClick = { NavigationController.navigateTo(item.key) },
icon = { Icon(item.icon, contentDescription = item.label) }, icon = { Icon(item.icon, contentDescription = stringResource(item.labelRes)) },
label = { Text(item.label) }, label = { Text(stringResource(item.labelRes)) },
modifier = Modifier.testTag("nav_${item.label.lowercase().replace(' ', '_')}"), modifier =
Modifier.testTag(
"nav_${item.key::class.simpleName?.lowercase()?.removeSuffix("screen") ?: ""}",
),
) )
} }
} }
+30
View File
@@ -28,4 +28,34 @@
<!-- General Errors --> <!-- General Errors -->
<string name="error_unknown">Unknown error</string> <string name="error_unknown">Unknown error</string>
<!-- Navigation & Drawer -->
<string name="nav_drawer_title">⚡ Hermes</string>
<string name="nav_drawer_subtitle">AI Agent Control</string>
<string name="nav_drawer_section_converse">Converse</string>
<string name="nav_drawer_section_automate">Automate</string>
<string name="nav_drawer_section_configure">Configure</string>
<string name="nav_drawer_section_inspect">Inspect</string>
<!-- Screen Names -->
<string name="screen_chat">Chat</string>
<string name="screen_skills">Skills</string>
<string name="screen_cron">Cron</string>
<string name="screen_system">System</string>
<string name="screen_settings">Settings</string>
<string name="screen_profiles">Profiles</string>
<string name="screen_webhooks">Webhooks</string>
<string name="screen_gateway">Gateway</string>
<string name="screen_toolsets">Toolsets</string>
<string name="screen_plugins">Plugins</string>
<string name="screen_config">Config</string>
<string name="screen_mcp_servers">MCP Servers</string>
<string name="screen_models">Models</string>
<string name="screen_pairing">Pairing</string>
<string name="screen_keys">Keys</string>
<string name="screen_channels">Channels</string>
<string name="screen_logs">Logs</string>
<string name="screen_kanban">Kanban</string>
<string name="screen_achievements">Achievements</string>
<string name="screen_history">History</string>
</resources> </resources>