feat(ui): modern theme, sectioned drawer, shared components (v1) (#1)
* feat(ui): modern theme, sectioned drawer, shared components (v1)
Foundation for the modern redesign — fixes the biggest UX problems
(that vague "better ui ux" ticket) without rewriting all 20 screens
at once. This puts the infrastructure in place and modernizes the
highest-traffic screens.
Theme (
- Disable Material You dynamic color by default — brand identity
(Voltage Purple) wins. Can opt back in via HermesControlTheme.
- 3-step elevation ladder (surface / surfaceVariant / surfaceHigh)
with subtle blue tint so dark mode stops reading as flat black.
- Full Material 3 type hierarchy — every style explicitly tuned,
tighter letter spacing, comfortable line heights.
- New Shapes.kt (xs→xl rounded corner tokens) and Spacing.kt
(xs/sm/md/lg/xl/xxl spacing tokens) — no more ad-hoc dp values.
Navigation (
- Sectioned drawer: 17 items were dumped at one indent level with
duplicate icons (Channels & Chat both used the Chat icon!). Now
grouped into 4 logical sections — Converse / Automate / Configure
/ Inspect — with section headers and sane per-item icons.
- Settings added to the drawer (was previously hidden behind
ChatScreen's gear icon — bad IA).
- gesturesEnabled check collapsed from an 18-term OR chain into a
single set-membership test.
Common components (
- HermesScaffold: shared drawer-aware Scaffold + TopAppBar with
slots for refresh / actions. Removes the per-screen duplication
seen in SkillsScreen, CronJobsScreen, ChannelsScreen, etc.
- StateViews: LoadingState / ErrorState / EmptyState / list content
padding tokens — every list screen now renders loading/error/
empty identically instead of ad-hoc per screen.
Modernized screens:
- ConnectScreen: hero brand badge (was a lone "⚡" glyph), pairing
as the primary path, manual token/host/port collapsed behind a
toggle (was always-visible clutter), Card-in-Card nesting removed.
- SkillsScreen: now uses HermesScaffold + EmptyState + ErrorState +
LoadingState. Adds a search field, leading icon per row, and a
category sublabel.
Follow-up commits will migrate the remaining ~15 list screens onto
HermesScaffold; this commit stops at the foundation + the two screens
that see the most daily use.
Verified: ktlint clean across the entire tree.
Co-Authored-By: Ronia <ronia@m57>
* fix(imports): add missing safeDrawingPadding + dp imports
CI caught three compile errors that ktlint doesn't flag (unresolved
references are not lint errors, they're compile errors):
- Navigation.kt:239 — Modifier.safeDrawingPadding() import was
dropped when the file was rewritten for the sectioned drawer.
- SkillsScreen.kt:117 — `16.dp` and `12.dp` references had no
`androidx.compose.ui.unit.dp` import after the file was rewritten
onto HermesScaffold.
No code changes — pure import fixes.
Co-Authored-By: Ronia <ronia@m57>
* ci: split monolithic job into 4 parallel jobs + summary
Before — one job ran ktlint → lint → test → build sequentially. If
ktlint failed you waited ~30s to find out, and the test/build stages
never even started (no useful artifacts uploaded on partial failure).
After — 4 jobs run in parallel on every push and PR:
1. ktlint (fast feedback, ~30s)
2. android-lint (parallel, includes compileDebugKotlin)
3. unit-tests (parallel, JUnit 5 + mockk)
4. build (gated by 1+2+3, only runs if all pass)
Plus a 5th `ci-summary` job that aggregates results so branch
protection only needs a single status check configured.
Other improvements:
- concurrency cancel-in-progress — new commits on the same ref
cancel the stale run instead of piling up.
- timeout-minutes on every job — no more hung runners burning minutes
on a stuck Gradle daemon.
- workflow_dispatch — can trigger manually from the Actions tab.
- Per-job artifact uploads with `if: always()` — reports upload even
when the job fails, so you can download test/lint reports to debug.
- Removed the monolithic "verification-reports" artifact that tried
to upload three unrelated paths and failed silently with "No files
were found" when an early step bailed.
Co-Authored-By: Ronia <ronia@m57>
* ci: silence ktlint artifact-not-found annotation
Adds `if-no-files-found: ignore` to the ktlint artifact upload step
because ktlint 1.2.1 does not produce HTML reports on success — only
on failure. The previous config `if-no-files-found: warn` (default)
emitted a noisy annotation on every green run.
Co-Authored-By: Ronia <ronia@m57>
---------
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
Co-authored-by: Ronia <ronia@m57>
This commit is contained in:
11 files changed
+1013
-548
No files matched your search
@@ -1,26 +1,28 @@
|
||||
package com.m57.hermescontrol
|
||||
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Chat
|
||||
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.CallSplit
|
||||
import androidx.compose.material.icons.filled.Chat
|
||||
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.Dns
|
||||
import androidx.compose.material.icons.filled.Extension
|
||||
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.List
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.ListAlt
|
||||
import androidx.compose.material.icons.filled.Memory
|
||||
import androidx.compose.material.icons.filled.Schedule
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material.icons.filled.Webhook
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -28,11 +30,14 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalDrawerSheet
|
||||
import androidx.compose.material3.ModalNavigationDrawer
|
||||
import androidx.compose.material3.NavigationDrawerItem
|
||||
import androidx.compose.material3.NavigationDrawerItemDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import androidx.navigation3.runtime.entryProvider
|
||||
@@ -61,6 +66,64 @@ 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
|
||||
|
||||
/**
|
||||
* Drawer sections — the navigation soul of the app.
|
||||
*
|
||||
* Each section reflects how the user actually thinks about Hermes:
|
||||
* CONVERSE → talking to the agent
|
||||
* AUTOMATE → scheduled + push-driven actions
|
||||
* CONFIGURE → settings, capabilities, integrations
|
||||
* INSPECT → observability, ops, achievements
|
||||
*/
|
||||
private enum class DrawerSection(val title: String) {
|
||||
CONVERSE("Converse"),
|
||||
AUTOMATE("Automate"),
|
||||
CONFIGURE("Configure"),
|
||||
INSPECT("Inspect"),
|
||||
}
|
||||
|
||||
private data class DrawerEntry(
|
||||
val key: NavKey,
|
||||
val label: String,
|
||||
val icon: ImageVector,
|
||||
val section: DrawerSection,
|
||||
)
|
||||
|
||||
private val DRAWER_ENTRIES =
|
||||
listOf(
|
||||
// Converse
|
||||
DrawerEntry(ChatScreen, "Chat", Icons.AutoMirrored.Filled.Chat, DrawerSection.CONVERSE),
|
||||
DrawerEntry(ProfilesScreen, "Profiles", Icons.Filled.AccountCircle, DrawerSection.CONVERSE),
|
||||
// Automate
|
||||
DrawerEntry(CronJobsScreen, "Cron Jobs", Icons.Filled.Schedule, DrawerSection.AUTOMATE),
|
||||
DrawerEntry(WebhooksScreen, "Webhooks", Icons.Filled.Webhook, DrawerSection.AUTOMATE),
|
||||
DrawerEntry(GatewayScreen, "Gateway", Icons.Filled.Bolt, DrawerSection.AUTOMATE),
|
||||
// Configure
|
||||
DrawerEntry(SkillsScreen, "Skills", Icons.Filled.Extension, 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.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),
|
||||
)
|
||||
|
||||
/**
|
||||
* Screens where swipe-to-open-drawer is allowed.
|
||||
*
|
||||
* Settings is intentionally excluded because it's pushed onto the stack
|
||||
* (uses Back, not drawer); Connect is the entry gate and has no drawer.
|
||||
*/
|
||||
private val DRAWER_GESTURE_SCREENS: Set<NavKey> =
|
||||
DRAWER_ENTRIES.map { it.key }.toSet()
|
||||
|
||||
@Composable
|
||||
fun MainNavigation() {
|
||||
val hasToken = !AuthManager.getToken().isNullOrBlank()
|
||||
@@ -73,16 +136,7 @@ fun MainNavigation() {
|
||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val gesturesEnabled =
|
||||
currentScreen == ChatScreen || currentScreen == SkillsScreen || currentScreen == CronJobsScreen ||
|
||||
currentScreen == GatewayScreen || currentScreen == ProfilesScreen ||
|
||||
currentScreen == ToolsetsScreen || currentScreen == AchievementsScreen ||
|
||||
currentScreen == PairingScreen || currentScreen == ConfigScreen ||
|
||||
currentScreen == McpServersScreen || currentScreen == WebhooksScreen ||
|
||||
currentScreen == ModelScreen || currentScreen == LogsScreen ||
|
||||
currentScreen == PluginsScreen || currentScreen == ChannelsScreen ||
|
||||
currentScreen == KeysScreen || currentScreen == SystemScreen ||
|
||||
currentScreen == KanbanScreen
|
||||
val gesturesEnabled = currentScreen in DRAWER_GESTURE_SCREENS
|
||||
val openDrawer = { scope.launch { drawerState.open() } }
|
||||
|
||||
ModalNavigationDrawer(
|
||||
@@ -93,192 +147,81 @@ fun MainNavigation() {
|
||||
ModalDrawerSheet(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
// Brand header
|
||||
Text(
|
||||
text = "Hermes Control",
|
||||
modifier = Modifier.padding(16.dp),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
text = "⚡ Hermes",
|
||||
modifier = Modifier.padding(start = 20.dp, top = 20.dp, end = 16.dp, bottom = 4.dp),
|
||||
style =
|
||||
MaterialTheme.typography.headlineSmall.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
HorizontalDivider()
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Chat, contentDescription = null) },
|
||||
label = { Text("Chat") },
|
||||
selected = currentScreen == ChatScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(ChatScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Extension, contentDescription = null) },
|
||||
label = { Text("Skills") },
|
||||
selected = currentScreen == SkillsScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(SkillsScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Schedule, contentDescription = null) },
|
||||
label = { Text("Cron Jobs") },
|
||||
selected = currentScreen == CronJobsScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(CronJobsScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.AccountCircle, contentDescription = null) },
|
||||
label = { Text("Profiles") },
|
||||
selected = currentScreen == ProfilesScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(ProfilesScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Build, contentDescription = null) },
|
||||
label = { Text("Toolsets") },
|
||||
selected = currentScreen == ToolsetsScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(ToolsetsScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Star, contentDescription = null) },
|
||||
label = { Text("Achievements") },
|
||||
selected = currentScreen == AchievementsScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(AchievementsScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Devices, contentDescription = null) },
|
||||
label = { Text("Pairing") },
|
||||
selected = currentScreen == PairingScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(PairingScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Code, contentDescription = null) },
|
||||
label = { Text("Config") },
|
||||
selected = currentScreen == ConfigScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(ConfigScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Dns, contentDescription = null) },
|
||||
label = { Text("MCP Servers") },
|
||||
selected = currentScreen == McpServersScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(McpServersScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.CallSplit, contentDescription = null) },
|
||||
label = { Text("Webhooks") },
|
||||
selected = currentScreen == WebhooksScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(WebhooksScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
Text(
|
||||
text = "AI Agent Control",
|
||||
modifier = Modifier.padding(start = 20.dp, bottom = 12.dp, end = 16.dp),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
|
||||
|
||||
// Render grouped entries
|
||||
for (section in DrawerSection.entries) {
|
||||
Text(
|
||||
text = section.title.uppercase(),
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
start = 20.dp,
|
||||
top = 16.dp,
|
||||
bottom = 6.dp,
|
||||
end = 16.dp,
|
||||
),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
DRAWER_ENTRIES
|
||||
.filter { it.section == section }
|
||||
.forEach { entry ->
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(entry.icon, contentDescription = null) },
|
||||
label = { Text(entry.label) },
|
||||
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 = 12.dp, vertical = 2.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
|
||||
// Settings is always at the bottom — not a section sibling
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Settings, contentDescription = null) },
|
||||
label = { Text("Models") },
|
||||
selected = currentScreen == ModelScreen,
|
||||
label = { Text("Settings") },
|
||||
selected = currentScreen == SettingsScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(ModelScreen)
|
||||
NavigationController.navigateTo(SettingsScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.PlayArrow, contentDescription = null) },
|
||||
label = { Text("Gateway") },
|
||||
selected = currentScreen == GatewayScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(GatewayScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.List, contentDescription = null) },
|
||||
label = { Text("Logs") },
|
||||
selected = currentScreen == LogsScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(LogsScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Extension, contentDescription = null) },
|
||||
label = { Text("Plugins") },
|
||||
selected = currentScreen == PluginsScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(PluginsScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Chat, contentDescription = null) },
|
||||
label = { Text("Channels") },
|
||||
selected = currentScreen == ChannelsScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(ChannelsScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Key, contentDescription = null) },
|
||||
label = { Text("Keys") },
|
||||
selected = currentScreen == KeysScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(KeysScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Info, contentDescription = null) },
|
||||
label = { Text("System") },
|
||||
selected = currentScreen == SystemScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(SystemScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Filled.Dashboard, contentDescription = null) },
|
||||
label = { Text("Kanban") },
|
||||
selected = currentScreen == KanbanScreen,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
NavigationController.navigateTo(KanbanScreen)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
colors =
|
||||
NavigationDrawerItemDefaults.colors(
|
||||
selectedContainerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
selectedTextColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user