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

This commit is contained in:
Hy4ri committed 2026-06-21 11:09:44 +03:00
1 parent 8efbe02c27
commit c34f6f1e47
2 files changed
+100 -55

No files matched your search

@@ -1,5 +1,6 @@
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
@@ -58,12 +59,14 @@ 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
import androidx.compose.ui.unit.dp
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay
import com.m57.hermescontrol.R
import com.m57.hermescontrol.data.local.AuthManager
import com.m57.hermescontrol.data.ws.ConnectionStatus
import com.m57.hermescontrol.data.ws.HermesWsClient
@@ -94,33 +97,33 @@ import com.m57.hermescontrol.ui.webhooks.WebhooksScreen as WebhooksScreenContent
private data class BottomNavItem(
val key: NavKey,
val label: String,
@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, "Chat", Icons.AutoMirrored.Filled.Chat),
BottomNavItem(SkillsScreen, "Skills", Icons.Filled.Extension),
BottomNavItem(CronJobsScreen, "Cron", Icons.Filled.Schedule),
BottomNavItem(SystemScreen, "System", Icons.Filled.Info),
BottomNavItem(SettingsScreen, "Settings", Icons.Filled.Settings),
BottomNavItem(ProfilesScreen, "Profiles", Icons.Filled.AccountCircle),
BottomNavItem(WebhooksScreen, "Webhooks", Icons.Filled.Webhook),
BottomNavItem(GatewayScreen, "Gateway", Icons.Filled.Bolt),
BottomNavItem(ToolsetsScreen, "Toolsets", Icons.Filled.Build),
BottomNavItem(PluginsScreen, "Plugins", Icons.Filled.Memory),
BottomNavItem(ConfigScreen, "Config", Icons.Filled.Code),
BottomNavItem(McpServersScreen, "MCP Servers", Icons.Filled.Dashboard),
BottomNavItem(ModelScreen, "Models", Icons.Filled.Settings),
BottomNavItem(PairingScreen, "Pairing", Icons.Filled.Devices),
BottomNavItem(KeysScreen, "Keys", Icons.Filled.Key),
BottomNavItem(ChannelsScreen, "Channels", Icons.AutoMirrored.Filled.ListAlt),
BottomNavItem(LogsScreen, "Logs", Icons.Filled.HistoryEdu),
BottomNavItem(KanbanScreen, "Kanban", Icons.Filled.Dashboard),
BottomNavItem(AchievementsScreen, "Achievements", Icons.Filled.Info),
BottomNavItem(HistoryScreen, "History", Icons.Filled.History),
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.Settings),
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). */
@@ -134,17 +137,17 @@ private fun resolveBottomNavItems(names: List<String>): List<BottomNavItem> =
// ── Drawer sections ────────────────────────────────────────────────────
private enum class DrawerSection(
val title: String,
@param:StringRes val titleRes: Int,
) {
CONVERSE("Converse"),
AUTOMATE("Automate"),
CONFIGURE("Configure"),
INSPECT("Inspect"),
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,
val label: String,
@param:StringRes val labelRes: Int,
val icon: ImageVector,
val section: DrawerSection,
)
@@ -152,29 +155,34 @@ private data class DrawerEntry(
private val DRAWER_ENTRIES =
listOf(
// Converse
DrawerEntry(ChatScreen, "Chat", Icons.AutoMirrored.Filled.Chat, DrawerSection.CONVERSE),
DrawerEntry(HistoryScreen, "History", Icons.Filled.History, DrawerSection.CONVERSE),
DrawerEntry(ProfilesScreen, "Profiles", Icons.Filled.AccountCircle, DrawerSection.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, "Cron", Icons.Filled.Schedule, DrawerSection.AUTOMATE),
DrawerEntry(WebhooksScreen, "Webhooks", Icons.Filled.Webhook, DrawerSection.AUTOMATE),
DrawerEntry(GatewayScreen, "Gateway", Icons.Filled.Bolt, DrawerSection.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, "Skills", Icons.Filled.Extension, DrawerSection.CONFIGURE),
DrawerEntry(SettingsScreen, "Settings", Icons.Filled.Settings, DrawerSection.CONFIGURE),
DrawerEntry(ToolsetsScreen, "Toolsets", Icons.Filled.Build, DrawerSection.CONFIGURE),
DrawerEntry(PluginsScreen, "Plugins", Icons.Filled.Memory, DrawerSection.CONFIGURE),
DrawerEntry(ConfigScreen, "Config", Icons.Filled.Code, DrawerSection.CONFIGURE),
DrawerEntry(McpServersScreen, "MCP Servers", Icons.Filled.Dashboard, DrawerSection.CONFIGURE),
DrawerEntry(ModelScreen, "Models", Icons.Filled.Settings, DrawerSection.CONFIGURE),
DrawerEntry(PairingScreen, "Pairing", Icons.Filled.Devices, DrawerSection.CONFIGURE),
DrawerEntry(KeysScreen, "Keys", Icons.Filled.Key, DrawerSection.CONFIGURE),
DrawerEntry(ChannelsScreen, "Channels", Icons.AutoMirrored.Filled.ListAlt, DrawerSection.CONFIGURE),
DrawerEntry(SkillsScreen, R.string.screen_skills, Icons.Filled.Extension, DrawerSection.CONFIGURE),
DrawerEntry(SettingsScreen, R.string.screen_settings, Icons.Filled.Settings, 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.Settings, 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, "System", Icons.Filled.Info, DrawerSection.INSPECT),
DrawerEntry(LogsScreen, "Logs", Icons.Filled.HistoryEdu, DrawerSection.INSPECT),
DrawerEntry(KanbanScreen, "Kanban", Icons.Filled.Dashboard, DrawerSection.INSPECT),
DrawerEntry(AchievementsScreen, "Achievements", Icons.Filled.Info, DrawerSection.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),
)
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),
) {
Text(
text = "⚡ Hermes",
text = stringResource(R.string.nav_drawer_title),
style =
MaterialTheme.typography.headlineSmall.copy(
fontWeight = FontWeight.Bold,
@@ -386,7 +394,7 @@ fun MainNavigation(sessionId: String? = null) {
)
}
Text(
text = "AI Agent Control",
text = stringResource(R.string.nav_drawer_subtitle),
modifier = Modifier.padding(start = 20.dp, bottom = 12.dp, end = 16.dp),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -395,7 +403,7 @@ fun MainNavigation(sessionId: String? = null) {
for (section in DrawerSection.entries) {
Text(
text = section.title.uppercase(),
text = stringResource(section.titleRes).uppercase(),
modifier =
Modifier.padding(
start = 20.dp,
@@ -412,7 +420,7 @@ fun MainNavigation(sessionId: String? = null) {
.forEach { entry ->
NavigationDrawerItem(
icon = { Icon(entry.icon, contentDescription = null) },
label = { Text(entry.label) },
label = { Text(stringResource(entry.labelRes)) },
selected = currentScreen == entry.key,
onClick = {
scope.launch { drawerState.close() }
@@ -427,7 +435,11 @@ fun MainNavigation(sessionId: String? = null) {
modifier =
Modifier
.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(
selected = currentScreen == item.key,
onClick = { NavigationController.navigateTo(item.key) },
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) },
modifier = Modifier.testTag("nav_${item.label.lowercase().replace(' ', '_')}"),
icon = { Icon(item.icon, contentDescription = stringResource(item.labelRes)) },
label = { Text(stringResource(item.labelRes)) },
modifier =
Modifier.testTag(
"nav_${item.key::class.simpleName?.lowercase()?.removeSuffix("screen") ?: ""}",
),
)
}
}