From 60c88e29f1ab9235bbc0058d7083084ed02d9469 Mon Sep 17 00:00:00 2001 From: Yasan Ghaffarian Date: Sat, 16 Mar 2024 20:43:26 +0100 Subject: [PATCH] Improve handling material colors & previews --- .../concrete/component/SpacerVertical.kt | 7 -- .../glass/yasan/concrete/component/Switch.kt | 22 ++++-- .../glass/yasan/concrete/component/Text.kt | 21 ++--- .../yasan/concrete/component/TextMono.kt | 14 ++++ .../concrete/component/internal/Preview.kt | 40 ++++++++++ .../component/preference/Preference.kt | 7 +- .../glass/yasan/concrete/theme/Theme.kt | 15 ++-- .../concrete/theme/color/ColorBuilders.kt | 45 ++++++++--- .../yasan/concrete/theme/color/ColorTokens.kt | 5 +- .../yasan/concrete/theme/color/Colors.kt | 79 ++++++++++++++++++- 10 files changed, 208 insertions(+), 47 deletions(-) delete mode 100644 concrete/src/main/kotlin/glass/yasan/concrete/component/SpacerVertical.kt create mode 100644 concrete/src/main/kotlin/glass/yasan/concrete/component/internal/Preview.kt diff --git a/concrete/src/main/kotlin/glass/yasan/concrete/component/SpacerVertical.kt b/concrete/src/main/kotlin/glass/yasan/concrete/component/SpacerVertical.kt deleted file mode 100644 index e5261ff..0000000 --- a/concrete/src/main/kotlin/glass/yasan/concrete/component/SpacerVertical.kt +++ /dev/null @@ -1,7 +0,0 @@ -package glass.yasan.concrete.component - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp - diff --git a/concrete/src/main/kotlin/glass/yasan/concrete/component/Switch.kt b/concrete/src/main/kotlin/glass/yasan/concrete/component/Switch.kt index 322d281..dd3f802 100644 --- a/concrete/src/main/kotlin/glass/yasan/concrete/component/Switch.kt +++ b/concrete/src/main/kotlin/glass/yasan/concrete/component/Switch.kt @@ -3,11 +3,14 @@ package glass.yasan.concrete.component import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.sp +import glass.yasan.concrete.component.internal.ConcretePreviews +import glass.yasan.concrete.component.internal.PreviewTheme import glass.yasan.concrete.component.preference.Preference import glass.yasan.concrete.theme.ConcreteTheme import glass.yasan.spine.compose.foundation.grid @@ -91,25 +94,34 @@ internal enum class SwitchPreviewParams( Disabled( enabled = false, ), + DisabledUnChecked( + enabled = false, + checked = false, + ), } internal class SwitchPreviewParamsProvider : PreviewParameterProvider { override val values: Sequence = SwitchPreviewParams.entries.asSequence() } -@Preview +@ConcretePreviews @Composable internal fun SwitchPreview( @PreviewParameter(SwitchPreviewParamsProvider::class) params: SwitchPreviewParams, ) { - ConcreteTheme { + PreviewTheme { with(params) { + val mutableChecked = remember { + mutableStateOf(checked) + } Switch( title = title, description = description, - checked = checked, - onCheckedChange = {}, + checked = mutableChecked.value, + onCheckedChange = { + mutableChecked.value = it + }, enabled = enabled, ) } diff --git a/concrete/src/main/kotlin/glass/yasan/concrete/component/Text.kt b/concrete/src/main/kotlin/glass/yasan/concrete/component/Text.kt index 6acacad..2e15eb4 100644 --- a/concrete/src/main/kotlin/glass/yasan/concrete/component/Text.kt +++ b/concrete/src/main/kotlin/glass/yasan/concrete/component/Text.kt @@ -1,6 +1,5 @@ package glass.yasan.concrete.component -import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -10,10 +9,11 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.TextUnit -import glass.yasan.spine.compose.type.rubikFamily +import glass.yasan.concrete.component.internal.ConcretePreviews +import glass.yasan.concrete.component.internal.PreviewTheme import glass.yasan.concrete.theme.ConcreteTheme +import glass.yasan.spine.compose.type.rubikFamily import androidx.compose.material3.Text as Material3Text @Composable @@ -54,17 +54,12 @@ public fun Text( ) } -@Preview +@ConcretePreviews @Composable private fun TextPreview() { - ConcreteTheme { - Surface( - color = ConcreteTheme.colors.layer.foreground, - ) { - Text( - text = "Hello World", - color = Color.Black, - ) - } + PreviewTheme { + Text( + text = "Concrete Text", + ) } } \ No newline at end of file diff --git a/concrete/src/main/kotlin/glass/yasan/concrete/component/TextMono.kt b/concrete/src/main/kotlin/glass/yasan/concrete/component/TextMono.kt index b92d5c0..48a8a92 100644 --- a/concrete/src/main/kotlin/glass/yasan/concrete/component/TextMono.kt +++ b/concrete/src/main/kotlin/glass/yasan/concrete/component/TextMono.kt @@ -9,6 +9,9 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit +import glass.yasan.concrete.component.internal.ConcretePreviews +import glass.yasan.concrete.component.internal.PreviewTheme +import glass.yasan.concrete.theme.ConcreteTheme import glass.yasan.spine.compose.type.rubikMonoFamily @Composable @@ -46,4 +49,15 @@ public fun TextMono( maxLines = maxLines, minLines = minLines, ) +} + +@ConcretePreviews +@Composable +private fun TextMonoPreview() { + PreviewTheme { + TextMono( + text = "Concrete Text Mono", + color = ConcreteTheme.colors.content.major, + ) + } } \ No newline at end of file diff --git a/concrete/src/main/kotlin/glass/yasan/concrete/component/internal/Preview.kt b/concrete/src/main/kotlin/glass/yasan/concrete/component/internal/Preview.kt new file mode 100644 index 0000000..96dd26d --- /dev/null +++ b/concrete/src/main/kotlin/glass/yasan/concrete/component/internal/Preview.kt @@ -0,0 +1,40 @@ +package glass.yasan.concrete.component.internal + +import android.content.res.Configuration +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import glass.yasan.concrete.theme.ConcreteTheme +import glass.yasan.concrete.theme.color.darkColors +import glass.yasan.concrete.theme.color.lightColors + +@Composable +internal fun PreviewTheme( + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + val colors = when (!isSystemInDarkTheme()) { + true -> lightColors() + false -> darkColors() + } + ConcreteTheme(colors = colors) { + Surface( + modifier = modifier, + ) { + content() + } + } +} + +@Preview( + name = "Light", + group = "light-mode", +) +@Preview( + name = "Dark", + group = "dark-mode", + uiMode = Configuration.UI_MODE_NIGHT_YES, +) +internal annotation class ConcretePreviews diff --git a/concrete/src/main/kotlin/glass/yasan/concrete/component/preference/Preference.kt b/concrete/src/main/kotlin/glass/yasan/concrete/component/preference/Preference.kt index e2ba88d..817621c 100644 --- a/concrete/src/main/kotlin/glass/yasan/concrete/component/preference/Preference.kt +++ b/concrete/src/main/kotlin/glass/yasan/concrete/component/preference/Preference.kt @@ -9,9 +9,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import glass.yasan.concrete.component.Text +import glass.yasan.concrete.component.internal.ConcretePreviews +import glass.yasan.concrete.component.internal.PreviewTheme import glass.yasan.concrete.theme.ConcreteTheme import glass.yasan.spine.compose.foundation.grid @@ -72,10 +73,10 @@ public fun Preference( // region Preview -@Preview +@ConcretePreviews @Composable private fun PreferenceLayoutPreview() { - ConcreteTheme { + PreviewTheme { Preference( start = { Text( diff --git a/concrete/src/main/kotlin/glass/yasan/concrete/theme/Theme.kt b/concrete/src/main/kotlin/glass/yasan/concrete/theme/Theme.kt index 5a76fbe..dc78845 100644 --- a/concrete/src/main/kotlin/glass/yasan/concrete/theme/Theme.kt +++ b/concrete/src/main/kotlin/glass/yasan/concrete/theme/Theme.kt @@ -1,5 +1,6 @@ package glass.yasan.concrete.theme +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ReadOnlyComposable @@ -18,11 +19,15 @@ public fun ConcreteTheme( val rememberedColors = remember { colors.copy() }.apply { updateColorsFrom(colors) } val rememberedSizes = remember { sizes.copy() }.apply { updateSizesFrom(sizes) } - CompositionLocalProvider( - LocalColors provides rememberedColors, - LocalSizes provides rememberedSizes, - content = content - ) + MaterialTheme( + colorScheme = colors.toMaterial3Colors(), + ) { + CompositionLocalProvider( + LocalColors provides rememberedColors, + LocalSizes provides rememberedSizes, + content = content + ) + } } public object ConcreteTheme { diff --git a/concrete/src/main/kotlin/glass/yasan/concrete/theme/color/ColorBuilders.kt b/concrete/src/main/kotlin/glass/yasan/concrete/theme/color/ColorBuilders.kt index 8793af1..27ec09c 100644 --- a/concrete/src/main/kotlin/glass/yasan/concrete/theme/color/ColorBuilders.kt +++ b/concrete/src/main/kotlin/glass/yasan/concrete/theme/color/ColorBuilders.kt @@ -3,15 +3,24 @@ package glass.yasan.concrete.theme.color import androidx.compose.ui.graphics.Color import glass.yasan.concrete.theme.color.Colors.Content import glass.yasan.concrete.theme.color.Colors.Layer +import glass.yasan.concrete.theme.color.Colors.Theme public fun lightColors( + themePrimary: Color = ColorTokens.themePrimary, + themeSecondary: Color = ColorTokens.themeSecondary, layerForeground: Color = ColorTokens.layerForegroundLight, layerMidground: Color = ColorTokens.layerMidgroundLight, layerBackground: Color = ColorTokens.layerBackgroundLight, - layerBackgroundOpposite: Color = ColorTokens.layerBackgroundOppositeLight, + layerForegroundInverse: Color = ColorTokens.layerForegroundDark, + layerMidgroundInverse: Color = ColorTokens.layerMidgroundDark, + layerBackgroundInverse: Color = ColorTokens.layerBackgroundDark, textTitle: Color = ColorTokens.contentMajorLight, textDescription: Color = ColorTokens.contentMinorLight, ): Colors = Colors( + theme = Theme( + primary = themePrimary, + secondary = themeSecondary, + ), content = Content( major = textTitle, minor = textDescription, @@ -20,24 +29,40 @@ public fun lightColors( foreground = layerForeground, midground = layerMidground, background = layerBackground, - backgroundOpposite = layerBackgroundOpposite, + foregroundInverse = layerForegroundInverse, + midgroundInverse = layerMidgroundInverse, + backgroundInverse = layerBackgroundInverse, ), isLight = true, ) public fun darkColors( + themePrimary: Color = ColorTokens.themePrimary, + themeSecondary: Color = ColorTokens.themeSecondary, layerForeground: Color = ColorTokens.layerForegroundDark, layerMidground: Color = ColorTokens.layerMidgroundDark, layerBackground: Color = ColorTokens.layerBackgroundDark, - layerBackgroundOpposite: Color = ColorTokens.layerBackgroundOppositeDark, + layerForegroundInverse: Color = ColorTokens.layerForegroundLight, + layerMidgroundInverse: Color = ColorTokens.layerMidgroundLight, + layerBackgroundInverse: Color = ColorTokens.layerBackgroundLight, textTitle: Color = ColorTokens.contentMajorDark, textDescription: Color = ColorTokens.contentMinorDark, ): Colors = Colors( - layerForeground = layerForeground, - layerMidground = layerMidground, - layerBackground = layerBackground, - layerBackgroundOpposite = layerBackgroundOpposite, - contentMajor = textTitle, - contentMinor = textDescription, - isLight = false, + theme = Theme( + primary = themePrimary, + secondary = themeSecondary, + ), + content = Content( + major = textTitle, + minor = textDescription, + ), + layer = Layer( + foreground = layerForeground, + midground = layerMidground, + background = layerBackground, + foregroundInverse = layerForegroundInverse, + midgroundInverse = layerMidgroundInverse, + backgroundInverse = layerBackgroundInverse, + ), + isLight = true, ) \ No newline at end of file diff --git a/concrete/src/main/kotlin/glass/yasan/concrete/theme/color/ColorTokens.kt b/concrete/src/main/kotlin/glass/yasan/concrete/theme/color/ColorTokens.kt index 44d6687..31df23a 100644 --- a/concrete/src/main/kotlin/glass/yasan/concrete/theme/color/ColorTokens.kt +++ b/concrete/src/main/kotlin/glass/yasan/concrete/theme/color/ColorTokens.kt @@ -4,11 +4,14 @@ import androidx.compose.ui.graphics.Color internal object ColorTokens { + // Theme + val themePrimary = Color(0xFF006970) + val themeSecondary = Color(0xFF9f410c) + // Light val layerForegroundLight = Color(0xFFFFFFFF) val layerMidgroundLight = Color(0xFFEEEEEE) val layerBackgroundLight = Color(0xFFD8D8D8) - val layerBackgroundOppositeLight = Color(0xFF000000) val contentMajorLight = Color(0xFF212121) val contentMinorLight = Color(0xFF666666) diff --git a/concrete/src/main/kotlin/glass/yasan/concrete/theme/color/Colors.kt b/concrete/src/main/kotlin/glass/yasan/concrete/theme/color/Colors.kt index e42f83e..90a6a96 100644 --- a/concrete/src/main/kotlin/glass/yasan/concrete/theme/color/Colors.kt +++ b/concrete/src/main/kotlin/glass/yasan/concrete/theme/color/Colors.kt @@ -1,26 +1,38 @@ package glass.yasan.concrete.theme.color +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.compositionLocalOf import androidx.compose.ui.graphics.Color +import glass.yasan.spine.compose.util.adaptiveContentColor @Immutable public class Colors( + public var theme: Theme, public var content: Content, public var layer: Layer, public var isLight: Boolean, ) { public constructor( + themePrimary: Color, + themeSecondary: Color, contentMajor: Color, contentMinor: Color, layerForeground: Color, layerMidground: Color, layerBackground: Color, - layerBackgroundOpposite: Color, + layerForegroundInverse: Color, + layerMidgroundInverse: Color, + layerBackgroundInverse: Color, isLight: Boolean, ) : this( + theme = Theme( + primary = themePrimary, + secondary = themeSecondary, + ), content = Content( major = contentMajor, minor = contentMinor, @@ -29,7 +41,9 @@ public class Colors( foreground = layerForeground, midground = layerMidground, background = layerBackground, - backgroundOpposite = layerBackgroundOpposite, + foregroundInverse = layerForegroundInverse, + midgroundInverse = layerMidgroundInverse, + backgroundInverse = layerBackgroundInverse, ), isLight = isLight, ) @@ -41,16 +55,28 @@ public class Colors( public class Layer( public val foreground: Color, + public val foregroundInverse: Color, public val midground: Color, + public val midgroundInverse: Color, public val background: Color, - public val backgroundOpposite: Color, + public val backgroundInverse: Color, + ) + + public class Theme( + public val primary: Color, + public val secondary: Color, + public val containerAlpha: Float = 0.8f, + public val primaryContainer: Color = primary.copy(alpha = containerAlpha), + public val secondaryContainer: Color = secondary.copy(alpha = containerAlpha), ) public fun copy( + theme: Theme = this.theme, content: Content = this.content, layer: Layer = this.layer, isLight: Boolean = this.isLight, ): Colors = Colors( + theme = theme, content = content, layer = layer, isLight = isLight, @@ -59,10 +85,57 @@ public class Colors( public fun updateColorsFrom( other: Colors, ) { + theme = other.theme content = other.content layer = other.layer isLight = other.isLight } + + @Composable + internal fun toMaterial3Colors(): androidx.compose.material3.ColorScheme = + MaterialTheme.colorScheme.copy( + background = layer.midground, + error = Color.Unspecified, + errorContainer = Color.Unspecified, + inverseOnSurface = layer.foreground, + inversePrimary = adaptiveContentColor(theme.primary), + inverseSurface = layer.foregroundInverse, + onBackground = content.minor, + onError = Color.Unspecified, + onErrorContainer = Color.Unspecified, + onPrimary = adaptiveContentColor(theme.primary), + onPrimaryContainer = containerColor(adaptiveContentColor(theme.primaryContainer)), + onSecondary = adaptiveContentColor(theme.secondary), + onSecondaryContainer = containerColor(adaptiveContentColor(theme.secondaryContainer)), + onSurface = content.minor, + onSurfaceVariant = content.minor, + onTertiary = Color.Unspecified, + onTertiaryContainer = Color.Unspecified, + outline = content.minor, + outlineVariant = content.minor, + primary = theme.primary, + primaryContainer = containerColor(theme.primary), + scrim = layer.foreground, + secondary = theme.secondary, + secondaryContainer = containerColor(theme.secondary), + surface = layer.foreground, + surfaceBright = layer.foreground, + surfaceContainer = layer.foreground, + surfaceContainerHigh = layer.foreground, + surfaceContainerHighest = layer.foreground, + surfaceContainerLow = layer.foreground, + surfaceContainerLowest = layer.foreground, + surfaceDim = layer.foreground, + surfaceTint = layer.foreground, + surfaceVariant = layer.foreground, + tertiary = Color.Unspecified, + tertiaryContainer = Color.Unspecified, + ) + + private fun containerColor( + color: Color, + containerAlpha: Float = 0.8f, + ) = color.copy(alpha = containerAlpha) } internal val LocalColors: ProvidableCompositionLocal =