fix(#290): code health — magic colors, flag params, silent catch (#353)

* fix(#290): use LocalHermesStatusColors instead of magic colors (CODE-33)

* fix(#290): replace boolean-flag params with sealed NavIcon class (CODE-37)

* fix(#290): resolve compilation errors — Color import + nullable NavIcon params

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-06-24 21:26:32 +03:00
1 parent cc40658661
commit f190b67ee6
22 files changed
+73 -42

No files matched your search

@@ -50,6 +50,7 @@ import com.m57.hermescontrol.data.local.AuthManager
import com.m57.hermescontrol.data.ws.ConnectionStatus
import com.m57.hermescontrol.data.ws.HermesWsClient
import com.m57.hermescontrol.theme.BottomNavDisplayMode
import com.m57.hermescontrol.theme.LocalHermesStatusColors
import kotlinx.coroutines.launch
import com.m57.hermescontrol.ui.authlogin.AuthLoginScreen as AuthLoginScreenContent
import com.m57.hermescontrol.ui.landing.LandingScreen as LandingScreenContent
@@ -137,13 +138,13 @@ fun MainNavigation(sessionId: String? = null) {
val connectionStatus by HermesWsClient.connectionStatus.collectAsState()
val statusColor =
when (connectionStatus) {
ConnectionStatus.CONNECTED -> Color(0xFF4CAF50)
ConnectionStatus.CONNECTED -> LocalHermesStatusColors.current.success
ConnectionStatus.CONNECTING,
ConnectionStatus.RECONNECTING,
-> Color(0xFFFFC107)
-> LocalHermesStatusColors.current.warning
ConnectionStatus.DISCONNECTED -> Color(0xFFF44336)
ConnectionStatus.DISCONNECTED -> LocalHermesStatusColors.current.error
}
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -36,6 +36,7 @@ import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
@Composable
@@ -62,7 +63,7 @@ fun AchievementsScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_achievements)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadAchievements() },
) { paddingValues ->
@@ -36,6 +36,7 @@ import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
@@ -66,7 +67,7 @@ fun ChannelsScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_channels)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadPlatforms() },
) { paddingValues ->
@@ -109,6 +109,7 @@ import com.m57.hermescontrol.notification.NotificationHelper
import com.m57.hermescontrol.theme.StatusRed
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.NavIcon
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -296,7 +297,7 @@ fun ChatScreen(
}
}
},
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
snackbarHost = {
SnackbarHost(snackbarHostState) { data ->
Snackbar(
@@ -24,28 +24,35 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import com.m57.hermescontrol.R
/** Sealed interface for navigation icon types — eliminates boolean-flag anti-pattern. */
sealed interface NavIcon {
/** Hamburger menu icon that opens the drawer. */
data class Menu(val onOpen: () -> Unit) : NavIcon
/** Back arrow icon. */
data class Back(val onBack: () -> Unit) : NavIcon
}
/**
* Shared Scaffold wrapper that standardizes the TopAppBar.
*
* Improvements (v2):
* - Scroll-aware top bar (pinned collapse on scroll)
* - Uses Spacing tokens internally
* - Simplified API: onOpenDrawer OR showBack (mutually exclusive)
* - Simplified API: [navigationIcon] replaces the old showBack/onBack/onOpenDrawer trio
* - Safe-area aware via Scaffold insets
*
* Screens that still need a Back arrow instead of a Menu icon can set
* `showBack = true` and pass an `onBack` lambda.
* `navigationIcon = NavIcon.Back { … }` instead.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HermesScaffold(
modifier: Modifier = Modifier,
title: @Composable () -> Unit,
onOpenDrawer: (() -> Unit)? = null,
navigationIcon: NavIcon? = null,
onRefresh: (() -> Unit)? = null,
isRefreshing: Boolean = false,
onBack: (() -> Unit)? = null,
showBack: Boolean = false,
pinTopBar: Boolean = false,
snackbarHost: @Composable () -> Unit = {},
actions: @Composable () -> Unit = {},
@@ -66,9 +73,9 @@ fun HermesScaffold(
TopAppBar(
title = title,
navigationIcon = {
when {
showBack && onBack != null -> {
IconButton(onClick = onBack) {
when (val icon = navigationIcon) {
is NavIcon.Back -> {
IconButton(onClick = icon.onBack) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.content_desc_back),
@@ -77,8 +84,8 @@ fun HermesScaffold(
}
}
onOpenDrawer != null -> {
IconButton(onClick = onOpenDrawer) {
is NavIcon.Menu -> {
IconButton(onClick = icon.onOpen) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = stringResource(R.string.content_desc_open_drawer),
@@ -86,6 +93,8 @@ fun HermesScaffold(
)
}
}
null -> { /* no navigation icon */ }
}
},
actions = {
@@ -33,6 +33,7 @@ import com.m57.hermescontrol.R
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.ToastEffect
@OptIn(ExperimentalMaterial3Api::class)
@@ -53,7 +54,7 @@ fun ConfigScreen(
HermesScaffold(
title = { Text(stringResource(R.string.config_screen_title)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadRawConfig() },
) { paddingValues ->
@@ -35,6 +35,7 @@ import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.StatusBadge
import com.m57.hermescontrol.ui.common.StatusBadgeType
import com.m57.hermescontrol.ui.common.ToastEffect
@@ -59,7 +60,7 @@ fun CronJobsScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_cron)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadCronJobs() },
) {
@@ -39,6 +39,7 @@ import com.m57.hermescontrol.R
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.ToastEffect
@OptIn(ExperimentalMaterial3Api::class)
@@ -58,7 +59,7 @@ fun GatewayScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_gateway)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadStatus() },
) { paddingValues ->
@@ -47,6 +47,7 @@ import com.m57.hermescontrol.data.model.KanbanTask
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.ToastEffect
@@ -87,7 +88,7 @@ fun KanbanScreen(
HermesScaffold(
title = { Text(stringResource(R.string.kanban_board_title)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadBoards() },
) { paddingValues ->
@@ -43,6 +43,7 @@ import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
@@ -75,7 +76,7 @@ fun KeysScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_keys)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadKeys() },
) { paddingValues ->
@@ -31,6 +31,7 @@ import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.ToastEffect
@@ -68,7 +69,7 @@ fun LogsScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_logs)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadLogs() },
) {
@@ -38,6 +38,7 @@ import com.m57.hermescontrol.R
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
@@ -71,7 +72,7 @@ fun McpServersScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_mcp_servers)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadServers() },
) { paddingValues ->
@@ -43,6 +43,7 @@ import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.ToastEffect
@@ -76,7 +77,7 @@ fun ModelScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_models)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadModelOptions() },
) { paddingValues ->
@@ -35,6 +35,7 @@ import com.m57.hermescontrol.R
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.ToastEffect
@OptIn(ExperimentalMaterial3Api::class)
@@ -54,7 +55,7 @@ fun PairingScreen(
HermesScaffold(
title = { Text(stringResource(R.string.pairing_screen_title)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadPairing() },
) { paddingValues ->
@@ -37,6 +37,7 @@ import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
@@ -69,7 +70,7 @@ fun PluginsScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_plugins)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadPlugins() },
) { paddingValues ->
@@ -46,6 +46,7 @@ import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.ToastEffect
@OptIn(ExperimentalMaterial3Api::class)
@@ -70,7 +71,7 @@ fun ProfilesScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_profiles)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadProfiles() },
) { paddingValues ->
@@ -38,6 +38,7 @@ import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.listContentPadding
import com.m57.hermescontrol.ui.common.listItemSpacing
@@ -67,7 +68,7 @@ fun SessionsScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_history)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadSessions() },
modifier = modifier,
@@ -69,6 +69,7 @@ import com.m57.hermescontrol.theme.BottomNavDisplayMode
import com.m57.hermescontrol.theme.ThemePreference
import com.m57.hermescontrol.theme.ThemePreset
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.NavIcon
enum class SettingsTab {
CONNECTION,
@@ -93,8 +94,7 @@ fun SettingsScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_settings)) },
showBack = true,
onBack = onBack,
navigationIcon = NavIcon.Back(onBack),
) {
Column(
modifier =
@@ -49,6 +49,7 @@ import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.FilterChipRow
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
@@ -113,7 +114,7 @@ fun SkillsScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_skills)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadSkills() },
) {
@@ -378,14 +379,16 @@ fun SkillEditorDialog(
) {
HermesScaffold(
title = { Text(stringResource(R.string.skills_edit_title, skillName)) },
onBack = {
if (hasChanges) {
showDiscardConfirm = true
} else {
onDismiss()
}
},
showBack = true,
navigationIcon =
NavIcon.Back(
onBack = {
if (hasChanges) {
showDiscardConfirm = true
} else {
onDismiss()
}
},
),
actions = {
if (!isLoading) {
IconButton(
@@ -36,6 +36,7 @@ import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.InfoRow
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SectionHeader
import com.m57.hermescontrol.ui.common.StatCard
import com.m57.hermescontrol.ui.common.StatusBadge
@@ -61,7 +62,7 @@ fun SystemScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_system)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadSystemData() },
) {
@@ -42,6 +42,7 @@ import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
@@ -75,7 +76,7 @@ fun ToolsetsScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_toolsets)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadToolsets() },
) { paddingValues ->
@@ -39,6 +39,7 @@ import com.m57.hermescontrol.R
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
@@ -71,7 +72,7 @@ fun WebhooksScreen(
HermesScaffold(
title = { Text(stringResource(R.string.screen_webhooks)) },
onOpenDrawer = onOpenDrawer,
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadWebhooks() },
) { paddingValues ->