feat: Material You design system rewrite — Phases 1–4

Complete UI/UX rewrite of hermes-mobile with Material You design system.

Phase 1: Color/Theme/Type/Shapes/Spacing/Motion tokens
Phase 2: HermesScaffold, StateViews (skeleton/empty/error), SearchBar, StatusBadge, ToggleRow
Phase 3: Bottom nav + drawer navigation with NavigationController
Phase 4: All 17 screens rewritten with HermesScaffold and Material You patterns

Closes PR #3.
This commit is contained in:
M57 authored and GitHub committed 2026-06-18 16:34:44 +03:00
1 parent f833005bec
commit d90e36974c
29 files changed
+2669 -2377

No files matched your search

@@ -8,6 +8,7 @@ import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
@@ -18,9 +19,17 @@ val LocalThemePreference = compositionLocalOf { ThemePreference.SYSTEM }
/**
* Set to true to opt into Material You dynamic color (Android 12+).
* Off by default — brand identity (Voltage Purple) is the priority.
*
* On API 31+ the app derives its entire colour scheme from the user's
* wallpaper — primary, secondary, tertiary, surfaces, the lot. On older
* devices the [HermesDarkColorScheme] / [HermesLightColorScheme] brand
* palette is used instead.
*
* Semantic status colours (success / warning / error / info) and chat-bubble
* colours are ALWAYS brand-defined regardless of dynamic colour — see
* [Color.kt] and the `LocalHermesStatusColors` provider below.
*/
private const val ENABLE_DYNAMIC_COLOR = false
private const val ENABLE_DYNAMIC_COLOR = true
private val HermesDarkColorScheme =
darkColorScheme(
@@ -33,6 +42,7 @@ private val HermesDarkColorScheme =
secondaryContainer = Color(0xFF3D2F0F),
onSecondaryContainer = HermesAmberLight,
tertiary = HermesAmberLight,
onTertiary = Color.Black,
background = DarkBackground,
onBackground = DarkOnSurface,
surface = DarkSurface,
@@ -40,17 +50,18 @@ private val HermesDarkColorScheme =
surfaceVariant = DarkSurfaceVariant,
onSurfaceVariant = DarkOnSurfaceVariant,
surfaceTint = HermesPurple,
surfaceContainer = DarkSurfaceVariant,
surfaceContainerHigh = DarkSurfaceHigh,
surfaceContainerHighest = DarkSurfaceHigh,
inverseSurface = Color(0xFFE8E6EE),
inverseOnSurface = Color(0xFF1A1A24),
surfaceContainer = DarkSurfaceContainer,
surfaceContainerHigh = DarkSurfaceContainerHigh,
surfaceContainerHighest = DarkSurfaceContainerHighest,
inverseSurface = DarkInverseSurface,
inverseOnSurface = DarkInverseOnSurface,
error = StatusRed,
onError = Color.White,
errorContainer = StatusRedContainer,
onErrorContainer = Color(0xFFFFB4B4),
outline = DarkOutline,
outlineVariant = DarkOutlineVariant,
scrim = DarkScrim,
)
private val HermesLightColorScheme =
@@ -64,6 +75,7 @@ private val HermesLightColorScheme =
secondaryContainer = HermesAmberLight,
onSecondaryContainer = Color(0xFF3D2F0F),
tertiary = HermesAmberDark,
onTertiary = Color.White,
background = LightBackground,
onBackground = LightOnSurface,
surface = LightSurface,
@@ -71,17 +83,18 @@ private val HermesLightColorScheme =
surfaceVariant = LightSurfaceVariant,
onSurfaceVariant = LightOnSurfaceVariant,
surfaceTint = HermesPurple,
surfaceContainer = LightSurfaceVariant,
surfaceContainerHigh = LightSurfaceHigh,
surfaceContainerHighest = LightSurfaceHigh,
inverseSurface = Color(0xFF1A1A24),
inverseOnSurface = Color(0xFFE8E6EE),
surfaceContainer = LightSurfaceContainer,
surfaceContainerHigh = LightSurfaceContainerHigh,
surfaceContainerHighest = LightSurfaceContainerHighest,
inverseSurface = LightInverseSurface,
inverseOnSurface = LightInverseOnSurface,
error = Color(0xFFB3261E),
onError = Color.White,
errorContainer = Color(0xFFF9DEDC),
onErrorContainer = Color(0xFF410E0B),
outline = LightOutline,
outlineVariant = LightOutlineVariant,
scrim = LightScrim,
)
@Composable
@@ -107,10 +120,41 @@ fun HermesControlTheme(
else -> HermesLightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
shapes = HermesShapes,
content = content,
)
val statusColors =
if (darkTheme) {
HermesStatusColors(
success = StatusGreen,
successContainer = StatusGreenContainer,
warning = StatusYellow,
warningContainer = StatusYellowContainer,
error = StatusRed,
errorContainer = StatusRedContainer,
info = StatusBlue,
infoContainer = StatusBlueContainer,
)
} else {
HermesStatusColors(
success = Color(0xFF1B873A),
successContainer = Color(0xFFD7F5E0),
warning = HermesAmberDark,
warningContainer = Color(0xFFFFEAB3),
error = Color(0xFFB3261E),
errorContainer = Color(0xFFF9DEDC),
info = Color(0xFF2E6FBD),
infoContainer = Color(0xFFD4E7FF),
)
}
CompositionLocalProvider(
LocalHermesStatusColors provides statusColors,
LocalSpacing provides SpacingDefaults,
LocalMotion provides MotionDefaults,
) {
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
shapes = HermesShapes,
content = content,
)
}
}