sample the bar colour from the gradient, saturate the accents

This commit is contained in:
austinried
2026-10-04 00:29:53 +00:00
parent 139e12af75
commit 121d793b72
2 changed files with 34 additions and 26 deletions
@@ -78,6 +78,7 @@ import com.subtracks.ui.components.rememberViewportFill
import com.subtracks.ui.theme.ArtworkColors import com.subtracks.ui.theme.ArtworkColors
import com.subtracks.ui.theme.ArtworkTheme import com.subtracks.ui.theme.ArtworkTheme
import com.subtracks.ui.theme.HeroGradient import com.subtracks.ui.theme.HeroGradient
import com.subtracks.ui.theme.gradientColorAt
import com.subtracks.ui.theme.rememberArtworkColors import com.subtracks.ui.theme.rememberArtworkColors
import org.koin.compose.koinInject import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
@@ -130,8 +131,9 @@ fun AlbumDetailScreen(
BoxWithConstraints(modifier.fillMaxSize().background(Color.Black)) { BoxWithConstraints(modifier.fillMaxSize().background(Color.Black)) {
val listState = rememberLazyListState() val listState = rememberLazyListState()
val fill = rememberViewportFill(listState) val fill = rememberViewportFill(listState)
val gradientHeight = maxHeight * GRADIENT_SCREENS
val density = LocalDensity.current val density = LocalDensity.current
val gradientHeight = maxHeight * GRADIENT_SCREENS
val gradientHeightPx = with(density) { gradientHeight.toPx() }
val statusBarTop = WindowInsets.statusBars.getTop(density) val statusBarTop = WindowInsets.statusBars.getTop(density)
val navBarBottom = WindowInsets.navigationBars.getBottom(density) val navBarBottom = WindowInsets.navigationBars.getBottom(density)
val statusBarDp = with(density) { statusBarTop.toDp() } val statusBarDp = with(density) { statusBarTop.toDp() }
@@ -162,6 +164,9 @@ fun AlbumDetailScreen(
animationSpec = tween(durationMillis = 600), animationSpec = tween(durationMillis = 600),
label = "gradientAlpha", label = "gradientAlpha",
) )
val barColor =
artwork?.gradientColorAt(((scrollPx + barHeightPx / 2f) / gradientHeightPx).coerceIn(0f, 1f))
?: Color.Black
HeroGradient( HeroGradient(
colors = artwork, colors = artwork,
@@ -223,24 +228,6 @@ fun AlbumDetailScreen(
).alpha(1f - barFraction), ).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( TopAppBar(
title = { title = {
Text( Text(
@@ -260,6 +247,7 @@ fun AlbumDetailScreen(
modifier = modifier =
Modifier Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
.background(barColor.copy(alpha = barFraction))
.clickable( .clickable(
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
indication = null, indication = null,
@@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.luminance
import androidx.palette.graphics.Palette import androidx.palette.graphics.Palette
import coil3.SingletonImageLoader import coil3.SingletonImageLoader
@@ -92,20 +93,39 @@ fun artworkColorsFromSeeds(
val secondary = val secondary =
secondarySeed?.let { seed -> secondarySeed?.let { seed ->
val color = Color(seed or 0xFF000000.toInt()) val (sh, ss, sl) = Color(seed or 0xFF000000.toInt()).toHsl()
val (sh, ss, sl) = color.toHsl() Color.hsl(
if (sl > 0.55f) Color.hsl(sh, ss, 0.30f) else color blendHue(sh, hue, 0.5f),
} ?: tone(hue, s, 0.08f) ss.coerceAtLeast(s * 0.7f).coerceAtMost(0.9f),
sl.coerceIn(0.34f, 0.56f),
)
} ?: tone(hue, s, 0.40f)
return ArtworkColors( return ArtworkColors(
scheme = scheme, scheme = scheme,
gradientHigh = tone(hue, (s * 0.6f).coerceAtMost(0.50f), 0.22f), gradientHigh = tone(hue, (s * 0.85f).coerceAtMost(0.65f), 0.30f),
gradientLow = tone(hue, s * 0.30f, 0.03f), gradientLow = tone(hue, s * 0.45f, 0.04f),
accents = listOf(tone(hue, s * 0.8f, 0.90f), secondary), accents = listOf(tone(hue, s, 0.72f), secondary),
blobSeed = primarySeed, 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 @Composable
fun rememberArtworkColors(ref: CoverArtRef?): ArtworkColors? { fun rememberArtworkColors(ref: CoverArtRef?): ArtworkColors? {
val seeds by rememberArtworkSeed(ref) val seeds by rememberArtworkSeed(ref)