From 121d793b726bc8781118927842ece2c938dc236f Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:15:48 +0000 Subject: [PATCH] sample the bar colour from the gradient, saturate the accents --- .../subtracks/ui/library/AlbumDetailScreen.kt | 26 ++++---------- .../com/subtracks/ui/theme/ArtworkTheme.kt | 34 +++++++++++++++---- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt index 9d0f1149..a1a566ba 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt @@ -78,6 +78,7 @@ import com.subtracks.ui.components.rememberViewportFill import com.subtracks.ui.theme.ArtworkColors import com.subtracks.ui.theme.ArtworkTheme import com.subtracks.ui.theme.HeroGradient +import com.subtracks.ui.theme.gradientColorAt import com.subtracks.ui.theme.rememberArtworkColors import org.koin.compose.koinInject import org.koin.compose.viewmodel.koinViewModel @@ -130,8 +131,9 @@ fun AlbumDetailScreen( BoxWithConstraints(modifier.fillMaxSize().background(Color.Black)) { val listState = rememberLazyListState() val fill = rememberViewportFill(listState) - val gradientHeight = maxHeight * GRADIENT_SCREENS val density = LocalDensity.current + val gradientHeight = maxHeight * GRADIENT_SCREENS + val gradientHeightPx = with(density) { gradientHeight.toPx() } val statusBarTop = WindowInsets.statusBars.getTop(density) val navBarBottom = WindowInsets.navigationBars.getBottom(density) val statusBarDp = with(density) { statusBarTop.toDp() } @@ -162,6 +164,9 @@ fun AlbumDetailScreen( animationSpec = tween(durationMillis = 600), label = "gradientAlpha", ) + val barColor = + artwork?.gradientColorAt(((scrollPx + barHeightPx / 2f) / gradientHeightPx).coerceIn(0f, 1f)) + ?: Color.Black HeroGradient( colors = artwork, @@ -223,24 +228,6 @@ fun AlbumDetailScreen( ).alpha(1f - barFraction), ) - Box( - Modifier - .align(Alignment.TopStart) - .fillMaxWidth() - .height(with(density) { barHeightPx.toDp() }) - .clipToBounds() - .alpha(barFraction), - ) { - HeroGradient( - colors = artwork, - modifier = - Modifier - .fillMaxWidth() - .requiredHeight(gradientHeight) - .offset { IntOffset(0, -scrollPx.roundToInt()) }, - ) - } - TopAppBar( title = { Text( @@ -260,6 +247,7 @@ fun AlbumDetailScreen( modifier = Modifier .align(Alignment.TopStart) + .background(barColor.copy(alpha = barFraction)) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null, diff --git a/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkTheme.kt b/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkTheme.kt index 2948783b..147a6475 100644 --- a/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkTheme.kt +++ b/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkTheme.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.lerp import androidx.compose.ui.graphics.luminance import androidx.palette.graphics.Palette import coil3.SingletonImageLoader @@ -92,20 +93,39 @@ fun artworkColorsFromSeeds( val secondary = secondarySeed?.let { seed -> - val color = Color(seed or 0xFF000000.toInt()) - val (sh, ss, sl) = color.toHsl() - if (sl > 0.55f) Color.hsl(sh, ss, 0.30f) else color - } ?: tone(hue, s, 0.08f) + val (sh, ss, sl) = Color(seed or 0xFF000000.toInt()).toHsl() + Color.hsl( + blendHue(sh, hue, 0.5f), + ss.coerceAtLeast(s * 0.7f).coerceAtMost(0.9f), + sl.coerceIn(0.34f, 0.56f), + ) + } ?: tone(hue, s, 0.40f) return ArtworkColors( scheme = scheme, - gradientHigh = tone(hue, (s * 0.6f).coerceAtMost(0.50f), 0.22f), - gradientLow = tone(hue, s * 0.30f, 0.03f), - accents = listOf(tone(hue, s * 0.8f, 0.90f), secondary), + gradientHigh = tone(hue, (s * 0.85f).coerceAtMost(0.65f), 0.30f), + gradientLow = tone(hue, s * 0.45f, 0.04f), + accents = listOf(tone(hue, s, 0.72f), secondary), blobSeed = primarySeed, ) } +fun ArtworkColors.gradientColorAt(fraction: Float): Color { + val t = fraction.coerceIn(0f, 1f) + val base = lerp(gradientHigh, gradientLow, t) + val fade = ((t - GRADIENT_FADE_START) / (1f - GRADIENT_FADE_START)).coerceIn(0f, 1f) + return lerp(base, Color.Black, fade) +} + +private fun blendHue( + from: Float, + to: Float, + fraction: Float, +): Float { + val delta = ((to - from + 540f) % 360f) - 180f + return (from + delta * fraction + 360f) % 360f +} + @Composable fun rememberArtworkColors(ref: CoverArtRef?): ArtworkColors? { val seeds by rememberArtworkSeed(ref)