diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/base/Surface.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/base/Surface.kt
new file mode 100644
index 00000000..b3ad145f
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/base/Surface.kt
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.krtirtho.spotube.core.ui.base
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.luminance
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import kotlin.random.Random
+
+/**
+ * The Frutiger-Aero material families available to [BaseSurface].
+ *
+ * - [Watery]: deep aqua water with caustic light and bubbles.
+ * - [Icy]: pale frosted ice with crystalline facets and frost.
+ * - [Chrome]: brushed metal with a hard specular reflection.
+ * - [Acrylic]: translucent tinted panel with fine grain.
+ * - [Glass]: clear, glossy glass with a strong sheen.
+ */
+enum class SurfaceStyle {
+ Watery,
+ Icy,
+ Chrome,
+ Acrylic,
+ Glass,
+}
+
+/**
+ * A themed surface panel rendered in one of the Frutiger-Aero [SurfaceStyle] materials.
+ *
+ * Distinct from [androidx.compose.material3.Surface] — this draws decorative material
+ * backgrounds (gradients, gloss, grain, bubbles, facets) and provides a matching
+ * [LocalContentColor] so text/content inside stays legible.
+ */
+@Composable
+fun BaseSurface(
+ modifier: Modifier = Modifier,
+ style: SurfaceStyle = SurfaceStyle.Glass,
+ shape: Shape = RectangleShape,
+ elevation: Dp? = null,
+ contentColor: Color? = null,
+ contentPadding: PaddingValues = PaddingValues.Zero,
+ contentAlignment: Alignment = Alignment.TopStart,
+ content: @Composable BoxScope.() -> Unit,
+) {
+ val isLight = MaterialTheme.colorScheme.surface.luminance() > 0.5f
+ val onSurface = MaterialTheme.colorScheme.onSurface
+
+ val resolvedElevation = elevation ?: when (style) {
+ SurfaceStyle.Watery -> 14.dp
+ SurfaceStyle.Icy -> 12.dp
+ SurfaceStyle.Chrome -> 10.dp
+ SurfaceStyle.Acrylic -> 16.dp
+ SurfaceStyle.Glass -> 12.dp
+ }
+ val resolvedContentColor = contentColor ?: when (style) {
+ SurfaceStyle.Watery -> Color.White
+ SurfaceStyle.Icy -> if (isLight) Color(0xFF0B3A55) else Color(0xFFDCEBF7)
+ SurfaceStyle.Chrome -> Color(0xFF1B2733)
+ SurfaceStyle.Acrylic, SurfaceStyle.Glass -> onSurface
+ }
+ val ambient = when (style) {
+ SurfaceStyle.Watery -> Color(0xFF0E6FB0).copy(alpha = 0.38f)
+ SurfaceStyle.Icy -> Color(0xFF3FA9E0).copy(alpha = 0.3f)
+ SurfaceStyle.Chrome -> Color.Black.copy(alpha = 0.3f)
+ SurfaceStyle.Acrylic -> onSurface.copy(alpha = 0.22f)
+ SurfaceStyle.Glass -> onSurface.copy(alpha = 0.18f)
+ }
+ val spot = ambient.copy(alpha = (ambient.alpha + 0.08f).coerceAtMost(1f))
+
+ val borderBrush = when (style) {
+ SurfaceStyle.Watery -> Brush.verticalGradient(
+ listOf(Color.White.copy(alpha = 0.7f), Color.White.copy(alpha = 0.12f)),
+ )
+
+ SurfaceStyle.Icy -> Brush.verticalGradient(
+ listOf(Color.White.copy(alpha = 0.95f), Color.White.copy(alpha = 0.35f)),
+ )
+
+ SurfaceStyle.Chrome -> Brush.verticalGradient(
+ listOf(Color.White.copy(alpha = 0.95f), Color.Black.copy(alpha = 0.28f)),
+ )
+
+ SurfaceStyle.Acrylic -> Brush.verticalGradient(
+ listOf(Color.White.copy(alpha = 0.5f), Color.White.copy(alpha = 0.12f)),
+ )
+
+ SurfaceStyle.Glass -> Brush.verticalGradient(
+ listOf(Color.White.copy(alpha = 0.9f), Color.White.copy(alpha = 0.3f)),
+ )
+ }
+
+ // Translucent materials (Glass, Acrylic) must not use a drop shadow: it is drawn
+ // behind the node and, because the panel is see-through, shows up as a dark inset
+ // shape. Their depth comes from the edge, sheen and bevel instead.
+ val castsShadow = style != SurfaceStyle.Glass && style != SurfaceStyle.Acrylic
+
+ CompositionLocalProvider(LocalContentColor provides resolvedContentColor) {
+ Box(
+ modifier = modifier
+ .then(
+ if (castsShadow) {
+ Modifier.shadow(
+ elevation = resolvedElevation,
+ shape = shape,
+ ambientColor = ambient,
+ spotColor = spot,
+ )
+ } else {
+ Modifier
+ }
+ )
+ .clip(shape)
+ .drawWithCache {
+ onDrawBehind { drawSurfaceMaterial(style, isLight) }
+ }
+ .border(BorderStroke(1.dp, borderBrush), shape)
+ .padding(contentPadding),
+ contentAlignment = contentAlignment,
+ content = content,
+ )
+ }
+}
+
+private fun DrawScope.drawSurfaceMaterial(style: SurfaceStyle, isLight: Boolean) {
+ when (style) {
+ SurfaceStyle.Watery -> drawWatery(isLight)
+ SurfaceStyle.Icy -> drawIcy(isLight)
+ SurfaceStyle.Chrome -> drawChrome()
+ SurfaceStyle.Acrylic -> drawAcrylic(isLight)
+ SurfaceStyle.Glass -> drawGlass(isLight)
+ }
+}
+
+private fun DrawScope.drawGlass(isLight: Boolean) {
+ // See-through glass: a faint blue-tinted fill so whatever is behind shows through,
+ // defined by its bright edge, sheen and shadow rather than an opaque body.
+ val tint = if (isLight) Color(0xFFCFE0F2) else Color(0xFF9FB8D6)
+ drawRect(
+ brush = Brush.verticalGradient(
+ listOf(
+ tint.copy(alpha = if (isLight) 0.4f else 0.2f),
+ tint.copy(alpha = if (isLight) 0.24f else 0.1f),
+ ),
+ ),
+ )
+ drawGloss(if (isLight) 0.55f else 0.16f)
+ drawBottomReflection(if (isLight) 0.4f else 0.1f)
+ drawInnerBevel(isLight)
+ drawBubbles(seed = 11, count = 3, alpha = if (isLight) 0.4f else 0.14f)
+}
+
+private fun DrawScope.drawAcrylic(isLight: Boolean) {
+ // See-through acrylic: a translucent tinted sheet with a fine grain.
+ val tint = if (isLight) Color(0xFFCFD9E8) else Color(0xFF8FA2B8)
+ drawRect(
+ brush = Brush.verticalGradient(
+ listOf(
+ tint.copy(alpha = if (isLight) 0.45f else 0.22f),
+ tint.copy(alpha = if (isLight) 0.28f else 0.12f),
+ ),
+ ),
+ )
+
+ // Fine grain so the panel reads as a translucent acrylic sheet.
+ val random = Random(23)
+ repeat(180) {
+ val light = random.nextBoolean()
+ drawCircle(
+ color = if (light) {
+ Color.White.copy(alpha = if (isLight) 0.16f else 0.06f)
+ } else {
+ Color.Black.copy(alpha = if (isLight) 0.06f else 0.08f)
+ },
+ radius = 0.7f,
+ center = Offset(random.nextFloat() * size.width, random.nextFloat() * size.height),
+ )
+ }
+ drawGloss(if (isLight) 0.4f else 0.12f, heightFraction = 0.45f)
+ drawInnerBevel(isLight)
+}
+
+private fun DrawScope.drawChrome() {
+ drawRect(
+ brush = Brush.verticalGradient(
+ 0f to Color(0xFFF8FBFF),
+ 0.16f to Color(0xFFD6DEE7),
+ 0.4f to Color(0xFF8E99A8),
+ 0.5f to Color(0xFFEDF2F7),
+ 0.62f to Color(0xFF9AA5B4),
+ 1f to Color(0xFF6C7787),
+ ),
+ )
+ // Hard specular reflection near the top.
+ drawRect(
+ brush = Brush.verticalGradient(
+ colors = listOf(Color.White.copy(alpha = 0.85f), Color.Transparent),
+ startY = size.height * 0.05f,
+ endY = size.height * 0.22f,
+ ),
+ )
+ // Darkened lower edge for the metal bevel.
+ drawRect(
+ brush = Brush.verticalGradient(
+ colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.25f)),
+ startY = size.height * 0.78f,
+ endY = size.height,
+ ),
+ )
+}
+
+private fun DrawScope.drawIcy(isLight: Boolean) {
+ val colors = if (isLight) {
+ listOf(Color(0xFFF4FCFF), Color(0xFFCBE9FB), Color(0xFF9BD3F0))
+ } else {
+ // Snowy-night ice: dim slate-blue with faint frost.
+ listOf(Color(0xFF2A3B4D), Color(0xFF1B2A39), Color(0xFF0F1A26))
+ }
+ drawRect(brush = Brush.verticalGradient(colors))
+
+ val frost = if (isLight) Color.White else Color(0xFFB8CCE0)
+
+ // Crystalline facets (kept inset so they are never clipped by the shape).
+ val random = Random(37)
+ repeat(4) {
+ val s = size.minDimension * (0.2f + random.nextFloat() * 0.3f)
+ val x = random.nextFloat() * (size.width - s).coerceAtLeast(0f)
+ val y = random.nextFloat() * (size.height - s).coerceAtLeast(0f)
+ val path = Path().apply {
+ moveTo(x, y)
+ lineTo(x + s, y + s * 0.4f)
+ lineTo(x + s * 0.4f, y + s)
+ close()
+ }
+ drawPath(path, frost.copy(alpha = if (isLight) 0.25f else 0.12f))
+ }
+
+ // Frost speckles.
+ repeat(90) {
+ drawCircle(
+ color = frost.copy(alpha = if (isLight) 0.5f else 0.28f),
+ radius = 0.8f,
+ center = Offset(random.nextFloat() * size.width, random.nextFloat() * size.height),
+ )
+ }
+ drawGloss(if (isLight) 0.6f else 0.12f, heightFraction = 0.5f)
+}
+
+private fun DrawScope.drawWatery(isLight: Boolean) {
+ val colors = if (isLight) {
+ listOf(Color(0xFFA6E3FF), Color(0xFF3FB6EA), Color(0xFF0E6FB0))
+ } else {
+ // Moonlit night water: deep blue with a pale silver sheen.
+ listOf(Color(0xFF1C3A5E), Color(0xFF102942), Color(0xFF071827))
+ }
+ drawRect(brush = Brush.verticalGradient(colors))
+
+ val sheen = if (isLight) Color.White else Color(0xFFB9D2F0)
+
+ // Caustic / moonlight pooling from the surface.
+ drawRect(
+ brush = Brush.verticalGradient(
+ colors = listOf(sheen.copy(alpha = if (isLight) 0.35f else 0.22f), Color.Transparent),
+ startY = 0f,
+ endY = size.height * 0.5f,
+ ),
+ )
+
+ val random = Random(53)
+ repeat(5) {
+ val rx = size.width * (0.12f + random.nextFloat() * 0.2f)
+ val cx = rx + random.nextFloat() * (size.width - 2 * rx).coerceAtLeast(0f)
+ val cy = rx * 0.5f + random.nextFloat() * (size.height * 0.6f - rx).coerceAtLeast(0f)
+ drawOval(
+ brush = Brush.radialGradient(
+ colors = listOf(sheen.copy(alpha = if (isLight) 0.3f else 0.18f), Color.Transparent),
+ center = Offset(cx, cy),
+ radius = rx,
+ ),
+ topLeft = Offset(cx - rx, cy - rx * 0.5f),
+ size = Size(rx * 2f, rx),
+ )
+ }
+
+ drawBubbles(seed = 71, count = 6, alpha = if (isLight) 0.4f else 0.22f)
+}
+
+private fun DrawScope.drawGloss(alpha: Float, heightFraction: Float = 0.5f) {
+ drawRect(
+ brush = Brush.verticalGradient(
+ colors = listOf(Color.White.copy(alpha = alpha), Color.Transparent),
+ startY = 0f,
+ endY = size.height * heightFraction,
+ ),
+ )
+}
+
+private fun DrawScope.drawBottomReflection(alpha: Float) {
+ drawRect(
+ brush = Brush.verticalGradient(
+ colors = listOf(Color.Transparent, Color.White.copy(alpha = alpha)),
+ startY = size.height * 0.7f,
+ endY = size.height,
+ ),
+ )
+}
+
+/** A bright inner top edge plus a shaded inner bottom edge, giving the pane a glass bevel. */
+private fun DrawScope.drawInnerBevel(isLight: Boolean) {
+ drawRect(
+ brush = Brush.verticalGradient(
+ colors = listOf(Color.White.copy(alpha = if (isLight) 0.85f else 0.22f), Color.Transparent),
+ startY = 0f,
+ endY = size.height * 0.1f,
+ ),
+ )
+ drawRect(
+ brush = Brush.verticalGradient(
+ colors = listOf(Color.Transparent, Color.Black.copy(alpha = if (isLight) 0.08f else 0.28f)),
+ startY = size.height * 0.9f,
+ endY = size.height,
+ ),
+ )
+}
+
+private fun DrawScope.drawBubbles(seed: Int, count: Int, alpha: Float) {
+ val random = Random(seed)
+ repeat(count) {
+ val radius = size.minDimension * (0.05f + random.nextFloat() * 0.14f)
+ // Keep bubbles inset so they are never clipped into hard arcs at the edges.
+ val cx = radius + random.nextFloat() * (size.width - 2 * radius).coerceAtLeast(0f)
+ val cy = radius + random.nextFloat() * (size.height - 2 * radius).coerceAtLeast(0f)
+ val center = Offset(cx, cy)
+ drawCircle(
+ brush = Brush.radialGradient(
+ colors = listOf(Color.White.copy(alpha = alpha), Color.White.copy(alpha = 0f)),
+ center = center,
+ radius = radius,
+ ),
+ radius = radius,
+ center = center,
+ )
+ }
+}
+
+@BaseUIPhonePreview
+@BaseUIDesktopPreview
+@Composable
+private fun BaseSurfacePreview() {
+ BaseUIPreview {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(20.dp),
+ verticalArrangement = Arrangement.spacedBy(14.dp),
+ ) {
+ SurfaceStyle.entries.forEach { style ->
+ BaseSurface(
+ style = style,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(84.dp),
+ ) {
+ Text(
+ text = style.name,
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.SemiBold,
+ )
+ }
+ }
+ }
+ }
+}
+
+@BaseUIPhonePreview
+@Composable
+private fun BaseSurfaceGridPreview() {
+ BaseUIPreview {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(20.dp),
+ verticalArrangement = Arrangement.spacedBy(14.dp),
+ ) {
+ SurfaceStyle.entries.chunked(2).forEach { rowStyles ->
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(14.dp),
+ ) {
+ rowStyles.forEach { style ->
+ BaseSurface(
+ style = style,
+ modifier = Modifier
+ .weight(1f)
+ .height(140.dp),
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ text = style.name,
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.SemiBold,
+ )
+ Text(
+ text = "Frutiger Aero",
+ style = MaterialTheme.typography.labelMedium,
+ )
+ }
+ }
+ }
+ repeat(2 - rowStyles.size) {
+ androidx.compose.foundation.layout.Spacer(modifier = Modifier.weight(1f))
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/base/Theme.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/base/Theme.kt
index e195ffc2..9e27fc39 100644
--- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/base/Theme.kt
+++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/base/Theme.kt
@@ -30,6 +30,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.luminance
+import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -219,19 +220,51 @@ fun rememberBaseUITheme(): BaseUITheme {
val scheme = MaterialTheme.colorScheme
val isLight = scheme.surface.luminance() > 0.5f
- val containerLighter = if (isLight) Color.White else scheme.surfaceContainerHigh
- val containerDarker = if (isLight) Color(0xFFF2F2F4) else scheme.surfaceContainer
- val containerPressed = if (isLight) Color(0xFFE0E0E3) else scheme.surfaceContainerHighest
- val highlight = if (isLight) Color.White.copy(alpha = 0.9f) else Color.White.copy(alpha = 0.06f)
+ // ------------------------------------------------------------------
+ // Frutiger-Aero glass palette.
+ //
+ // Surfaces are glossy, top-lit gradients with a strong white specular
+ // highlight and translucent "glass" edges, lifted by soft, coloured
+ // shadows — the aqua/glass language of Frutiger-Aero.
+ // ------------------------------------------------------------------
+ val glassTop = if (isLight) {
+ Color.White
+ } else {
+ lerp(scheme.surfaceContainerHigh, Color.White, 0.10f)
+ }
+ val glassBottom = if (isLight) {
+ lerp(scheme.surface, Color.White, 0.04f)
+ } else {
+ scheme.surfaceContainer
+ }
+ val glassPressed = if (isLight) {
+ lerp(scheme.surfaceContainerHighest, Color.White, 0.06f)
+ } else {
+ lerp(scheme.surfaceContainerHighest, Color.Black, 0.08f)
+ }
+
+ val gloss = if (isLight) Color.White.copy(alpha = 0.75f) else Color.White.copy(alpha = 0.18f)
+ val glossSoft = if (isLight) Color.White.copy(alpha = 0.5f) else Color.White.copy(alpha = 0.12f)
+ val glassEdge = if (isLight) Color.White.copy(alpha = 0.95f) else Color.White.copy(alpha = 0.22f)
+ val glassBorder = if (isLight) {
+ lerp(scheme.outlineVariant, Color.White, 0.55f)
+ } else {
+ scheme.outlineVariant.copy(alpha = 0.5f)
+ }
val shadowColor = scheme.onSurface
- val border = scheme.outlineVariant
- val secondaryContainer = scheme.secondaryContainer
- val secondaryContainerPressed = secondaryContainer.copy(alpha = if (isLight) 0.85f else 0.92f)
- val secondaryHighlight =
- if (isLight) Color.White.copy(alpha = 0.5f) else Color.White.copy(alpha = 0.08f)
+ fun glassBrush(top: Color, bottom: Color): Brush =
+ Brush.verticalGradient(listOf(top, bottom))
- val defaultShape = RoundedCornerShape(14.dp)
+ fun aqua(accent: Color): Brush = Brush.verticalGradient(
+ listOf(
+ lerp(accent, Color.White, 0.45f),
+ accent,
+ lerp(accent, Color.Black, 0.14f),
+ )
+ )
+
+ val defaultShape = RoundedCornerShape(16.dp)
val defaultShapeState =
BaseUITheme.InteractionState(defaultShape, defaultShape, defaultShape, defaultShape)
val noBorder = BaseUITheme.Border(Color.Transparent, 0.dp)
@@ -239,231 +272,208 @@ fun rememberBaseUITheme(): BaseUITheme {
val noShadow = BaseUITheme.Shadow(0.dp, false, Color.Transparent, Color.Transparent)
val noShadowState = BaseUITheme.InteractionState(noShadow, noShadow, noShadow, noShadow)
val defaultButtonPadding = BaseUITheme.InteractionState(
- PaddingValues(horizontal = 20.dp, vertical = 10.dp),
- PaddingValues(horizontal = 20.dp, vertical = 10.dp),
- PaddingValues(horizontal = 20.dp, vertical = 10.dp),
- PaddingValues(horizontal = 20.dp, vertical = 10.dp),
+ PaddingValues(horizontal = 22.dp, vertical = 10.dp),
+ PaddingValues(horizontal = 22.dp, vertical = 10.dp),
+ PaddingValues(horizontal = 22.dp, vertical = 10.dp),
+ PaddingValues(horizontal = 22.dp, vertical = 10.dp),
)
val iconButtonPadding = BaseUITheme.InteractionState(
- PaddingValues(8.dp),
- PaddingValues(8.dp),
- PaddingValues(8.dp),
- PaddingValues(8.dp),
+ PaddingValues(9.dp),
+ PaddingValues(9.dp),
+ PaddingValues(9.dp),
+ PaddingValues(9.dp),
)
+ // --- Glass (outline) -------------------------------------------------
val outlineColors = BaseUITheme.InteractionState(
normal = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
+ background = glassBrush(glassTop, glassBottom),
foreground = scheme.onSurface,
- highlight = highlight,
+ highlight = gloss,
),
hovered = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
+ background = glassBrush(glassTop, glassBottom),
foreground = scheme.onSurface,
- highlight = highlight,
+ highlight = Color.White.copy(alpha = if (isLight) 0.9f else 0.26f),
),
pressed = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(containerPressed, containerPressed)),
+ background = glassBrush(glassPressed, glassPressed),
foreground = scheme.onSurface,
- highlight = highlight,
+ highlight = glossSoft,
),
focused = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
+ background = glassBrush(glassTop, glassBottom),
foreground = scheme.onSurface,
- highlight = highlight,
+ highlight = gloss,
),
)
- val outlineShadow = BaseUITheme.InteractionState(
+ val glassShadow = BaseUITheme.InteractionState(
normal = BaseUITheme.Shadow(
- 6.dp,
+ 8.dp,
true,
- shadowColor.copy(alpha = 0.15f),
- shadowColor.copy(alpha = 0.18f)
+ shadowColor.copy(alpha = 0.16f),
+ shadowColor.copy(alpha = 0.2f),
),
hovered = BaseUITheme.Shadow(
- 9.dp,
+ 14.dp,
true,
shadowColor.copy(alpha = 0.22f),
- shadowColor.copy(alpha = 0.26f)
+ shadowColor.copy(alpha = 0.26f),
),
pressed = BaseUITheme.Shadow(
- 1.dp,
+ 2.dp,
true,
- shadowColor.copy(alpha = 0.08f),
- shadowColor.copy(alpha = 0.1f)
+ shadowColor.copy(alpha = 0.1f),
+ shadowColor.copy(alpha = 0.12f),
),
focused = BaseUITheme.Shadow(
- 6.dp,
+ 8.dp,
true,
- shadowColor.copy(alpha = 0.15f),
- shadowColor.copy(alpha = 0.18f)
+ shadowColor.copy(alpha = 0.16f),
+ shadowColor.copy(alpha = 0.2f),
),
)
- val outlineBorder = BaseUITheme.InteractionState(
- normal = BaseUITheme.Border(border, 0.5.dp),
- hovered = BaseUITheme.Border(border, 0.5.dp),
- pressed = BaseUITheme.Border(border.copy(alpha = 0.7f), 0.5.dp),
- focused = BaseUITheme.Border(border, 0.5.dp),
+ val glassBorderState = BaseUITheme.InteractionState(
+ normal = BaseUITheme.Border(glassBorder, 1.dp),
+ hovered = BaseUITheme.Border(glassEdge, 1.dp),
+ pressed = BaseUITheme.Border(glassBorder.copy(alpha = 0.7f), 1.dp),
+ focused = BaseUITheme.Border(glassEdge, 1.dp),
)
+ // --- Aqua accent (primary) -------------------------------------------
+ val primaryEdge = lerp(scheme.primary, Color.White, 0.5f)
val primaryColors = BaseUITheme.InteractionState(
- normal = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(scheme.primary, scheme.primary)),
- foreground = scheme.onPrimary,
- highlight = Color.White.copy(alpha = 0.25f),
- ),
+ normal = BaseUITheme.ButtonColors(aqua(scheme.primary), scheme.onPrimary, gloss),
hovered = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(scheme.primary, scheme.primary)),
- foreground = scheme.onPrimary,
- highlight = Color.White.copy(alpha = 0.25f),
+ aqua(scheme.primary),
+ scheme.onPrimary,
+ Color.White.copy(alpha = if (isLight) 0.9f else 0.3f),
),
pressed = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(
- listOf(
- scheme.primary.copy(alpha = 0.85f),
- scheme.primary
- )
+ glassBrush(
+ lerp(scheme.primary, Color.Black, 0.16f),
+ lerp(scheme.primary, Color.Black, 0.24f),
),
- foreground = scheme.onPrimary,
- highlight = Color.White.copy(alpha = 0.25f),
- ),
- focused = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(scheme.primary, scheme.primary)),
- foreground = scheme.onPrimary,
- highlight = Color.White.copy(alpha = 0.25f),
+ scheme.onPrimary,
+ glossSoft,
),
+ focused = BaseUITheme.ButtonColors(aqua(scheme.primary), scheme.onPrimary, gloss),
)
val primaryShadow = BaseUITheme.InteractionState(
normal = BaseUITheme.Shadow(
- 6.dp,
+ 10.dp,
true,
scheme.primary.copy(alpha = 0.35f),
- scheme.primary.copy(alpha = 0.4f)
+ scheme.primary.copy(alpha = 0.42f),
),
hovered = BaseUITheme.Shadow(
- 9.dp,
+ 16.dp,
true,
scheme.primary.copy(alpha = 0.45f),
- scheme.primary.copy(alpha = 0.5f)
+ scheme.primary.copy(alpha = 0.5f),
),
pressed = BaseUITheme.Shadow(
- 1.dp,
+ 3.dp,
true,
- scheme.primary.copy(alpha = 0.2f),
- scheme.primary.copy(alpha = 0.25f)
+ scheme.primary.copy(alpha = 0.22f),
+ scheme.primary.copy(alpha = 0.28f),
),
focused = BaseUITheme.Shadow(
- 6.dp,
+ 10.dp,
true,
scheme.primary.copy(alpha = 0.35f),
- scheme.primary.copy(alpha = 0.4f)
+ scheme.primary.copy(alpha = 0.42f),
),
)
val primaryBorder = BaseUITheme.InteractionState(
- normal = BaseUITheme.Border(scheme.primary, 0.5.dp),
- hovered = BaseUITheme.Border(scheme.primary, 0.5.dp),
- pressed = BaseUITheme.Border(scheme.primary, 0.5.dp),
- focused = BaseUITheme.Border(scheme.primary, 0.5.dp),
+ normal = BaseUITheme.Border(primaryEdge, 1.dp),
+ hovered = BaseUITheme.Border(primaryEdge, 1.dp),
+ pressed = BaseUITheme.Border(primaryEdge.copy(alpha = 0.8f), 1.dp),
+ focused = BaseUITheme.Border(primaryEdge, 1.dp),
)
+ // --- Secondary (soft aqua glass) -------------------------------------
+ val secondaryBase = scheme.secondaryContainer
val secondaryColors = BaseUITheme.InteractionState(
normal = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(
- listOf(
- secondaryContainer,
- secondaryContainer.copy(alpha = if (secondaryContainer.luminance() > 0.5f) 0.92f else 1f)
- )
- ),
- foreground = scheme.onSecondaryContainer,
- highlight = secondaryHighlight,
+ glassBrush(lerp(secondaryBase, Color.White, 0.5f), secondaryBase),
+ scheme.onSecondaryContainer,
+ gloss,
),
hovered = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(
- listOf(
- secondaryContainer,
- secondaryContainer.copy(alpha = if (secondaryContainer.luminance() > 0.5f) 0.92f else 1f)
- )
- ),
- foreground = scheme.onSecondaryContainer,
- highlight = secondaryHighlight,
+ glassBrush(lerp(secondaryBase, Color.White, 0.55f), secondaryBase),
+ scheme.onSecondaryContainer,
+ Color.White.copy(alpha = if (isLight) 0.85f else 0.24f),
),
pressed = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(
- listOf(
- secondaryContainerPressed,
- secondaryContainerPressed
- )
+ glassBrush(
+ lerp(secondaryBase, Color.Black, 0.08f),
+ lerp(secondaryBase, Color.Black, 0.14f),
),
- foreground = scheme.onSecondaryContainer,
- highlight = secondaryHighlight,
+ scheme.onSecondaryContainer,
+ glossSoft,
),
focused = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(
- listOf(
- secondaryContainer,
- secondaryContainer.copy(alpha = if (secondaryContainer.luminance() > 0.5f) 0.92f else 1f)
- )
- ),
- foreground = scheme.onSecondaryContainer,
- highlight = secondaryHighlight,
+ glassBrush(lerp(secondaryBase, Color.White, 0.5f), secondaryBase),
+ scheme.onSecondaryContainer,
+ gloss,
),
)
val secondaryShadow = BaseUITheme.InteractionState(
normal = BaseUITheme.Shadow(
- 6.dp,
+ 8.dp,
true,
- shadowColor.copy(alpha = 0.15f),
- shadowColor.copy(alpha = 0.18f)
+ secondaryBase.copy(alpha = 0.4f),
+ secondaryBase.copy(alpha = 0.5f),
),
hovered = BaseUITheme.Shadow(
- 9.dp,
+ 14.dp,
true,
- shadowColor.copy(alpha = 0.22f),
- shadowColor.copy(alpha = 0.26f)
+ secondaryBase.copy(alpha = 0.5f),
+ secondaryBase.copy(alpha = 0.6f),
),
pressed = BaseUITheme.Shadow(
- 1.dp,
+ 2.dp,
true,
- shadowColor.copy(alpha = 0.08f),
- shadowColor.copy(alpha = 0.1f)
+ secondaryBase.copy(alpha = 0.3f),
+ secondaryBase.copy(alpha = 0.35f),
),
focused = BaseUITheme.Shadow(
- 6.dp,
+ 8.dp,
true,
- shadowColor.copy(alpha = 0.15f),
- shadowColor.copy(alpha = 0.18f)
+ secondaryBase.copy(alpha = 0.4f),
+ secondaryBase.copy(alpha = 0.5f),
),
)
val secondaryBorder = BaseUITheme.InteractionState(
- normal = BaseUITheme.Border(secondaryContainer.copy(alpha = 0.5f), 0.5.dp),
- hovered = BaseUITheme.Border(secondaryContainer.copy(alpha = 0.5f), 0.5.dp),
- pressed = BaseUITheme.Border(secondaryContainer.copy(alpha = 0.5f), 0.5.dp),
- focused = BaseUITheme.Border(secondaryContainer.copy(alpha = 0.5f), 0.5.dp),
+ normal = BaseUITheme.Border(lerp(secondaryBase, Color.White, 0.5f), 1.dp),
+ hovered = BaseUITheme.Border(lerp(secondaryBase, Color.White, 0.6f), 1.dp),
+ pressed = BaseUITheme.Border(lerp(secondaryBase, Color.White, 0.4f), 1.dp),
+ focused = BaseUITheme.Border(lerp(secondaryBase, Color.White, 0.5f), 1.dp),
)
+ // --- Ghost -----------------------------------------------------------
val ghostColors = BaseUITheme.InteractionState(
normal = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(Color.Transparent, Color.Transparent)),
+ background = glassBrush(Color.Transparent, Color.Transparent),
foreground = scheme.onSurface,
highlight = Color.Transparent,
),
hovered = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(
- listOf(
- containerPressed.copy(alpha = 0.5f),
- containerPressed.copy(alpha = 0.5f)
- )
+ background = glassBrush(
+ lerp(scheme.surfaceContainerHigh, Color.White, 0.1f),
+ scheme.surfaceContainer,
),
foreground = scheme.onSurface,
- highlight = Color.Transparent,
+ highlight = glossSoft,
),
pressed = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(containerPressed, containerPressed)),
+ background = glassBrush(glassPressed, glassPressed),
foreground = scheme.onSurface,
highlight = Color.Transparent,
),
focused = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(Color.Transparent, Color.Transparent)),
+ background = glassBrush(Color.Transparent, Color.Transparent),
foreground = scheme.onSurface,
highlight = Color.Transparent,
),
@@ -472,8 +482,8 @@ fun rememberBaseUITheme(): BaseUITheme {
val outlineStyle = BaseUITheme.ButtonStyle(
outlineColors,
defaultShapeState,
- outlineShadow,
- outlineBorder,
+ glassShadow,
+ glassBorderState,
defaultButtonPadding
)
val primaryStyle = BaseUITheme.ButtonStyle(
@@ -501,8 +511,8 @@ fun rememberBaseUITheme(): BaseUITheme {
val outlineIconStyle = BaseUITheme.ButtonStyle(
outlineColors,
defaultShapeState,
- outlineShadow,
- outlineBorder,
+ glassShadow,
+ glassBorderState,
iconButtonPadding
)
val primaryIconStyle = BaseUITheme.ButtonStyle(
@@ -534,20 +544,20 @@ fun rememberBaseUITheme(): BaseUITheme {
focused = scheme.onSurface,
)
- val checkBoxShape = RoundedCornerShape(6.dp)
+ val checkBoxShape = RoundedCornerShape(7.dp)
val checkBoxShapeState =
BaseUITheme.InteractionState(checkBoxShape, checkBoxShape, checkBoxShape, checkBoxShape)
val noPadding =
BaseUITheme.InteractionState(PaddingValues(0.dp), PaddingValues(0.dp), PaddingValues(0.dp), PaddingValues(0.dp))
- val chipTabShape = RoundedCornerShape(10.dp)
+ val chipTabShape = RoundedCornerShape(999.dp)
val chipTabShapeState =
BaseUITheme.InteractionState(chipTabShape, chipTabShape, chipTabShape, chipTabShape)
val chipTabPadding = BaseUITheme.InteractionState(
- PaddingValues(horizontal = 14.dp, vertical = 4.dp),
- PaddingValues(horizontal = 14.dp, vertical = 4.dp),
- PaddingValues(horizontal = 14.dp, vertical = 4.dp),
- PaddingValues(horizontal = 14.dp, vertical = 4.dp),
+ PaddingValues(horizontal = 16.dp, vertical = 6.dp),
+ PaddingValues(horizontal = 16.dp, vertical = 6.dp),
+ PaddingValues(horizontal = 16.dp, vertical = 6.dp),
+ PaddingValues(horizontal = 16.dp, vertical = 6.dp),
)
val toggleShape = RoundedCornerShape(50)
@@ -575,16 +585,16 @@ fun rememberBaseUITheme(): BaseUITheme {
),
textField = TextFieldTheme(
background = BaseUITheme.InteractionState(
- normal = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
- hovered = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
- pressed = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
- focused = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
+ normal = glassBrush(glassTop, glassBottom),
+ hovered = glassBrush(glassTop, glassBottom),
+ pressed = glassBrush(glassPressed, glassPressed),
+ focused = glassBrush(glassTop, glassBottom),
),
highlight = BaseUITheme.InteractionState(
- normal = highlight,
- hovered = highlight,
- pressed = highlight,
- focused = highlight,
+ normal = gloss,
+ hovered = gloss,
+ pressed = glossSoft,
+ focused = Color.White.copy(alpha = if (isLight) 0.85f else 0.22f),
),
shape = BaseUITheme.InteractionState(
normal = defaultShape,
@@ -593,53 +603,48 @@ fun rememberBaseUITheme(): BaseUITheme {
focused = defaultShape,
),
border = BaseUITheme.InteractionState(
- normal = BaseUITheme.Border(border.copy(alpha = 0.85f), 0.5.dp),
- hovered = BaseUITheme.Border(border.copy(alpha = 0.85f), 0.5.dp),
- pressed = BaseUITheme.Border(border, 0.5.dp),
- focused = BaseUITheme.Border(scheme.primary, 0.5.dp),
+ normal = BaseUITheme.Border(glassBorder, 1.dp),
+ hovered = BaseUITheme.Border(glassEdge, 1.dp),
+ pressed = BaseUITheme.Border(glassBorder.copy(alpha = 0.7f), 1.dp),
+ focused = BaseUITheme.Border(primaryEdge, 1.5.dp),
),
shadow = BaseUITheme.InteractionState(
normal = BaseUITheme.Shadow(
- 9.dp,
+ 8.dp,
true,
- shadowColor.copy(alpha = 0.22f),
- shadowColor.copy(alpha = 0.26f)
- ),
- hovered = BaseUITheme.Shadow(
- 9.dp,
- true,
- shadowColor.copy(alpha = 0.22f),
- shadowColor.copy(alpha = 0.26f)
- ),
- pressed = BaseUITheme.Shadow(
- 6.dp,
- true,
- shadowColor.copy(alpha = 0.15f),
+ shadowColor.copy(alpha = 0.14f),
shadowColor.copy(alpha = 0.18f)
),
- focused = BaseUITheme.Shadow(
- 9.dp,
+ hovered = BaseUITheme.Shadow(
+ 10.dp,
true,
- scheme.primary.copy(alpha = 0.2f),
- scheme.primary.copy(alpha = 0.25f)
+ shadowColor.copy(alpha = 0.18f),
+ shadowColor.copy(alpha = 0.22f)
+ ),
+ pressed = BaseUITheme.Shadow(
+ 4.dp,
+ true,
+ shadowColor.copy(alpha = 0.1f),
+ shadowColor.copy(alpha = 0.12f)
+ ),
+ focused = BaseUITheme.Shadow(
+ 10.dp,
+ true,
+ scheme.primary.copy(alpha = 0.22f),
+ scheme.primary.copy(alpha = 0.28f)
),
),
- padding = PaddingValues(horizontal = 14.dp, vertical = 10.dp),
+ padding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
textStyle = TextStyle.Default,
cursor = SolidColor(scheme.primary),
foreground = textFieldForeground,
),
autoCompleteMenuTheme = AutoCompleteMenuTheme(
- background = Brush.verticalGradient(
- listOf(
- scheme.surfaceContainerHigh,
- scheme.surfaceContainer
- )
- ),
- shape = RoundedCornerShape(12.dp),
- border = BaseUITheme.Border(border, 0.5.dp),
+ background = glassBrush(glassTop, glassBottom),
+ shape = RoundedCornerShape(16.dp),
+ border = BaseUITheme.Border(glassBorder, 1.dp),
padding = PaddingValues(0.dp),
- shadowElevation = 8.dp,
+ shadowElevation = 16.dp,
),
slider = SliderTheme(
trackActiveColor = BaseUITheme.InteractionState(
@@ -649,10 +654,10 @@ fun rememberBaseUITheme(): BaseUITheme {
focused = scheme.primary,
),
trackInactiveColor = BaseUITheme.InteractionState(
- normal = border.copy(alpha = 0.6f),
- hovered = border.copy(alpha = 0.6f),
- pressed = border.copy(alpha = 0.6f),
- focused = border.copy(alpha = 0.6f),
+ normal = glassBorder,
+ hovered = glassBorder,
+ pressed = glassBorder,
+ focused = glassBorder,
),
thumbColor = BaseUITheme.InteractionState(
normal = scheme.primary,
@@ -664,94 +669,44 @@ fun rememberBaseUITheme(): BaseUITheme {
normal = BaseUITheme.Shadow(
5.dp,
true,
- scheme.primary.copy(alpha = 0.35f),
- scheme.primary.copy(alpha = 0.4f)
+ scheme.primary.copy(alpha = 0.4f),
+ scheme.primary.copy(alpha = 0.45f)
),
hovered = BaseUITheme.Shadow(
8.dp,
true,
- scheme.primary.copy(alpha = 0.4f),
+ scheme.primary.copy(alpha = 0.45f),
scheme.primary.copy(alpha = 0.5f)
),
pressed = BaseUITheme.Shadow(
2.dp,
true,
- scheme.primary.copy(alpha = 0.2f),
- scheme.primary.copy(alpha = 0.25f)
+ scheme.primary.copy(alpha = 0.25f),
+ scheme.primary.copy(alpha = 0.3f)
),
focused = BaseUITheme.Shadow(
5.dp,
true,
- scheme.primary.copy(alpha = 0.35f),
- scheme.primary.copy(alpha = 0.4f)
+ scheme.primary.copy(alpha = 0.4f),
+ scheme.primary.copy(alpha = 0.45f)
),
),
- stepActiveColor = scheme.onPrimary.copy(alpha = 0.5f),
+ stepActiveColor = Color.White.copy(alpha = 0.7f),
stepInactiveColor = scheme.onSurface.copy(alpha = 0.25f),
),
checkBox = CheckBoxTheme(
selected = BaseUITheme.ButtonStyle(
colors = primaryColors,
shape = checkBoxShapeState,
- shadow = BaseUITheme.InteractionState(
- normal = BaseUITheme.Shadow(
- 6.dp,
- true,
- scheme.primary.copy(alpha = 0.3f),
- scheme.primary.copy(alpha = 0.35f)
- ),
- hovered = BaseUITheme.Shadow(
- 8.dp,
- true,
- scheme.primary.copy(alpha = 0.4f),
- scheme.primary.copy(alpha = 0.45f)
- ),
- pressed = BaseUITheme.Shadow(
- 1.dp,
- true,
- scheme.primary.copy(alpha = 0.2f),
- scheme.primary.copy(alpha = 0.25f)
- ),
- focused = BaseUITheme.Shadow(
- 6.dp,
- true,
- scheme.primary.copy(alpha = 0.3f),
- scheme.primary.copy(alpha = 0.35f)
- ),
- ),
+ shadow = primaryShadow,
border = primaryBorder,
padding = noPadding,
),
unselected = BaseUITheme.ButtonStyle(
colors = outlineColors,
shape = checkBoxShapeState,
- shadow = BaseUITheme.InteractionState(
- normal = BaseUITheme.Shadow(
- 3.dp,
- true,
- shadowColor.copy(alpha = 0.15f),
- shadowColor.copy(alpha = 0.18f)
- ),
- hovered = BaseUITheme.Shadow(
- 5.dp,
- true,
- shadowColor.copy(alpha = 0.2f),
- shadowColor.copy(alpha = 0.24f)
- ),
- pressed = BaseUITheme.Shadow(
- 1.dp,
- true,
- shadowColor.copy(alpha = 0.08f),
- shadowColor.copy(alpha = 0.1f)
- ),
- focused = BaseUITheme.Shadow(
- 3.dp,
- true,
- shadowColor.copy(alpha = 0.15f),
- shadowColor.copy(alpha = 0.18f)
- ),
- ),
- border = outlineBorder,
+ shadow = glassShadow,
+ border = glassBorderState,
padding = noPadding,
),
checkmarkColor = scheme.onPrimary,
@@ -767,131 +722,24 @@ fun rememberBaseUITheme(): BaseUITheme {
unselected = BaseUITheme.ButtonStyle(
colors = outlineColors,
shape = chipTabShapeState,
- shadow = outlineShadow,
- border = outlineBorder,
+ shadow = glassShadow,
+ border = glassBorderState,
padding = chipTabPadding,
),
),
toggle = BaseUITheme.ToggleTheme(
checked = BaseUITheme.ButtonStyle(
- colors = BaseUITheme.InteractionState(
- normal = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(scheme.primary, scheme.primary)),
- foreground = scheme.onPrimary,
- highlight = Color.White.copy(alpha = 0.25f),
- ),
- hovered = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(scheme.primary, scheme.primary)),
- foreground = scheme.onPrimary,
- highlight = Color.White.copy(alpha = 0.25f),
- ),
- pressed = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(
- listOf(
- scheme.primary.copy(alpha = 0.85f),
- scheme.primary
- )
- ),
- foreground = scheme.onPrimary,
- highlight = Color.White.copy(alpha = 0.25f),
- ),
- focused = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(scheme.primary, scheme.primary)),
- foreground = scheme.onPrimary,
- highlight = Color.White.copy(alpha = 0.25f),
- ),
- ),
+ colors = primaryColors,
shape = toggleShapeState,
- shadow = BaseUITheme.InteractionState(
- normal = BaseUITheme.Shadow(
- 4.dp,
- true,
- scheme.primary.copy(alpha = 0.3f),
- scheme.primary.copy(alpha = 0.35f)
- ),
- hovered = BaseUITheme.Shadow(
- 6.dp,
- true,
- scheme.primary.copy(alpha = 0.4f),
- scheme.primary.copy(alpha = 0.45f)
- ),
- pressed = BaseUITheme.Shadow(
- 1.dp,
- true,
- scheme.primary.copy(alpha = 0.2f),
- scheme.primary.copy(alpha = 0.25f)
- ),
- focused = BaseUITheme.Shadow(
- 4.dp,
- true,
- scheme.primary.copy(alpha = 0.3f),
- scheme.primary.copy(alpha = 0.35f)
- ),
- ),
- border = BaseUITheme.InteractionState(
- normal = BaseUITheme.Border(scheme.primary, 0.5.dp),
- hovered = BaseUITheme.Border(scheme.primary, 0.5.dp),
- pressed = BaseUITheme.Border(scheme.primary, 0.5.dp),
- focused = BaseUITheme.Border(scheme.primary, 0.5.dp),
- ),
+ shadow = primaryShadow,
+ border = primaryBorder,
padding = togglePadding,
),
unchecked = BaseUITheme.ButtonStyle(
- colors = BaseUITheme.InteractionState(
- normal = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
- foreground = scheme.onSurface,
- highlight = highlight,
- ),
- hovered = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
- foreground = scheme.onSurface,
- highlight = highlight,
- ),
- pressed = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(containerPressed, containerPressed)),
- foreground = scheme.onSurface,
- highlight = highlight,
- ),
- focused = BaseUITheme.ButtonColors(
- background = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
- foreground = scheme.onSurface,
- highlight = highlight,
- ),
- ),
+ colors = outlineColors,
shape = toggleShapeState,
- shadow = BaseUITheme.InteractionState(
- normal = BaseUITheme.Shadow(
- 4.dp,
- true,
- shadowColor.copy(alpha = 0.15f),
- shadowColor.copy(alpha = 0.18f)
- ),
- hovered = BaseUITheme.Shadow(
- 6.dp,
- true,
- shadowColor.copy(alpha = 0.22f),
- shadowColor.copy(alpha = 0.26f)
- ),
- pressed = BaseUITheme.Shadow(
- 1.dp,
- true,
- shadowColor.copy(alpha = 0.08f),
- shadowColor.copy(alpha = 0.1f)
- ),
- focused = BaseUITheme.Shadow(
- 4.dp,
- true,
- shadowColor.copy(alpha = 0.15f),
- shadowColor.copy(alpha = 0.18f)
- ),
- ),
- border = BaseUITheme.InteractionState(
- normal = BaseUITheme.Border(border, 0.5.dp),
- hovered = BaseUITheme.Border(border, 0.5.dp),
- pressed = BaseUITheme.Border(border.copy(alpha = 0.7f), 0.5.dp),
- focused = BaseUITheme.Border(border, 0.5.dp),
- ),
+ shadow = glassShadow,
+ border = glassBorderState,
padding = togglePadding,
),
thumb = BaseUITheme.InteractionState(
@@ -928,61 +776,62 @@ fun rememberBaseUITheme(): BaseUITheme {
),
),
card = BaseUITheme.CardTheme(
- background = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
- shape = RoundedCornerShape(16.dp),
- border = BaseUITheme.Border(border, 0.5.dp),
+ background = glassBrush(glassTop, glassBottom),
+ shape = RoundedCornerShape(20.dp),
+ border = BaseUITheme.Border(glassBorder, 1.dp),
padding = PaddingValues(0.dp),
shadow = BaseUITheme.Shadow(
- 6.dp,
+ 12.dp,
true,
- shadowColor.copy(alpha = 0.15f),
- shadowColor.copy(alpha = 0.18f)
+ shadowColor.copy(alpha = 0.16f),
+ shadowColor.copy(alpha = 0.2f)
),
),
listRowTile = BaseUITheme.ListRowTheme(
background = BaseUITheme.AdvancedInteractionState(
- normal = Brush.verticalGradient(listOf(Color.Transparent, Color.Transparent)),
- hovered = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
- pressed = Brush.verticalGradient(listOf(containerPressed, containerPressed)),
- focused = Brush.verticalGradient(listOf(containerLighter, containerDarker)),
- selected = Brush.verticalGradient(
- listOf(
- scheme.primaryContainer.copy(alpha = 0.3f),
- scheme.primaryContainer.copy(alpha = 0.3f)
- )
+ normal = glassBrush(Color.Transparent, Color.Transparent),
+ hovered = glassBrush(glassTop, glassBottom),
+ pressed = glassBrush(glassPressed, glassPressed),
+ focused = glassBrush(glassTop, glassBottom),
+ selected = glassBrush(
+ lerp(scheme.primaryContainer, Color.White, 0.4f),
+ scheme.primaryContainer,
),
- disabled = Brush.verticalGradient(
- listOf(
- containerLighter.copy(alpha = 0.5f),
- containerDarker.copy(alpha = 0.5f)
- )
+ disabled = glassBrush(
+ glassTop.copy(alpha = 0.5f),
+ glassBottom.copy(alpha = 0.5f),
),
),
shape = BaseUITheme.AdvancedInteractionState(
- normal = RoundedCornerShape(8.dp),
- hovered = RoundedCornerShape(8.dp),
- pressed = RoundedCornerShape(8.dp),
- focused = RoundedCornerShape(8.dp),
- selected = RoundedCornerShape(8.dp),
- disabled = RoundedCornerShape(8.dp),
+ normal = RoundedCornerShape(10.dp),
+ hovered = RoundedCornerShape(10.dp),
+ pressed = RoundedCornerShape(10.dp),
+ focused = RoundedCornerShape(10.dp),
+ selected = RoundedCornerShape(10.dp),
+ disabled = RoundedCornerShape(10.dp),
),
border = BaseUITheme.AdvancedInteractionState(
normal = BaseUITheme.Border(Color.Transparent, 0.dp),
- hovered = BaseUITheme.Border(Color.Transparent, 0.dp),
+ hovered = BaseUITheme.Border(glassBorder, 1.dp),
pressed = BaseUITheme.Border(Color.Transparent, 0.dp),
- focused = BaseUITheme.Border(Color.Transparent, 0.dp),
+ focused = BaseUITheme.Border(glassBorder, 1.dp),
selected = BaseUITheme.Border(Color.Transparent, 0.dp),
disabled = BaseUITheme.Border(Color.Transparent, 0.dp),
),
shadow = BaseUITheme.AdvancedInteractionState(
- normal = BaseUITheme.Shadow(0.dp, false, Color.Transparent, Color.Transparent),
- hovered = BaseUITheme.Shadow(0.dp, false, Color.Transparent, Color.Transparent),
- pressed = BaseUITheme.Shadow(0.dp, false, Color.Transparent, Color.Transparent),
- focused = BaseUITheme.Shadow(0.dp, false, Color.Transparent, Color.Transparent),
- selected = BaseUITheme.Shadow(0.dp, false, Color.Transparent, Color.Transparent),
- disabled = BaseUITheme.Shadow(0.dp, false, Color.Transparent, Color.Transparent),
+ normal = noShadow,
+ hovered = BaseUITheme.Shadow(
+ 6.dp,
+ true,
+ shadowColor.copy(alpha = 0.12f),
+ shadowColor.copy(alpha = 0.16f)
+ ),
+ pressed = noShadow,
+ focused = noShadow,
+ selected = noShadow,
+ disabled = noShadow,
),
- padding = PaddingValues(horizontal = 8.dp, vertical = 6.dp),
+ padding = PaddingValues(horizontal = 10.dp, vertical = 8.dp),
textStyle = BaseUITheme.AdvancedInteractionState(
normal = TextStyle.Default,
hovered = TextStyle.Default,
@@ -1001,17 +850,12 @@ fun rememberBaseUITheme(): BaseUITheme {
),
),
dialog = BaseUITheme.DialogTheme(
- background = Brush.verticalGradient(
- listOf(
- scheme.surface,
- scheme.surfaceContainerHigh,
- )
- ),
+ background = glassBrush(glassTop, glassBottom),
scrim = Color.Black.copy(alpha = 0.42f),
- shape = RoundedCornerShape(16.dp),
- border = BaseUITheme.Border(border, 0.5.dp),
+ shape = RoundedCornerShape(24.dp),
+ border = BaseUITheme.Border(glassEdge, 1.dp),
shadow = BaseUITheme.Shadow(
- 24.dp,
+ 28.dp,
true,
shadowColor.copy(alpha = 0.3f),
shadowColor.copy(alpha = 0.4f)
@@ -1019,27 +863,20 @@ fun rememberBaseUITheme(): BaseUITheme {
padding = PaddingValues(24.dp),
),
dropdownMenu = BaseUITheme.DropdownMenuTheme(
- background = Brush.verticalGradient(
- listOf(
- scheme.surfaceContainerHigh,
- scheme.surfaceContainer,
- )
- ),
- shape = RoundedCornerShape(12.dp),
- border = BaseUITheme.Border(border, 0.5.dp),
+ background = glassBrush(glassTop, glassBottom),
+ shape = RoundedCornerShape(16.dp),
+ border = BaseUITheme.Border(glassBorder, 1.dp),
shadow = BaseUITheme.Shadow(
- 12.dp,
+ 16.dp,
true,
- shadowColor.copy(alpha = 0.2f),
- shadowColor.copy(alpha = 0.25f)
- ),
- itemHoverBackground = Brush.verticalGradient(
- listOf(containerLighter, containerDarker)
+ shadowColor.copy(alpha = 0.22f),
+ shadowColor.copy(alpha = 0.28f)
),
+ itemHoverBackground = glassBrush(glassTop, glassBottom),
itemForeground = scheme.onSurface,
- itemHighlight = highlight,
- padding = PaddingValues(4.dp),
- itemPadding = PaddingValues(horizontal = 14.dp, vertical = 10.dp),
+ itemHighlight = gloss,
+ padding = PaddingValues(6.dp),
+ itemPadding = PaddingValues(horizontal = 14.dp, vertical = 11.dp),
),
)
}
diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/base/Utils.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/base/Utils.kt
index 19e986eb..96d44fd5 100644
--- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/base/Utils.kt
+++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/base/Utils.kt
@@ -24,15 +24,27 @@ import androidx.compose.ui.unit.dp
fun Modifier.highlight(color: Color): Modifier =
drawWithCache {
- val highlightBrush = Brush.verticalGradient(
- colors = listOf(color, Color.Transparent),
+ // Frutiger-Aero glass: a bright specular sheen across the top half plus a
+ // faint reflection rising from the bottom edge.
+ val topGloss = Brush.verticalGradient(
+ colors = listOf(color, color.copy(alpha = color.alpha * 0.25f), Color.Transparent),
startY = 0f,
- endY = size.height * 0.5f,
+ endY = size.height * 0.55f,
+ )
+ val bottomReflection = Brush.verticalGradient(
+ colors = listOf(Color.Transparent, color.copy(alpha = color.alpha * 0.3f)),
+ startY = size.height * 0.72f,
+ endY = size.height,
)
onDrawWithContent {
drawContent()
drawRect(
- brush = highlightBrush,
+ brush = topGloss,
+ topLeft = androidx.compose.ui.geometry.Offset.Zero,
+ size = size,
+ )
+ drawRect(
+ brush = bottomReflection,
topLeft = androidx.compose.ui.geometry.Offset.Zero,
size = size,
)
diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppSidebar.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppSidebar.kt
index ce632b19..dd61c618 100644
--- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppSidebar.kt
+++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppSidebar.kt
@@ -50,9 +50,6 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import dev.chrisbanes.haze.HazeState
-import dev.chrisbanes.haze.blur.HazeColorEffect
-import dev.chrisbanes.haze.blur.blurEffect
-import dev.chrisbanes.haze.hazeEffect
import dev.krtirtho.spotube.NavigationItem
import dev.krtirtho.spotube.core.navigation.NavigationState
import dev.krtirtho.spotube.core.navigation.Navigator
@@ -85,21 +82,13 @@ fun AppSidebar(
var expanded by rememberSaveable { mutableStateOf(true) }
val width by animateDpAsState(targetValue = if (expanded) 236.dp else 86.dp)
val currentLibraryTab by libraryState.currentTab.collectAsState()
- val surfaceTint = MaterialTheme.colorScheme.surface.copy(alpha = 0.85f)
val listState = rememberLazyListState()
+
Column(
modifier = modifier
.fillMaxHeight()
- .width(width)
- .hazeEffect(hazeState) {
- blurEffect {
- blurRadius = 20.dp
- colorEffects = listOf(
- HazeColorEffect.tint(surfaceTint)
- )
- }
- },
+ .width(width),
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(modifier = Modifier.weight(1f)) {