ui: oklch colors

This commit is contained in:
Evgeny @ SimpleX Chat
2026-04-20 22:09:38 +00:00
parent 8bad82b680
commit c99d685ae5
7 changed files with 212 additions and 108 deletions
@@ -5,8 +5,39 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.colorspace.ColorSpaces
import chat.simplex.common.views.helpers.mixWith
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sin
/** Create a Display P3 Color from oklch components. H in degrees. */
fun oklch(L: Float, C: Float, H: Float, alpha: Float = 1f): Color {
val hRad = H * (Math.PI.toFloat() / 180f)
val a = C * cos(hRad)
val b = C * sin(hRad)
// oklab → LMS (Ottosson 2021)
val l_ = L + 0.3963377774f * a + 0.2158037573f * b
val m_ = L - 0.1055613458f * a - 0.0638541728f * b
val s_ = L - 0.0894841775f * a - 1.2914855480f * b
val l = l_ * l_ * l_
val m = m_ * m_ * m_
val s = s_ * s_ * s_
// LMS → linear Display P3 (direct, no sRGB clamping)
val r = 3.1281105148f * l - 2.2570749853f * m + 0.1293047593f * s
val g = -1.0911282009f * l + 2.4132668169f * m - 0.3221681599f * s
val bl = -0.0260136845f * l - 0.5080276339f * m + 1.5333166364f * s
// linear P3 → gamma-encoded P3 (same transfer function as sRGB)
fun gammaEncode(x: Float) = if (x >= 0.0031308f) 1.055f * x.pow(1f / 2.4f) - 0.055f else 12.92f * x
return Color(
red = gammaEncode(r.coerceIn(0f, 1f)),
green = gammaEncode(g.coerceIn(0f, 1f)),
blue = gammaEncode(bl.coerceIn(0f, 1f)),
alpha = alpha,
colorSpace = ColorSpaces.DisplayP3
)
}
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
@@ -14,12 +45,12 @@ val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
val Gray = Color(0x22222222)
val Indigo = Color(0xFF9966FF)
val SimplexBlue = Color(0, 136, 255, 255) // If this value changes also need to update #0088ff in string resource files
val SimplexGreen = Color(77, 218, 103, 255)
val SecretColor = Color(0x40808080)
val LightGray = Color(241, 242, 246, 255)
val DarkGray = Color(43, 44, 46, 255)
val HighOrLowlight = Color(139, 135, 134, 255)
val SimplexBlue = oklch(0.6320536f, 0.2017874f, 254.0879f) // If this value changes also need to update #0088ff in string resource files
val SimplexGreen = oklch(0.7871495f, 0.1979258f, 146.6814f)
val SecretColor = oklch(0.5998708f, 0f, 0f, 0.2509804f)
val LightGray = oklch(0.9615242f, 0.005440391f, 274.9652f)
val DarkGray = oklch(0.2928853f, 0.003884885f, 264.5058f)
val HighOrLowlight = oklch(0.6265517f, 0.005036114f, 34.30441f)
val MessagePreviewDark = Color(179, 175, 174, 255)
val MessagePreviewLight = Color(49, 45, 44, 255)
val ToolbarLight = Color(220, 220, 220, 12)
@@ -537,8 +537,8 @@ data class ThemeModeOverride (
preset = wallpaperType.filename,
scale = p?.scale ?: wallpaper?.scale,
scaleType = null,
background = p?.background?.get(base)?.toReadableHex(),
tint = p?.tint?.get(base)?.toReadableHex(),
background = null,
tint = null,
image = null,
imageFile = null,
)
@@ -578,8 +578,14 @@ data class ThemeModeOverride (
preset = wallpaper.preset,
scale = if (wallpaper.scale != w.scale) wallpaper.scale else null,
scaleType = if (wallpaper.scaleType != w.scaleType) wallpaper.scaleType else null,
background = if (wallpaper.background != w.background) wallpaper.background else null,
tint = if (wallpaper.tint != w.tint) wallpaper.tint else null,
background = if (WallpaperType.from(wallpaper) is WallpaperType.Preset) {
val p = PresetWallpaper.from((WallpaperType.from(wallpaper) as WallpaperType.Preset).filename)
if (wallpaper.background?.colorFromReadableHex()?.toArgb() != p?.background?.get(base)?.toArgb()) wallpaper.background else null
} else if (wallpaper.background != w.background) wallpaper.background else null,
tint = if (WallpaperType.from(wallpaper) is WallpaperType.Preset) {
val p = PresetWallpaper.from((WallpaperType.from(wallpaper) as WallpaperType.Preset).filename)
if (wallpaper.tint?.colorFromReadableHex()?.toArgb() != p?.tint?.get(base)?.toArgb()) wallpaper.tint else null
} else if (wallpaper.tint != w.tint) wallpaper.tint else null,
image = wallpaper.image,
imageFile = wallpaper.imageFile,
)
@@ -637,20 +643,20 @@ val DarkColorPalette = darkColors(
secondary = HighOrLowlight,
secondaryVariant = DarkGray,
// background = Color.Black,
surface = Color(0xFF222222),
surface = oklch(0.2519647f, 0f, 0f),
// background = Color(0xFF121212),
error = Color.Red,
onBackground = Color(0xFFFFFBFA),
onSurface = Color(0xFFFFFBFA),
onBackground = oklch(0.9908696f, 0.00447983f, 34.30938f),
onSurface = oklch(0.9908696f, 0.00447983f, 34.30938f),
// onError: Color = Color.Black,
)
val DarkColorPaletteApp = AppColors(
title = SimplexBlue,
primaryVariant2 = Color(0xFF18262E),
sentMessage = Color(0xFF18262E),
sentQuote = Color(0xFF1D3847),
receivedMessage = Color(0xff262627),
receivedQuote = Color(0xff373739),
primaryVariant2 = oklch(0.2596116f, 0.02435695f, 234.0127f),
sentMessage = oklch(0.2596116f, 0.02435695f, 234.0127f),
sentQuote = oklch(0.3262685f, 0.04242656f, 234.4608f),
receivedMessage = oklch(0.2690091f, 0.001828474f, 286.2761f),
receivedQuote = oklch(0.3375424f, 0.003458181f, 286.2129f),
)
val LightColorPalette = lightColors(
@@ -668,57 +674,57 @@ val LightColorPalette = lightColors(
)
val LightColorPaletteApp = AppColors(
title = SimplexBlue,
primaryVariant2 = Color(0xFFE9F7FF),
sentMessage = Color(0xFFE9F7FF),
sentQuote = Color(0xFFD6F0FF),
receivedMessage = Color(0xfff5f5f6),
receivedQuote = Color(0xffececee),
primaryVariant2 = oklch(0.9680794f, 0.0183117f, 232.503f),
sentMessage = oklch(0.9680794f, 0.0183117f, 232.503f),
sentQuote = oklch(0.9407676f, 0.03409827f, 233.0377f),
receivedMessage = oklch(0.9704316f, 0.001324867f, 286.3749f),
receivedQuote = oklch(0.943656f, 0.002669381f, 286.3484f),
)
val SimplexColorPalette = darkColors(
primary = Color(0xFF70F0F9), // If this value changes also need to update #0088ff in string resource files
primaryVariant = Color(0xFF1298A5),
primary = oklch(0.8862563f, 0.113699f, 201.5703f), // If this value changes also need to update #0088ff in string resource files
primaryVariant = oklch(0.6217762f, 0.1034518f, 205.799f),
secondary = HighOrLowlight,
secondaryVariant = Color(0xFF2C464D),
background = Color(0xFF111528),
secondaryVariant = oklch(0.3761328f, 0.03384425f, 216.5493f),
background = oklch(0.2024453f, 0.03849037f, 273.4875f),
// surface = Color.Black,
// background = Color(0xFF121212),
surface = Color(0xFF121C37),
surface = oklch(0.2331016f, 0.05415408f, 266.8904f),
error = Color.Red,
// onBackground = Color(0xFFFFFBFA),
// onSurface = Color(0xFFFFFBFA),
// onError: Color = Color.Black,
)
val SimplexColorPaletteApp = AppColors(
title = Color(0xFF267BE5),
primaryVariant2 = Color(0xFF172941),
sentMessage = Color(0xFF172941),
sentQuote = Color(0xFF1C3A57),
receivedMessage = Color(0xff25283a),
receivedQuote = Color(0xff36394a),
title = oklch(0.5909158f, 0.1793687f, 256.4473f),
primaryVariant2 = oklch(0.2782386f, 0.05068946f, 256.0234f),
sentMessage = oklch(0.2782386f, 0.05068946f, 256.0234f),
sentQuote = oklch(0.3401385f, 0.06268949f, 249.4274f),
receivedMessage = oklch(0.2823021f, 0.03341238f, 276.7299f),
receivedQuote = oklch(0.3488846f, 0.02991734f, 276.9226f),
)
val BlackColorPalette = darkColors(
primary = Color(0xff0077e0), // If this value changes also need to update #0088ff in string resource files
primaryVariant = Color(0xff0077e0),
primary = oklch(0.5737125f, 0.1826329f, 254.0001f), // If this value changes also need to update #0088ff in string resource files
primaryVariant = oklch(0.5737125f, 0.1826329f, 254.0001f),
secondary = HighOrLowlight,
secondaryVariant = DarkGray,
background = Color(0xff070707),
surface = Color(0xff161617),
background = oklch(0.1285578f, 0f, 0f),
surface = oklch(0.200616f, 0.001969079f, 286.2208f),
// background = Color(0xFF121212),
// surface = Color(0xFF121212),
error = Color.Red,
onBackground = Color(0xFFFFFBFA),
onSurface = Color(0xFFFFFBFA),
onBackground = oklch(0.9908696f, 0.00447983f, 34.30938f),
onSurface = oklch(0.9908696f, 0.00447983f, 34.30938f),
// onError: Color = Color.Black,
)
val BlackColorPaletteApp = AppColors(
title = Color(0xff0077e0),
primaryVariant2 = Color(0xff243747),
sentMessage = Color(0xFF18262E),
sentQuote = Color(0xFF1D3847),
receivedMessage = Color(0xff1b1b1b),
receivedQuote = Color(0xff29292b),
title = oklch(0.5737125f, 0.1826329f, 254.0001f),
primaryVariant2 = oklch(0.327876f, 0.03765742f, 244.7111f),
sentMessage = oklch(0.2596116f, 0.02435695f, 234.0127f),
sentQuote = oklch(0.3262685f, 0.04242656f, 234.4608f),
receivedMessage = oklch(0.2221287f, 0f, 0f),
receivedQuote = oklch(0.2817215f, 0.003620283f, 286.165f),
)
var systemInDarkThemeCurrently: Boolean = isInNightMode()
@@ -3,6 +3,8 @@ package chat.simplex.common.ui.theme
import androidx.compose.material.Colors
import androidx.compose.runtime.MutableState
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.colorspace.ColorSpaces
import androidx.compose.ui.graphics.convert
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.text.font.FontFamily
import chat.simplex.common.model.*
@@ -223,6 +225,7 @@ object ThemeManager {
fun String.colorFromReadableHex(): Color =
Color(this.replace("#", "").toLongOrNull(16) ?: Color.White.toArgb().toLong())
.convert(ColorSpaces.DisplayP3)
fun Color.toReadableHex(): String {
val s = Integer.toHexString(toArgb())