mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
stretch overscroll everywhere, overlay the album bar, richer gradient
This commit is contained in:
@@ -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.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(
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user