diff --git a/app/src/main/kotlin/com/subtracks/ui/components/StretchOverscroll.kt b/app/src/main/kotlin/com/subtracks/ui/components/StretchOverscroll.kt new file mode 100644 index 00000000..b7005d2a --- /dev/null +++ b/app/src/main/kotlin/com/subtracks/ui/components/StretchOverscroll.kt @@ -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) + } + } +} 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 5f82eef0..8f762737 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt @@ -4,10 +4,12 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer 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.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars @@ -36,7 +39,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults @@ -51,8 +53,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha 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.layout.onGloballyPositioned import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextAlign 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.playback.PlaybackController import com.subtracks.ui.components.CoverArt +import com.subtracks.ui.components.stretchOverscroll import com.subtracks.ui.theme.ArtworkColors import com.subtracks.ui.theme.ArtworkTheme import com.subtracks.ui.theme.HeroGradient @@ -76,8 +83,7 @@ import kotlin.math.roundToInt private const val DOT = "\u00B7" private const val GRADIENT_SCREENS = 1.5f -private const val FADE_DISTANCE_DP = 96 -private const val CONTROLS_BLOCK_DP = 64 +private const val FADE_DISTANCE_DP = 64 @Composable fun AlbumDetailRoute( @@ -122,12 +128,13 @@ fun AlbumDetailScreen( val listState = rememberLazyListState() val density = LocalDensity.current val statusBarTop = WindowInsets.statusBars.getTop(density) + val navBarBottom = WindowInsets.navigationBars.getBottom(density) 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() } var headerHeightPx by remember { mutableFloatStateOf(0f) } var rowHeightPx by remember { mutableFloatStateOf(0f) } + var titleTopPx by remember { mutableFloatStateOf(0f) } val scrollPx by remember { derivedStateOf { val index = listState.firstVisibleItemIndex @@ -137,12 +144,10 @@ fun AlbumDetailScreen( } val barFraction by remember { derivedStateOf { - if (headerHeightPx == 0f) { + if (titleTopPx <= 0f || fadeDistancePx <= 0f) { 0f } else { - val end = headerHeightPx - controlsBlockPx - barHeightPx - val start = end - fadeDistancePx - ((scrollPx - start) / (end - start)).coerceIn(0f, 1f) + ((scrollPx - (titleTopPx - barHeightPx)) / fadeDistancePx).coerceIn(0f, 1f) } } } @@ -162,53 +167,75 @@ fun AlbumDetailScreen( .alpha(gradientAlpha), ) - Scaffold( - containerColor = Color.Transparent, - topBar = { - TopAppBar( - title = { - Text( - text = album?.name.orEmpty(), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.alpha(barFraction), - ) - }, - navigationIcon = { - IconButton(onClick = onBack, modifier = Modifier.alpha(barFraction)) { - Icon(Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "Back") - } - }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), - modifier = Modifier.background(Color.Black.copy(alpha = barFraction)), + LazyColumn( + state = listState, + 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) }, ) - }, - ) { 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 - .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( + title = { + Text( + text = album?.name.orEmpty(), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.alpha(barFraction), + ) + }, + navigationIcon = { + IconButton(onClick = onBack, modifier = Modifier.alpha(barFraction)) { + Icon(Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "Back") + } + }, + colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + modifier = + Modifier + .align(Alignment.TopStart) + .background(Color.Black.copy(alpha = barFraction)) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + ) {}, + ) } } } @@ -223,16 +250,20 @@ private fun AlbumHeader( onShuffle: () -> Unit, onDownload: () -> Unit, onMore: () -> Unit, + titleModifier: Modifier = Modifier, modifier: Modifier = Modifier, ) { 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, ) { CoverArt( ref = coverArt(album?.coverArt, false), 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)) Text( @@ -241,6 +272,7 @@ private fun AlbumHeader( textAlign = TextAlign.Center, maxLines = 2, overflow = TextOverflow.Ellipsis, + modifier = titleModifier, ) val subtitle = listOfNotNull( diff --git a/app/src/main/kotlin/com/subtracks/ui/library/AlbumsContent.kt b/app/src/main/kotlin/com/subtracks/ui/library/AlbumsContent.kt index 0717cd69..069ab97e 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/AlbumsContent.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/AlbumsContent.kt @@ -23,6 +23,7 @@ import com.subtracks.data.model.CoverArtRef import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.EmptyState import com.subtracks.ui.components.LoadingState +import com.subtracks.ui.components.stretchOverscroll 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), horizontalArrangement = 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 -> val album = items[index] diff --git a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt index 5eb13742..db4cbc0b 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt @@ -50,6 +50,7 @@ import com.subtracks.data.model.Album import com.subtracks.data.model.Artist import com.subtracks.data.model.CoverArtRef import com.subtracks.ui.components.CoverArt +import com.subtracks.ui.components.stretchOverscroll import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf @@ -128,7 +129,7 @@ fun ArtistDetailScreen( contentPadding = PaddingValues(bottom = 16.dp + navBarBottom), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize().stretchOverscroll(), ) { item(span = { GridItemSpan(maxLineSpan) }) { Box(Modifier.fillMaxWidth().padding(bottom = 12.dp)) { diff --git a/app/src/main/kotlin/com/subtracks/ui/library/ArtistsContent.kt b/app/src/main/kotlin/com/subtracks/ui/library/ArtistsContent.kt index 043e0296..28a0f5e7 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/ArtistsContent.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/ArtistsContent.kt @@ -24,6 +24,7 @@ import com.subtracks.data.model.CoverArtRef import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.EmptyState import com.subtracks.ui.components.LoadingState +import com.subtracks.ui.components.stretchOverscroll @Composable fun ArtistsContent( @@ -44,7 +45,7 @@ fun ArtistsContent( else -> { LazyColumn( - modifier = modifier.fillMaxSize(), + modifier = modifier.fillMaxSize().stretchOverscroll(), contentPadding = PaddingValues(bottom = bottomInset), ) { items(count = items.itemCount, key = items.itemKey { it.id }) { index -> diff --git a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt index 7990ed32..a4217a15 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt @@ -35,6 +35,7 @@ import com.subtracks.data.model.QueueKind import com.subtracks.data.model.SongListItem import com.subtracks.playback.PlaybackController import com.subtracks.ui.components.CoverArt +import com.subtracks.ui.components.stretchOverscroll import org.koin.compose.koinInject import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf @@ -83,7 +84,7 @@ fun PlaylistDetailScreen( ) }, ) { padding -> - LazyColumn(Modifier.padding(padding).fillMaxSize()) { + LazyColumn(Modifier.padding(padding).fillMaxSize().stretchOverscroll()) { item { Row( modifier = Modifier.fillMaxWidth().padding(16.dp), diff --git a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistsContent.kt b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistsContent.kt index b97e4549..0211b26a 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistsContent.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistsContent.kt @@ -24,6 +24,7 @@ import com.subtracks.data.model.Playlist import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.EmptyState import com.subtracks.ui.components.LoadingState +import com.subtracks.ui.components.stretchOverscroll @Composable fun PlaylistsContent( @@ -44,7 +45,7 @@ fun PlaylistsContent( else -> { LazyColumn( - modifier = modifier.fillMaxSize(), + modifier = modifier.fillMaxSize().stretchOverscroll(), contentPadding = PaddingValues(bottom = bottomInset), ) { items(count = items.itemCount, key = items.itemKey { it.id }) { index -> diff --git a/app/src/main/kotlin/com/subtracks/ui/library/SongsContent.kt b/app/src/main/kotlin/com/subtracks/ui/library/SongsContent.kt index 1253a45b..e92916b3 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/SongsContent.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/SongsContent.kt @@ -32,6 +32,7 @@ import com.subtracks.data.model.SongListItem import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.EmptyState import com.subtracks.ui.components.LoadingState +import com.subtracks.ui.components.stretchOverscroll @Composable fun SongsContent( @@ -53,7 +54,7 @@ fun SongsContent( else -> { LazyColumn( - modifier = modifier.fillMaxSize(), + modifier = modifier.fillMaxSize().stretchOverscroll(), contentPadding = PaddingValues(bottom = bottomInset), ) { items(count = items.itemCount, key = items.itemKey { it.song.id }) { index -> diff --git a/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt b/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt index 160b0d81..c9e7d35e 100644 --- a/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt @@ -51,6 +51,7 @@ import com.subtracks.data.repo.SourceRepository import com.subtracks.playback.PlaybackController import com.subtracks.ui.components.EmptyState import com.subtracks.ui.components.LoadingState +import com.subtracks.ui.components.stretchOverscroll import com.subtracks.ui.library.SongRow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -340,7 +341,7 @@ fun QueueScreen( else -> { LazyColumn( state = listState, - modifier = Modifier.padding(padding).fillMaxSize(), + modifier = Modifier.padding(padding).fillMaxSize().stretchOverscroll(), contentPadding = PaddingValues(bottom = 16.dp), ) { items(count = rows.size, key = { rows[it].id }) { index -> diff --git a/app/src/main/kotlin/com/subtracks/ui/settings/AddSourceScreen.kt b/app/src/main/kotlin/com/subtracks/ui/settings/AddSourceScreen.kt index d1279631..3b620393 100644 --- a/app/src/main/kotlin/com/subtracks/ui/settings/AddSourceScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/settings/AddSourceScreen.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.subtracks.ui.components.stretchOverscroll import org.koin.compose.viewmodel.koinViewModel @Composable @@ -105,6 +106,7 @@ fun AddSourceScreen( Modifier .padding(padding) .fillMaxSize() + .stretchOverscroll() .verticalScroll(rememberScrollState()) .padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp), diff --git a/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt index 2ff7ef49..6ff91761 100644 --- a/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.subtracks.data.model.Source +import com.subtracks.ui.components.stretchOverscroll import org.koin.compose.viewmodel.koinViewModel private val bitrateOptions = listOf(0, 24, 32, 64, 96, 128, 192, 256, 320) @@ -101,7 +102,7 @@ fun SettingsScreen( ) }, ) { padding -> - LazyColumn(Modifier.padding(padding).fillMaxSize()) { + LazyColumn(Modifier.padding(padding).fillMaxSize().stretchOverscroll()) { item { SectionHeader("Servers") } items(sources.size, key = { sources[it].id }) { index -> val source = sources[index] 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 95309104..af84b1d0 100644 --- a/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkTheme.kt +++ b/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkTheme.kt @@ -82,9 +82,9 @@ fun artworkColorsFromSeed(seed: Int): ArtworkColors { return ArtworkColors( scheme = scheme, - gradientHigh = tone(hue, (s * 0.7f).coerceAtMost(0.55f), 0.25f), - gradientLow = tone(hue, s * 0.20f, 0.04f), - accents = listOf(tone(hue, s, 0.55f), tone(hue, s * 0.7f, 0.42f)), + gradientHigh = tone(hue, (s * 0.8f).coerceAtMost(0.60f), 0.30f), + gradientLow = tone(hue, s * 0.25f, 0.04f), + accents = listOf(tone(hue, s, 0.70f), tone(hue, s * 0.85f, 0.38f)), ) } @@ -169,9 +169,9 @@ fun HeroGradient( drawRect( brush = Brush.radialGradient( - colors = listOf(accent.copy(alpha = 0.40f), accent.copy(alpha = 0f)), - center = Offset(size.width * 0.15f, size.height * 0.06f), - radius = size.width * 0.85f, + colors = listOf(accent.copy(alpha = 0.55f), accent.copy(alpha = 0f)), + center = Offset(size.width * 0.18f, size.height * 0.03f), + radius = size.width * 0.95f, ), ) } @@ -179,9 +179,19 @@ fun HeroGradient( drawRect( brush = Brush.radialGradient( - colors = listOf(accent.copy(alpha = 0.32f), accent.copy(alpha = 0f)), - center = Offset(size.width * 0.90f, size.height * 0.24f), - radius = size.width * 0.80f, + colors = listOf(accent.copy(alpha = 0.50f), accent.copy(alpha = 0f)), + center = Offset(size.width * 0.92f, size.height * 0.28f), + 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.radialGradient( colors = listOf(high.copy(alpha = 0.45f), high.copy(alpha = 0f)), - center = Offset(size.width * 0.55f, size.height * 0.45f), - radius = size.width * 0.90f, + center = Offset(size.width * 0.35f, size.height * 0.52f), + radius = size.width * 0.95f, ), ) drawRect(