stretch overscroll everywhere, overlay the album bar, richer gradient

This commit is contained in:
austinried
2026-10-04 00:29:52 +00:00
parent b9519f1a7f
commit 461eadb630
12 changed files with 208 additions and 73 deletions
@@ -0,0 +1,83 @@
package com.subtracks.ui.components
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.Velocity
import kotlinx.coroutines.launch
import kotlin.math.abs
private const val MAX_STRETCH_FRACTION = 0.35f
@Composable
fun Modifier.stretchOverscroll(): Modifier {
val overscroll = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
val connection =
remember(overscroll) {
object : NestedScrollConnection {
override fun onPreScroll(
available: Offset,
source: NestedScrollSource,
): Offset {
val current = overscroll.value
if (current == 0f || available.y == 0f) return Offset.Zero
val next = current + available.y
scope.launch {
overscroll.snapTo(if (current > 0f) next.coerceAtLeast(0f) else next.coerceAtMost(0f))
}
return available
}
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource,
): Offset {
val current = overscroll.value
if (available.y == 0f) return Offset.Zero
val next = current + available.y
scope.launch {
overscroll.snapTo(if (current > 0f) next.coerceAtLeast(0f) else next.coerceAtMost(0f))
}
return available
}
override suspend fun onPostFling(
consumed: Velocity,
available: Velocity,
): Velocity {
overscroll.animateTo(0f, spring(stiffness = Spring.StiffnessMediumLow))
return available
}
}
}
return this
.nestedScroll(connection)
.graphicsLayer {
val raw = overscroll.value
if (raw == 0f) {
scaleY = 1f
translationY = 0f
} else {
val limit = size.height * MAX_STRETCH_FRACTION
val capped = raw.coerceIn(-limit, limit)
val resisted = capped / (1f + abs(capped) / (size.height * 0.5f))
scaleY = 1f + abs(resisted) / size.height
translationY = resisted
transformOrigin = TransformOrigin(0.5f, if (resisted > 0f) 0f else 1f)
}
}
}
@@ -4,10 +4,12 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
@@ -15,6 +17,7 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.statusBars
@@ -36,7 +39,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
@@ -51,8 +53,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInParent
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -65,6 +71,7 @@ import com.subtracks.data.model.QueueKind
import com.subtracks.data.model.Song import com.subtracks.data.model.Song
import com.subtracks.playback.PlaybackController import com.subtracks.playback.PlaybackController
import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.CoverArt
import com.subtracks.ui.components.stretchOverscroll
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
@@ -76,8 +83,7 @@ import kotlin.math.roundToInt
private const val DOT = "\u00B7" private const val DOT = "\u00B7"
private const val GRADIENT_SCREENS = 1.5f private const val GRADIENT_SCREENS = 1.5f
private const val FADE_DISTANCE_DP = 96 private const val FADE_DISTANCE_DP = 64
private const val CONTROLS_BLOCK_DP = 64
@Composable @Composable
fun AlbumDetailRoute( fun AlbumDetailRoute(
@@ -122,12 +128,13 @@ fun AlbumDetailScreen(
val listState = rememberLazyListState() val listState = rememberLazyListState()
val density = LocalDensity.current val density = LocalDensity.current
val statusBarTop = WindowInsets.statusBars.getTop(density) val statusBarTop = WindowInsets.statusBars.getTop(density)
val navBarBottom = WindowInsets.navigationBars.getBottom(density)
val barHeightPx = statusBarTop + with(density) { TopAppBarDefaults.TopAppBarExpandedHeight.toPx() } val barHeightPx = statusBarTop + with(density) { TopAppBarDefaults.TopAppBarExpandedHeight.toPx() }
val controlsBlockPx = with(density) { CONTROLS_BLOCK_DP.dp.toPx() }
val fadeDistancePx = with(density) { FADE_DISTANCE_DP.dp.toPx() } val fadeDistancePx = with(density) { FADE_DISTANCE_DP.dp.toPx() }
var headerHeightPx by remember { mutableFloatStateOf(0f) } var headerHeightPx by remember { mutableFloatStateOf(0f) }
var rowHeightPx by remember { mutableFloatStateOf(0f) } var rowHeightPx by remember { mutableFloatStateOf(0f) }
var titleTopPx by remember { mutableFloatStateOf(0f) }
val scrollPx by remember { val scrollPx by remember {
derivedStateOf { derivedStateOf {
val index = listState.firstVisibleItemIndex val index = listState.firstVisibleItemIndex
@@ -137,12 +144,10 @@ fun AlbumDetailScreen(
} }
val barFraction by remember { val barFraction by remember {
derivedStateOf { derivedStateOf {
if (headerHeightPx == 0f) { if (titleTopPx <= 0f || fadeDistancePx <= 0f) {
0f 0f
} else { } else {
val end = headerHeightPx - controlsBlockPx - barHeightPx ((scrollPx - (titleTopPx - barHeightPx)) / fadeDistancePx).coerceIn(0f, 1f)
val start = end - fadeDistancePx
((scrollPx - start) / (end - start)).coerceIn(0f, 1f)
} }
} }
} }
@@ -162,9 +167,51 @@ fun AlbumDetailScreen(
.alpha(gradientAlpha), .alpha(gradientAlpha),
) )
Scaffold( LazyColumn(
containerColor = Color.Transparent, state = listState,
topBar = { contentPadding =
PaddingValues(
bottom = 16.dp + with(density) { navBarBottom.toDp() },
),
modifier = Modifier.fillMaxSize().stretchOverscroll(),
) {
item {
AlbumHeader(
album = album,
hasSongs = songs.isNotEmpty(),
coverArt = coverArt,
onPlay = { onSongClick(0) },
onShuffle = onShuffle,
onDownload = onDownload,
onMore = onMore,
titleModifier = Modifier.onGloballyPositioned { titleTopPx = it.positionInParent().y },
modifier = Modifier.onSizeChanged { headerHeightPx = it.height.toFloat() },
)
}
itemsIndexed(songs, key = { _, song -> song.id }) { index, song ->
SongRow(
song = song,
isPlaying = song.id == playingSongId,
modifier =
Modifier
.onSizeChanged { rowHeightPx = it.height.toFloat() }
.clickable { onSongClick(index) },
)
}
}
Box(
Modifier
.align(Alignment.TopStart)
.fillMaxWidth()
.height(with(density) { (barHeightPx + 24.dp.toPx()).toDp() })
.background(
Brush.verticalGradient(
listOf(Color.Black.copy(alpha = 0.8f), Color.Transparent),
),
).alpha(1f - barFraction),
)
TopAppBar( TopAppBar(
title = { title = {
Text( Text(
@@ -180,39 +227,19 @@ fun AlbumDetailScreen(
} }
}, },
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
modifier = Modifier.background(Color.Black.copy(alpha = barFraction)),
)
},
) { padding ->
LazyColumn(state = listState, modifier = Modifier.padding(padding).fillMaxSize()) {
item {
AlbumHeader(
album = album,
hasSongs = songs.isNotEmpty(),
coverArt = coverArt,
onPlay = { onSongClick(0) },
onShuffle = onShuffle,
onDownload = onDownload,
onMore = onMore,
modifier = Modifier.onSizeChanged { headerHeightPx = it.height.toFloat() },
)
}
itemsIndexed(songs, key = { _, song -> song.id }) { index, song ->
SongRow(
song = song,
isPlaying = song.id == playingSongId,
modifier = modifier =
Modifier Modifier
.onSizeChanged { rowHeightPx = it.height.toFloat() } .align(Alignment.TopStart)
.clickable { onSongClick(index) }, .background(Color.Black.copy(alpha = barFraction))
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
) {},
) )
} }
} }
} }
} }
}
}
}
@Composable @Composable
private fun AlbumHeader( private fun AlbumHeader(
@@ -223,16 +250,20 @@ private fun AlbumHeader(
onShuffle: () -> Unit, onShuffle: () -> Unit,
onDownload: () -> Unit, onDownload: () -> Unit,
onMore: () -> Unit, onMore: () -> Unit,
titleModifier: Modifier = Modifier,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Column( Column(
modifier = modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 16.dp), modifier =
modifier
.fillMaxWidth()
.padding(start = 24.dp, end = 24.dp, top = 8.dp, bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
CoverArt( CoverArt(
ref = coverArt(album?.coverArt, false), ref = coverArt(album?.coverArt, false),
name = album?.name.orEmpty(), name = album?.name.orEmpty(),
modifier = Modifier.fillMaxWidth(0.78f).aspectRatio(1f).clip(RoundedCornerShape(2.dp)), modifier = Modifier.fillMaxWidth(0.86f).aspectRatio(1f).clip(RoundedCornerShape(2.dp)),
) )
Spacer(Modifier.height(20.dp)) Spacer(Modifier.height(20.dp))
Text( Text(
@@ -241,6 +272,7 @@ private fun AlbumHeader(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = titleModifier,
) )
val subtitle = val subtitle =
listOfNotNull( listOfNotNull(
@@ -23,6 +23,7 @@ import com.subtracks.data.model.CoverArtRef
import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.CoverArt
import com.subtracks.ui.components.EmptyState import com.subtracks.ui.components.EmptyState
import com.subtracks.ui.components.LoadingState import com.subtracks.ui.components.LoadingState
import com.subtracks.ui.components.stretchOverscroll
const val ALBUM_COVER_TAG = "album-cover" const val ALBUM_COVER_TAG = "album-cover"
@@ -49,7 +50,7 @@ fun AlbumsContent(
contentPadding = PaddingValues(start = 8.dp, top = 8.dp, end = 8.dp, bottom = bottomInset + 8.dp), contentPadding = PaddingValues(start = 8.dp, top = 8.dp, end = 8.dp, bottom = bottomInset + 8.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize().stretchOverscroll(),
) { ) {
items(count = items.itemCount, key = items.itemKey { it.id }) { index -> items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
val album = items[index] val album = items[index]
@@ -50,6 +50,7 @@ import com.subtracks.data.model.Album
import com.subtracks.data.model.Artist import com.subtracks.data.model.Artist
import com.subtracks.data.model.CoverArtRef import com.subtracks.data.model.CoverArtRef
import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.CoverArt
import com.subtracks.ui.components.stretchOverscroll
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
@@ -128,7 +129,7 @@ fun ArtistDetailScreen(
contentPadding = PaddingValues(bottom = 16.dp + navBarBottom), contentPadding = PaddingValues(bottom = 16.dp + navBarBottom),
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize().stretchOverscroll(),
) { ) {
item(span = { GridItemSpan(maxLineSpan) }) { item(span = { GridItemSpan(maxLineSpan) }) {
Box(Modifier.fillMaxWidth().padding(bottom = 12.dp)) { Box(Modifier.fillMaxWidth().padding(bottom = 12.dp)) {
@@ -24,6 +24,7 @@ import com.subtracks.data.model.CoverArtRef
import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.CoverArt
import com.subtracks.ui.components.EmptyState import com.subtracks.ui.components.EmptyState
import com.subtracks.ui.components.LoadingState import com.subtracks.ui.components.LoadingState
import com.subtracks.ui.components.stretchOverscroll
@Composable @Composable
fun ArtistsContent( fun ArtistsContent(
@@ -44,7 +45,7 @@ fun ArtistsContent(
else -> { else -> {
LazyColumn( LazyColumn(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize().stretchOverscroll(),
contentPadding = PaddingValues(bottom = bottomInset), contentPadding = PaddingValues(bottom = bottomInset),
) { ) {
items(count = items.itemCount, key = items.itemKey { it.id }) { index -> items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
@@ -35,6 +35,7 @@ import com.subtracks.data.model.QueueKind
import com.subtracks.data.model.SongListItem import com.subtracks.data.model.SongListItem
import com.subtracks.playback.PlaybackController import com.subtracks.playback.PlaybackController
import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.CoverArt
import com.subtracks.ui.components.stretchOverscroll
import org.koin.compose.koinInject import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
@@ -83,7 +84,7 @@ fun PlaylistDetailScreen(
) )
}, },
) { padding -> ) { padding ->
LazyColumn(Modifier.padding(padding).fillMaxSize()) { LazyColumn(Modifier.padding(padding).fillMaxSize().stretchOverscroll()) {
item { item {
Row( Row(
modifier = Modifier.fillMaxWidth().padding(16.dp), modifier = Modifier.fillMaxWidth().padding(16.dp),
@@ -24,6 +24,7 @@ import com.subtracks.data.model.Playlist
import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.CoverArt
import com.subtracks.ui.components.EmptyState import com.subtracks.ui.components.EmptyState
import com.subtracks.ui.components.LoadingState import com.subtracks.ui.components.LoadingState
import com.subtracks.ui.components.stretchOverscroll
@Composable @Composable
fun PlaylistsContent( fun PlaylistsContent(
@@ -44,7 +45,7 @@ fun PlaylistsContent(
else -> { else -> {
LazyColumn( LazyColumn(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize().stretchOverscroll(),
contentPadding = PaddingValues(bottom = bottomInset), contentPadding = PaddingValues(bottom = bottomInset),
) { ) {
items(count = items.itemCount, key = items.itemKey { it.id }) { index -> items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
@@ -32,6 +32,7 @@ import com.subtracks.data.model.SongListItem
import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.CoverArt
import com.subtracks.ui.components.EmptyState import com.subtracks.ui.components.EmptyState
import com.subtracks.ui.components.LoadingState import com.subtracks.ui.components.LoadingState
import com.subtracks.ui.components.stretchOverscroll
@Composable @Composable
fun SongsContent( fun SongsContent(
@@ -53,7 +54,7 @@ fun SongsContent(
else -> { else -> {
LazyColumn( LazyColumn(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize().stretchOverscroll(),
contentPadding = PaddingValues(bottom = bottomInset), contentPadding = PaddingValues(bottom = bottomInset),
) { ) {
items(count = items.itemCount, key = items.itemKey { it.song.id }) { index -> items(count = items.itemCount, key = items.itemKey { it.song.id }) { index ->
@@ -51,6 +51,7 @@ import com.subtracks.data.repo.SourceRepository
import com.subtracks.playback.PlaybackController import com.subtracks.playback.PlaybackController
import com.subtracks.ui.components.EmptyState import com.subtracks.ui.components.EmptyState
import com.subtracks.ui.components.LoadingState import com.subtracks.ui.components.LoadingState
import com.subtracks.ui.components.stretchOverscroll
import com.subtracks.ui.library.SongRow import com.subtracks.ui.library.SongRow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -340,7 +341,7 @@ fun QueueScreen(
else -> { else -> {
LazyColumn( LazyColumn(
state = listState, state = listState,
modifier = Modifier.padding(padding).fillMaxSize(), modifier = Modifier.padding(padding).fillMaxSize().stretchOverscroll(),
contentPadding = PaddingValues(bottom = 16.dp), contentPadding = PaddingValues(bottom = 16.dp),
) { ) {
items(count = rows.size, key = { rows[it].id }) { index -> items(count = rows.size, key = { rows[it].id }) { index ->
@@ -32,6 +32,7 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.subtracks.ui.components.stretchOverscroll
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
@Composable @Composable
@@ -105,6 +106,7 @@ fun AddSourceScreen(
Modifier Modifier
.padding(padding) .padding(padding)
.fillMaxSize() .fillMaxSize()
.stretchOverscroll()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(16.dp), .padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
@@ -40,6 +40,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.subtracks.data.model.Source import com.subtracks.data.model.Source
import com.subtracks.ui.components.stretchOverscroll
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
private val bitrateOptions = listOf(0, 24, 32, 64, 96, 128, 192, 256, 320) private val bitrateOptions = listOf(0, 24, 32, 64, 96, 128, 192, 256, 320)
@@ -101,7 +102,7 @@ fun SettingsScreen(
) )
}, },
) { padding -> ) { padding ->
LazyColumn(Modifier.padding(padding).fillMaxSize()) { LazyColumn(Modifier.padding(padding).fillMaxSize().stretchOverscroll()) {
item { SectionHeader("Servers") } item { SectionHeader("Servers") }
items(sources.size, key = { sources[it].id }) { index -> items(sources.size, key = { sources[it].id }) { index ->
val source = sources[index] val source = sources[index]
@@ -82,9 +82,9 @@ fun artworkColorsFromSeed(seed: Int): ArtworkColors {
return ArtworkColors( return ArtworkColors(
scheme = scheme, scheme = scheme,
gradientHigh = tone(hue, (s * 0.7f).coerceAtMost(0.55f), 0.25f), gradientHigh = tone(hue, (s * 0.8f).coerceAtMost(0.60f), 0.30f),
gradientLow = tone(hue, s * 0.20f, 0.04f), gradientLow = tone(hue, s * 0.25f, 0.04f),
accents = listOf(tone(hue, s, 0.55f), tone(hue, s * 0.7f, 0.42f)), accents = listOf(tone(hue, s, 0.70f), tone(hue, s * 0.85f, 0.38f)),
) )
} }
@@ -169,9 +169,9 @@ fun HeroGradient(
drawRect( drawRect(
brush = brush =
Brush.radialGradient( Brush.radialGradient(
colors = listOf(accent.copy(alpha = 0.40f), accent.copy(alpha = 0f)), colors = listOf(accent.copy(alpha = 0.55f), accent.copy(alpha = 0f)),
center = Offset(size.width * 0.15f, size.height * 0.06f), center = Offset(size.width * 0.18f, size.height * 0.03f),
radius = size.width * 0.85f, radius = size.width * 0.95f,
), ),
) )
} }
@@ -179,9 +179,19 @@ fun HeroGradient(
drawRect( drawRect(
brush = brush =
Brush.radialGradient( Brush.radialGradient(
colors = listOf(accent.copy(alpha = 0.32f), accent.copy(alpha = 0f)), colors = listOf(accent.copy(alpha = 0.50f), accent.copy(alpha = 0f)),
center = Offset(size.width * 0.90f, size.height * 0.24f), center = Offset(size.width * 0.92f, size.height * 0.28f),
radius = size.width * 0.80f, radius = size.width * 0.85f,
),
)
}
accents.getOrNull(0)?.let { accent ->
drawRect(
brush =
Brush.radialGradient(
colors = listOf(accent.copy(alpha = 0.30f), accent.copy(alpha = 0f)),
center = Offset(size.width * 0.85f, size.height * 0.02f),
radius = size.width * 0.70f,
), ),
) )
} }
@@ -189,8 +199,8 @@ fun HeroGradient(
brush = brush =
Brush.radialGradient( Brush.radialGradient(
colors = listOf(high.copy(alpha = 0.45f), high.copy(alpha = 0f)), colors = listOf(high.copy(alpha = 0.45f), high.copy(alpha = 0f)),
center = Offset(size.width * 0.55f, size.height * 0.45f), center = Offset(size.width * 0.35f, size.height * 0.52f),
radius = size.width * 0.90f, radius = size.width * 0.95f,
), ),
) )
drawRect( drawRect(