android: expressive theme, tonal palette, ShxSpacing/ShxShapes tokens

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DenozordecandClaude Opus 5.5 committed 2026-10-09 23:35:59 +07:00
1 parent c872246b8c
commit fda3ec158c
1 file changed
+88 -58
@@ -13,9 +13,13 @@ import android.content.Context
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.MaterialShapes
import androidx.compose.material3.MotionScheme
import androidx.compose.material3.Shapes
import androidx.compose.material3.Typography
import androidx.compose.material3.toShape
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
@@ -27,6 +31,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
@@ -38,8 +43,41 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
val ConnectionCardRadius = 16.dp
val ScreenPadding = 16.dp
/** Spacing scale; use these instead of ad-hoc .dp literals. */
object ShxSpacing
{
val xs = 4.dp
val sm = 8.dp
val md = 12.dp
val lg = 16.dp
val xl = 24.dp
val xxl = 32.dp
}
/** Shape tokens. [scheme] feeds MaterialTheme.shapes; the rest are named radii. */
object ShxShapes
{
val cardRadius = 16.dp
val scheme = Shapes(
extraSmall = RoundedCornerShape(6.dp),
small = RoundedCornerShape(10.dp),
medium = RoundedCornerShape(14.dp),
large = RoundedCornerShape(16.dp),
extraLarge = RoundedCornerShape(22.dp),
)
/** Scalloped "cookie" used by the hero connect button (Expressive MaterialShapes). */
@ExperimentalMaterial3ExpressiveApi
val hero: Shape
@Composable get() = MaterialShapes.Cookie9Sided.toShape()
}
@Deprecated("Use ShxShapes.cardRadius", ReplaceWith("ShxShapes.cardRadius"))
val ConnectionCardRadius = ShxShapes.cardRadius
@Deprecated("Use ShxSpacing.lg", ReplaceWith("ShxSpacing.lg"))
val ScreenPadding = ShxSpacing.lg
val ProtonPurpleLight = Color(0xFF6D4AFF)
val ProtonPurpleDark = Color(0xFF9C7CFF)
@@ -49,34 +87,34 @@ val SuccessGreenDark = Color(0xFF28A671)
private val LightColors = lightColorScheme(
primary = ProtonPurpleLight,
onPrimary = Color.White,
primaryContainer = Color(0xFFEDE7FF),
onPrimaryContainer = Color(0xFF2E1A7A),
primaryContainer = Color(0xFFE7DFFF),
onPrimaryContainer = Color(0xFF1F0A6B),
inversePrimary = ProtonPurpleDark,
secondary = Color(0xFF625B71),
secondary = Color(0xFF5E5A7D),
onSecondary = Color.White,
secondaryContainer = Color(0xFFE8DEF8),
onSecondaryContainer = Color(0xFF1D192B),
secondaryContainer = Color(0xFFE4E0F9),
onSecondaryContainer = Color(0xFF1A1833),
tertiary = SuccessGreenLight,
onTertiary = Color.White,
tertiaryContainer = Color(0xFFC8F2DA),
onTertiaryContainer = Color(0xFF07391D),
background = Color(0xFFF7F7F8),
onBackground = Color(0xFF1B1B20),
surface = Color(0xFFF7F7F8),
onSurface = Color(0xFF1B1B20),
surfaceVariant = Color(0xFFEBEBEF),
onSurfaceVariant = Color(0xFF57575F),
surfaceDim = Color(0xFFDADAE0),
surfaceBright = Color(0xFFF7F7F8),
surfaceContainerLowest = Color.White,
surfaceContainerLow = Color(0xFFF2F2F4),
surfaceContainer = Color.White,
surfaceContainerHigh = Color(0xFFF2F2F4),
surfaceContainerHighest = Color(0xFFEBEBEF),
outline = Color(0xFF797981),
outlineVariant = Color(0xFFD0D0D6),
inverseSurface = Color(0xFF2F2F35),
inverseOnSurface = Color(0xFFF2F2F4),
background = Color(0xFFFCF8FF),
onBackground = Color(0xFF1C1B20),
surface = Color(0xFFFCF8FF),
onSurface = Color(0xFF1C1B20),
surfaceVariant = Color(0xFFE5E1EC),
onSurfaceVariant = Color(0xFF48454E),
surfaceDim = Color(0xFFDCD8E0),
surfaceBright = Color(0xFFFCF8FF),
surfaceContainerLowest = Color(0xFFFFFFFF),
surfaceContainerLow = Color(0xFFF6F2FA),
surfaceContainer = Color(0xFFF0ECF4),
surfaceContainerHigh = Color(0xFFEAE6EE),
surfaceContainerHighest = Color(0xFFE5E1E9),
outline = Color(0xFF79767F),
outlineVariant = Color(0xFFC9C5D0),
inverseSurface = Color(0xFF313036),
inverseOnSurface = Color(0xFFF3EFF7),
error = Color(0xFFD8303C),
onError = Color.White,
errorContainer = Color(0xFFFFDAD6),
@@ -90,31 +128,31 @@ private val DarkColors = darkColorScheme(
primaryContainer = Color(0xFF3B2C88),
onPrimaryContainer = Color(0xFFEDE7FF),
inversePrimary = ProtonPurpleLight,
secondary = Color(0xFFCCC2DC),
onSecondary = Color(0xFF332D41),
secondaryContainer = Color(0xFF4A4458),
onSecondaryContainer = Color(0xFFE8DEF8),
secondary = Color(0xFFCBC4E8),
onSecondary = Color(0xFF322F4D),
secondaryContainer = Color(0xFF484564),
onSecondaryContainer = Color(0xFFE4E0F9),
tertiary = SuccessGreenDark,
onTertiary = Color(0xFF00391B),
tertiaryContainer = Color(0xFF005228),
onTertiaryContainer = Color(0xFFC8F2DA),
background = Color(0xFF17171A),
onBackground = Color(0xFFE6E6E9),
surface = Color(0xFF17171A),
onSurface = Color(0xFFE6E6E9),
surfaceVariant = Color(0xFF2C2C31),
onSurfaceVariant = Color(0xFFC0C0C8),
surfaceDim = Color(0xFF17171A),
surfaceBright = Color(0xFF3B3B41),
surfaceContainerLowest = Color(0xFF121214),
surfaceContainerLow = Color(0xFF1F1F23),
surfaceContainer = Color(0xFF242428),
surfaceContainerHigh = Color(0xFF2C2C31),
surfaceContainerHighest = Color(0xFF36363B),
outline = Color(0xFF8B8B93),
outlineVariant = Color(0xFF3D3D43),
inverseSurface = Color(0xFFE6E6E9),
inverseOnSurface = Color(0xFF2C2C31),
background = Color(0xFF141318),
onBackground = Color(0xFFE5E1E9),
surface = Color(0xFF141318),
onSurface = Color(0xFFE5E1E9),
surfaceVariant = Color(0xFF48454E),
onSurfaceVariant = Color(0xFFC9C5D0),
surfaceDim = Color(0xFF141318),
surfaceBright = Color(0xFF3A383E),
surfaceContainerLowest = Color(0xFF0F0D13),
surfaceContainerLow = Color(0xFF1C1B20),
surfaceContainer = Color(0xFF201F24),
surfaceContainerHigh = Color(0xFF2B292F),
surfaceContainerHighest = Color(0xFF36343A),
outline = Color(0xFF938F99),
outlineVariant = Color(0xFF48454E),
inverseSurface = Color(0xFFE5E1E9),
inverseOnSurface = Color(0xFF313036),
error = Color(0xFFFF8A8C),
onError = Color(0xFF490005),
errorContainer = Color(0xFF68000E),
@@ -163,14 +201,6 @@ private val DarkStatusColors = ShxStatusColors(
* semantics must stay green/purple/red regardless of the user's wallpaper. */
val LocalShxStatusColors = staticCompositionLocalOf { LightStatusColors }
private val ShxShapes = Shapes(
extraSmall = RoundedCornerShape(6.dp),
small = RoundedCornerShape(10.dp),
medium = RoundedCornerShape(14.dp),
large = RoundedCornerShape(16.dp),
extraLarge = RoundedCornerShape(22.dp),
)
/**
* Process-wide holder of the "use system colors" preference, so flipping it in
* Settings re-themes every open Compose surface without recreating activities.
@@ -236,6 +266,7 @@ private fun buildTypography(): Typography
private val ShxTypography = buildTypography()
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ShxTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit)
{
@@ -251,11 +282,10 @@ fun ShxTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable ()
}
val statusColors = if (darkTheme) DarkStatusColors else LightStatusColors
CompositionLocalProvider(LocalShxStatusColors provides statusColors) {
/* MaterialExpressiveTheme/MotionScheme недоступны: в material3 1.4.0 они internal,
а 1.5.0-alpha требует compose 1.10+ (minSdk 23). Вернуться при подъеме minSdk. */
MaterialTheme(
MaterialExpressiveTheme(
colorScheme = colors,
shapes = ShxShapes,
motionScheme = MotionScheme.expressive(),
shapes = ShxShapes.scheme,
typography = ShxTypography,
content = content,
)