Files
aiturk-mobil/app/src/main/java/com/m57/hermescontrol/theme/Theme.kt
T
M57 34ba93901b feat(theme): allow dynamic colors to override preset theme and disable preset selector when active (#684)
* feat(theme): allow dynamic colors to override non-default theme presets

* feat(settings): disable theme preset selector when dynamic colors is enabled

* feat(theme): align Catppuccin preset with official Latte and Mocha style guide
2026-07-22 22:49:36 +03:00

128 lines
5.6 KiB
Kotlin

package com.m57.hermescontrol.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.platform.LocalContext
import com.m57.hermescontrol.theme.presets.AmoledDarkColorScheme
import com.m57.hermescontrol.theme.presets.AmoledDarkStatusColors
import com.m57.hermescontrol.theme.presets.AmoledLightColorScheme
import com.m57.hermescontrol.theme.presets.AmoledLightStatusColors
import com.m57.hermescontrol.theme.presets.CatppuccinDarkColorScheme
import com.m57.hermescontrol.theme.presets.CatppuccinDarkStatusColors
import com.m57.hermescontrol.theme.presets.CatppuccinLightColorScheme
import com.m57.hermescontrol.theme.presets.CatppuccinLightStatusColors
import com.m57.hermescontrol.theme.presets.DefaultDarkColorScheme
import com.m57.hermescontrol.theme.presets.DefaultDarkStatusColors
import com.m57.hermescontrol.theme.presets.DefaultLightColorScheme
import com.m57.hermescontrol.theme.presets.DefaultLightStatusColors
import com.m57.hermescontrol.theme.presets.GruvboxDarkColorScheme
import com.m57.hermescontrol.theme.presets.GruvboxDarkStatusColors
import com.m57.hermescontrol.theme.presets.GruvboxLightColorScheme
import com.m57.hermescontrol.theme.presets.GruvboxLightStatusColors
import com.m57.hermescontrol.theme.presets.MonochromeDarkColorScheme
import com.m57.hermescontrol.theme.presets.MonochromeDarkStatusColors
import com.m57.hermescontrol.theme.presets.MonochromeLightColorScheme
import com.m57.hermescontrol.theme.presets.MonochromeLightStatusColors
import com.m57.hermescontrol.theme.presets.NeonNoirDarkColorScheme
import com.m57.hermescontrol.theme.presets.NeonNoirDarkStatusColors
import com.m57.hermescontrol.theme.presets.NeonNoirLightColorScheme
import com.m57.hermescontrol.theme.presets.NeonNoirLightStatusColors
import kotlinx.serialization.Serializable
@Serializable
enum class ThemePreference { SYSTEM, LIGHT, DARK }
@Serializable
enum class ThemePreset { DEFAULT, MONOCHROME, GRUVBOX, CATPPUCCIN, AMOLED, NEON_NOIR }
val LocalThemePreference = compositionLocalOf { ThemePreference.SYSTEM }
val LocalThemePreset = compositionLocalOf { ThemePreset.DEFAULT }
/**
* Resolve the Material 3 [ColorScheme] for a preset + dark flag.
*
* Every preset exports both a dark and light scheme. AMOLED's light scheme is
* an explicit alias of the brand default light scheme.
*/
private fun resolveColorScheme(
preset: ThemePreset,
darkTheme: Boolean,
) = when (preset) {
ThemePreset.DEFAULT -> if (darkTheme) DefaultDarkColorScheme else DefaultLightColorScheme
ThemePreset.MONOCHROME -> if (darkTheme) MonochromeDarkColorScheme else MonochromeLightColorScheme
ThemePreset.GRUVBOX -> if (darkTheme) GruvboxDarkColorScheme else GruvboxLightColorScheme
ThemePreset.CATPPUCCIN -> if (darkTheme) CatppuccinDarkColorScheme else CatppuccinLightColorScheme
ThemePreset.AMOLED -> if (darkTheme) AmoledDarkColorScheme else AmoledLightColorScheme
ThemePreset.NEON_NOIR -> if (darkTheme) NeonNoirDarkColorScheme else NeonNoirLightColorScheme
}
/**
* Resolve the semantic status colors for a preset + dark flag.
*
* Every preset exports both a dark and light status set. MONOCHROME and AMOLED
* alias the brand default status colors (they have no bespoke semantic set).
*/
private fun resolveStatusColors(
preset: ThemePreset,
darkTheme: Boolean,
) = when (preset) {
ThemePreset.DEFAULT -> if (darkTheme) DefaultDarkStatusColors else DefaultLightStatusColors
ThemePreset.MONOCHROME -> if (darkTheme) MonochromeDarkStatusColors else MonochromeLightStatusColors
ThemePreset.GRUVBOX -> if (darkTheme) GruvboxDarkStatusColors else GruvboxLightStatusColors
ThemePreset.CATPPUCCIN -> if (darkTheme) CatppuccinDarkStatusColors else CatppuccinLightStatusColors
ThemePreset.AMOLED -> if (darkTheme) AmoledDarkStatusColors else AmoledLightStatusColors
ThemePreset.NEON_NOIR -> if (darkTheme) NeonNoirDarkStatusColors else NeonNoirLightStatusColors
}
@Composable
fun HermesControlTheme(
themePreference: ThemePreference = LocalThemePreference.current,
useDynamicColors: Boolean = true,
themePreset: ThemePreset = ThemePreset.DEFAULT,
content: @Composable () -> Unit,
) {
val darkTheme =
when (themePreference) {
ThemePreference.SYSTEM -> isSystemInDarkTheme()
ThemePreference.LIGHT -> false
ThemePreference.DARK -> true
}
val context = LocalContext.current
val dynamicAvailable =
useDynamicColors && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colorScheme =
if (dynamicAvailable) {
if (darkTheme) {
dynamicDarkColorScheme(context)
} else {
dynamicLightColorScheme(context)
}
} else {
resolveColorScheme(themePreset, darkTheme)
}
val statusColors = resolveStatusColors(themePreset, darkTheme)
CompositionLocalProvider(
LocalThemePreference provides themePreference,
LocalThemePreset provides themePreset,
LocalHermesStatusColors provides statusColors,
LocalSpacing provides SpacingDefaults,
LocalMotion provides MotionDefaults,
) {
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
shapes = HermesShapes,
content = content,
)
}
}