mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
fix overscroll stretch, pad the album hero, push the gradient fade down
This commit is contained in:
@@ -3,7 +3,10 @@ package com.subtracks.ui.components
|
|||||||
import androidx.compose.animation.core.Animatable
|
import androidx.compose.animation.core.Animatable
|
||||||
import androidx.compose.animation.core.Spring
|
import androidx.compose.animation.core.Spring
|
||||||
import androidx.compose.animation.core.spring
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.foundation.LocalOverscrollFactory
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -17,10 +20,13 @@ import androidx.compose.ui.unit.Velocity
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
private const val MAX_STRETCH_FRACTION = 0.35f
|
private const val MAX_STRETCH_FRACTION = 0.30f
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Modifier.stretchOverscroll(): Modifier {
|
fun StretchOverscroll(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
val overscroll = remember { Animatable(0f) }
|
val overscroll = remember { Animatable(0f) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
@@ -58,26 +64,33 @@ fun Modifier.stretchOverscroll(): Modifier {
|
|||||||
consumed: Velocity,
|
consumed: Velocity,
|
||||||
available: Velocity,
|
available: Velocity,
|
||||||
): Velocity {
|
): Velocity {
|
||||||
overscroll.animateTo(0f, spring(stiffness = Spring.StiffnessMediumLow))
|
overscroll.animateTo(0f, spring(stiffness = Spring.StiffnessMedium))
|
||||||
return available
|
return available
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this
|
CompositionLocalProvider(LocalOverscrollFactory provides null) {
|
||||||
.nestedScroll(connection)
|
Box(
|
||||||
.graphicsLayer {
|
modifier =
|
||||||
val raw = overscroll.value
|
modifier
|
||||||
if (raw == 0f) {
|
.nestedScroll(connection)
|
||||||
scaleY = 1f
|
.graphicsLayer {
|
||||||
translationY = 0f
|
val raw = overscroll.value
|
||||||
} else {
|
if (raw == 0f) {
|
||||||
val limit = size.height * MAX_STRETCH_FRACTION
|
scaleY = 1f
|
||||||
val capped = raw.coerceIn(-limit, limit)
|
translationY = 0f
|
||||||
val resisted = capped / (1f + abs(capped) / (size.height * 0.5f))
|
} else {
|
||||||
scaleY = 1f + abs(resisted) / size.height
|
val limit = size.height * MAX_STRETCH_FRACTION
|
||||||
translationY = resisted
|
val capped = raw.coerceIn(-limit, limit)
|
||||||
transformOrigin = TransformOrigin(0.5f, if (resisted > 0f) 0f else 1f)
|
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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
content()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ 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
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
@@ -71,7 +72,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.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
|
||||||
@@ -82,7 +83,7 @@ import org.koin.core.parameter.parametersOf
|
|||||||
import kotlin.math.roundToInt
|
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 = 2.0f
|
||||||
private const val FADE_DISTANCE_DP = 64
|
private const val FADE_DISTANCE_DP = 64
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -129,6 +130,7 @@ fun AlbumDetailScreen(
|
|||||||
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 navBarBottom = WindowInsets.navigationBars.getBottom(density)
|
||||||
|
val statusBarDp = with(density) { statusBarTop.toDp() }
|
||||||
val barHeightPx = statusBarTop + with(density) { TopAppBarDefaults.TopAppBarExpandedHeight.toPx() }
|
val barHeightPx = statusBarTop + with(density) { TopAppBarDefaults.TopAppBarExpandedHeight.toPx() }
|
||||||
val fadeDistancePx = with(density) { FADE_DISTANCE_DP.dp.toPx() }
|
val fadeDistancePx = with(density) { FADE_DISTANCE_DP.dp.toPx() }
|
||||||
|
|
||||||
@@ -167,36 +169,39 @@ fun AlbumDetailScreen(
|
|||||||
.alpha(gradientAlpha),
|
.alpha(gradientAlpha),
|
||||||
)
|
)
|
||||||
|
|
||||||
LazyColumn(
|
StretchOverscroll(modifier = Modifier.fillMaxSize()) {
|
||||||
state = listState,
|
LazyColumn(
|
||||||
contentPadding =
|
state = listState,
|
||||||
PaddingValues(
|
contentPadding =
|
||||||
bottom = 16.dp + with(density) { navBarBottom.toDp() },
|
PaddingValues(
|
||||||
),
|
bottom = 16.dp + with(density) { navBarBottom.toDp() },
|
||||||
modifier = Modifier.fillMaxSize().stretchOverscroll(),
|
),
|
||||||
) {
|
modifier = Modifier.fillMaxSize(),
|
||||||
item {
|
) {
|
||||||
AlbumHeader(
|
item {
|
||||||
album = album,
|
AlbumHeader(
|
||||||
hasSongs = songs.isNotEmpty(),
|
album = album,
|
||||||
coverArt = coverArt,
|
hasSongs = songs.isNotEmpty(),
|
||||||
onPlay = { onSongClick(0) },
|
coverArt = coverArt,
|
||||||
onShuffle = onShuffle,
|
onPlay = { onSongClick(0) },
|
||||||
onDownload = onDownload,
|
onShuffle = onShuffle,
|
||||||
onMore = onMore,
|
onDownload = onDownload,
|
||||||
titleModifier = Modifier.onGloballyPositioned { titleTopPx = it.positionInParent().y },
|
onMore = onMore,
|
||||||
modifier = Modifier.onSizeChanged { headerHeightPx = it.height.toFloat() },
|
topInset = statusBarDp,
|
||||||
)
|
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,
|
itemsIndexed(songs, key = { _, song -> song.id }) { index, song ->
|
||||||
isPlaying = song.id == playingSongId,
|
SongRow(
|
||||||
modifier =
|
song = song,
|
||||||
Modifier
|
isPlaying = song.id == playingSongId,
|
||||||
.onSizeChanged { rowHeightPx = it.height.toFloat() }
|
modifier =
|
||||||
.clickable { onSongClick(index) },
|
Modifier
|
||||||
)
|
.onSizeChanged { rowHeightPx = it.height.toFloat() }
|
||||||
|
.clickable { onSongClick(index) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +209,7 @@ fun AlbumDetailScreen(
|
|||||||
Modifier
|
Modifier
|
||||||
.align(Alignment.TopStart)
|
.align(Alignment.TopStart)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(with(density) { (barHeightPx + 24.dp.toPx()).toDp() })
|
.height(statusBarDp + 8.dp)
|
||||||
.background(
|
.background(
|
||||||
Brush.verticalGradient(
|
Brush.verticalGradient(
|
||||||
listOf(Color.Black.copy(alpha = 0.8f), Color.Transparent),
|
listOf(Color.Black.copy(alpha = 0.8f), Color.Transparent),
|
||||||
@@ -216,6 +221,7 @@ fun AlbumDetailScreen(
|
|||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
text = album?.name.orEmpty(),
|
text = album?.name.orEmpty(),
|
||||||
|
style = MaterialTheme.typography.headlineLarge,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.alpha(barFraction),
|
modifier = Modifier.alpha(barFraction),
|
||||||
@@ -250,6 +256,7 @@ private fun AlbumHeader(
|
|||||||
onShuffle: () -> Unit,
|
onShuffle: () -> Unit,
|
||||||
onDownload: () -> Unit,
|
onDownload: () -> Unit,
|
||||||
onMore: () -> Unit,
|
onMore: () -> Unit,
|
||||||
|
topInset: Dp,
|
||||||
titleModifier: Modifier = Modifier,
|
titleModifier: Modifier = Modifier,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
@@ -257,7 +264,7 @@ private fun AlbumHeader(
|
|||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 24.dp, end = 24.dp, top = 8.dp, bottom = 16.dp),
|
.padding(start = 24.dp, end = 24.dp, top = topInset + 8.dp, bottom = 16.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
CoverArt(
|
CoverArt(
|
||||||
@@ -268,7 +275,7 @@ private fun AlbumHeader(
|
|||||||
Spacer(Modifier.height(20.dp))
|
Spacer(Modifier.height(20.dp))
|
||||||
Text(
|
Text(
|
||||||
text = album?.name.orEmpty(),
|
text = album?.name.orEmpty(),
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineLarge,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
|||||||
@@ -23,7 +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
|
import com.subtracks.ui.components.StretchOverscroll
|
||||||
|
|
||||||
const val ALBUM_COVER_TAG = "album-cover"
|
const val ALBUM_COVER_TAG = "album-cover"
|
||||||
|
|
||||||
@@ -45,27 +45,29 @@ fun AlbumsContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
LazyVerticalGrid(
|
StretchOverscroll(modifier = modifier.fillMaxSize()) {
|
||||||
columns = GridCells.Fixed(3),
|
LazyVerticalGrid(
|
||||||
contentPadding = PaddingValues(start = 8.dp, top = 8.dp, end = 8.dp, bottom = bottomInset + 8.dp),
|
columns = GridCells.Fixed(3),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
contentPadding = PaddingValues(start = 8.dp, top = 8.dp, end = 8.dp, bottom = bottomInset + 8.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
modifier = modifier.fillMaxSize().stretchOverscroll(),
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
) {
|
modifier = Modifier.fillMaxSize(),
|
||||||
items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
|
) {
|
||||||
val album = items[index]
|
items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
|
||||||
if (album != null) {
|
val album = items[index]
|
||||||
CoverArt(
|
if (album != null) {
|
||||||
ref = coverArt(album.coverArt, true),
|
CoverArt(
|
||||||
name = album.name,
|
ref = coverArt(album.coverArt, true),
|
||||||
modifier =
|
name = album.name,
|
||||||
Modifier
|
modifier =
|
||||||
.fillMaxWidth()
|
Modifier
|
||||||
.aspectRatio(1f)
|
.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(2.dp))
|
.aspectRatio(1f)
|
||||||
.clickable { onAlbumClick(album) }
|
.clip(RoundedCornerShape(2.dp))
|
||||||
.testTag(ALBUM_COVER_TAG),
|
.clickable { onAlbumClick(album) }
|
||||||
)
|
.testTag(ALBUM_COVER_TAG),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +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 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
|
||||||
|
|
||||||
@@ -123,58 +123,60 @@ fun ArtistDetailScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Box(modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
|
Box(modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
|
||||||
LazyVerticalGrid(
|
StretchOverscroll(modifier = Modifier.fillMaxSize()) {
|
||||||
state = listState,
|
LazyVerticalGrid(
|
||||||
columns = GridCells.Fixed(2),
|
state = listState,
|
||||||
contentPadding = PaddingValues(bottom = 16.dp + navBarBottom),
|
columns = GridCells.Fixed(2),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
contentPadding = PaddingValues(bottom = 16.dp + navBarBottom),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
modifier = Modifier.fillMaxSize().stretchOverscroll(),
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
) {
|
modifier = Modifier.fillMaxSize(),
|
||||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
) {
|
||||||
Box(Modifier.fillMaxWidth().padding(bottom = 12.dp)) {
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
CoverArt(
|
Box(Modifier.fillMaxWidth().padding(bottom = 12.dp)) {
|
||||||
ref = art,
|
CoverArt(
|
||||||
name = artist?.name.orEmpty(),
|
ref = art,
|
||||||
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
|
name = artist?.name.orEmpty(),
|
||||||
)
|
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
|
||||||
Text(
|
)
|
||||||
text = artist?.name.orEmpty(),
|
Text(
|
||||||
style = imageNameStyle,
|
text = artist?.name.orEmpty(),
|
||||||
color = Color.White,
|
style = imageNameStyle,
|
||||||
maxLines = 1,
|
color = Color.White,
|
||||||
overflow = TextOverflow.Ellipsis,
|
maxLines = 1,
|
||||||
modifier = Modifier.align(Alignment.BottomStart).padding(TITLE_INSET),
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
modifier = Modifier.align(Alignment.BottomStart).padding(TITLE_INSET),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
itemsIndexed(albums, key = { _, album -> album.id }) { index, album ->
|
||||||
itemsIndexed(albums, key = { _, album -> album.id }) { index, album ->
|
Column(
|
||||||
Column(
|
modifier =
|
||||||
modifier =
|
Modifier
|
||||||
Modifier
|
.padding(
|
||||||
.padding(
|
start = if (index % 2 == 0) 16.dp else 0.dp,
|
||||||
start = if (index % 2 == 0) 16.dp else 0.dp,
|
end = if (index % 2 == 1) 16.dp else 0.dp,
|
||||||
end = if (index % 2 == 1) 16.dp else 0.dp,
|
).clickable { onAlbumClick(album) },
|
||||||
).clickable { onAlbumClick(album) },
|
) {
|
||||||
) {
|
CoverArt(
|
||||||
CoverArt(
|
ref = coverArt(album.coverArt, false),
|
||||||
ref = coverArt(album.coverArt, false),
|
name = album.name,
|
||||||
name = album.name,
|
modifier = Modifier.fillMaxWidth().aspectRatio(1f).clip(RoundedCornerShape(2.dp)),
|
||||||
modifier = Modifier.fillMaxWidth().aspectRatio(1f).clip(RoundedCornerShape(2.dp)),
|
)
|
||||||
)
|
Text(
|
||||||
Text(
|
text = album.name,
|
||||||
text = album.name,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
maxLines = 1,
|
||||||
maxLines = 1,
|
overflow = TextOverflow.Ellipsis,
|
||||||
overflow = TextOverflow.Ellipsis,
|
modifier = Modifier.padding(top = 6.dp),
|
||||||
modifier = Modifier.padding(top = 6.dp),
|
)
|
||||||
)
|
Text(
|
||||||
Text(
|
text = album.year?.takeIf { it > 0 }?.toString() ?: "\u00A0",
|
||||||
text = album.year?.takeIf { it > 0 }?.toString() ?: "\u00A0",
|
style = MaterialTheme.typography.bodySmall,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +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
|
import com.subtracks.ui.components.StretchOverscroll
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ArtistsContent(
|
fun ArtistsContent(
|
||||||
@@ -44,38 +44,40 @@ fun ArtistsContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
LazyColumn(
|
StretchOverscroll(modifier = modifier.fillMaxSize()) {
|
||||||
modifier = modifier.fillMaxSize().stretchOverscroll(),
|
LazyColumn(
|
||||||
contentPadding = PaddingValues(bottom = bottomInset),
|
modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
contentPadding = PaddingValues(bottom = bottomInset),
|
||||||
items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
|
) {
|
||||||
val artist = items[index]
|
items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
|
||||||
if (artist != null) {
|
val artist = items[index]
|
||||||
ListItem(
|
if (artist != null) {
|
||||||
headlineContent = {
|
ListItem(
|
||||||
Text(
|
headlineContent = {
|
||||||
text = artist.name,
|
Text(
|
||||||
maxLines = 1,
|
text = artist.name,
|
||||||
overflow = TextOverflow.Ellipsis,
|
maxLines = 1,
|
||||||
)
|
overflow = TextOverflow.Ellipsis,
|
||||||
},
|
)
|
||||||
supportingContent = {
|
},
|
||||||
Text(
|
supportingContent = {
|
||||||
text = "${artist.albumCount} albums",
|
Text(
|
||||||
maxLines = 1,
|
text = "${artist.albumCount} albums",
|
||||||
overflow = TextOverflow.Ellipsis,
|
maxLines = 1,
|
||||||
)
|
overflow = TextOverflow.Ellipsis,
|
||||||
},
|
)
|
||||||
leadingContent = {
|
},
|
||||||
CoverArt(
|
leadingContent = {
|
||||||
ref = coverArt(artist.coverArt, true),
|
CoverArt(
|
||||||
name = artist.name,
|
ref = coverArt(artist.coverArt, true),
|
||||||
modifier = Modifier.size(48.dp).clip(CircleShape),
|
name = artist.name,
|
||||||
)
|
modifier = Modifier.size(48.dp).clip(CircleShape),
|
||||||
},
|
)
|
||||||
modifier = Modifier.clickable { onArtistClick(artist) },
|
},
|
||||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
modifier = Modifier.clickable { onArtistClick(artist) },
|
||||||
)
|
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +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 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
|
||||||
@@ -84,38 +84,40 @@ fun PlaylistDetailScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { padding ->
|
) { padding ->
|
||||||
LazyColumn(Modifier.padding(padding).fillMaxSize().stretchOverscroll()) {
|
StretchOverscroll(modifier = Modifier.padding(padding).fillMaxSize()) {
|
||||||
item {
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
Row(
|
item {
|
||||||
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
) {
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
CoverArt(
|
) {
|
||||||
ref = coverArt(playlist?.coverArt, false),
|
CoverArt(
|
||||||
name = playlist?.name.orEmpty(),
|
ref = coverArt(playlist?.coverArt, false),
|
||||||
modifier = Modifier.size(120.dp).clip(RoundedCornerShape(2.dp)),
|
name = playlist?.name.orEmpty(),
|
||||||
)
|
modifier = Modifier.size(120.dp).clip(RoundedCornerShape(2.dp)),
|
||||||
Column {
|
|
||||||
Text(playlist?.name.orEmpty(), style = MaterialTheme.typography.titleLarge)
|
|
||||||
Text(
|
|
||||||
text = "${playlist?.songCount ?: 0} songs",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
)
|
)
|
||||||
|
Column {
|
||||||
|
Text(playlist?.name.orEmpty(), style = MaterialTheme.typography.titleLarge)
|
||||||
|
Text(
|
||||||
|
text = "${playlist?.songCount ?: 0} songs",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
items(count = songs.itemCount, key = songs.itemKey { it.song.id }) { index ->
|
||||||
items(count = songs.itemCount, key = songs.itemKey { it.song.id }) { index ->
|
val item = songs[index]
|
||||||
val item = songs[index]
|
if (item != null) {
|
||||||
if (item != null) {
|
SongRow(
|
||||||
SongRow(
|
song = item.song,
|
||||||
song = item.song,
|
coverArtId = item.coverArt,
|
||||||
coverArtId = item.coverArt,
|
coverArt = coverArt,
|
||||||
coverArt = coverArt,
|
isPlaying = item.song.id == playingSongId,
|
||||||
isPlaying = item.song.id == playingSongId,
|
modifier = Modifier.clickable { onSongClick(index) },
|
||||||
modifier = Modifier.clickable { onSongClick(index) },
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +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
|
import com.subtracks.ui.components.StretchOverscroll
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PlaylistsContent(
|
fun PlaylistsContent(
|
||||||
@@ -44,38 +44,40 @@ fun PlaylistsContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
LazyColumn(
|
StretchOverscroll(modifier = modifier.fillMaxSize()) {
|
||||||
modifier = modifier.fillMaxSize().stretchOverscroll(),
|
LazyColumn(
|
||||||
contentPadding = PaddingValues(bottom = bottomInset),
|
modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
contentPadding = PaddingValues(bottom = bottomInset),
|
||||||
items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
|
) {
|
||||||
val playlist = items[index]
|
items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
|
||||||
if (playlist != null) {
|
val playlist = items[index]
|
||||||
ListItem(
|
if (playlist != null) {
|
||||||
headlineContent = {
|
ListItem(
|
||||||
Text(
|
headlineContent = {
|
||||||
text = playlist.name,
|
Text(
|
||||||
maxLines = 1,
|
text = playlist.name,
|
||||||
overflow = TextOverflow.Ellipsis,
|
maxLines = 1,
|
||||||
)
|
overflow = TextOverflow.Ellipsis,
|
||||||
},
|
)
|
||||||
supportingContent = {
|
},
|
||||||
Text(
|
supportingContent = {
|
||||||
text = playlist.comment?.takeIf { it.isNotBlank() } ?: "${playlist.songCount} songs",
|
Text(
|
||||||
maxLines = 1,
|
text = playlist.comment?.takeIf { it.isNotBlank() } ?: "${playlist.songCount} songs",
|
||||||
overflow = TextOverflow.Ellipsis,
|
maxLines = 1,
|
||||||
)
|
overflow = TextOverflow.Ellipsis,
|
||||||
},
|
)
|
||||||
leadingContent = {
|
},
|
||||||
CoverArt(
|
leadingContent = {
|
||||||
ref = coverArt(playlist.coverArt, true),
|
CoverArt(
|
||||||
name = playlist.name,
|
ref = coverArt(playlist.coverArt, true),
|
||||||
modifier = Modifier.size(48.dp).clip(RoundedCornerShape(8.dp)),
|
name = playlist.name,
|
||||||
)
|
modifier = Modifier.size(48.dp).clip(RoundedCornerShape(8.dp)),
|
||||||
},
|
)
|
||||||
modifier = Modifier.clickable { onPlaylistClick(playlist) },
|
},
|
||||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
modifier = Modifier.clickable { onPlaylistClick(playlist) },
|
||||||
)
|
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +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
|
import com.subtracks.ui.components.StretchOverscroll
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SongsContent(
|
fun SongsContent(
|
||||||
@@ -53,20 +53,22 @@ fun SongsContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
LazyColumn(
|
StretchOverscroll(modifier = modifier.fillMaxSize()) {
|
||||||
modifier = modifier.fillMaxSize().stretchOverscroll(),
|
LazyColumn(
|
||||||
contentPadding = PaddingValues(bottom = bottomInset),
|
modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
contentPadding = PaddingValues(bottom = bottomInset),
|
||||||
items(count = items.itemCount, key = items.itemKey { it.song.id }) { index ->
|
) {
|
||||||
val item = items[index]
|
items(count = items.itemCount, key = items.itemKey { it.song.id }) { index ->
|
||||||
if (item != null) {
|
val item = items[index]
|
||||||
SongRow(
|
if (item != null) {
|
||||||
song = item.song,
|
SongRow(
|
||||||
coverArtId = item.coverArt,
|
song = item.song,
|
||||||
coverArt = coverArt,
|
coverArtId = item.coverArt,
|
||||||
isPlaying = item.song.id == playingSongId,
|
coverArt = coverArt,
|
||||||
modifier = Modifier.clickable { onSongClick(index) },
|
isPlaying = item.song.id == playingSongId,
|
||||||
)
|
modifier = Modifier.clickable { onSongClick(index) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +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.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
|
||||||
@@ -339,55 +339,57 @@ fun QueueScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
LazyColumn(
|
StretchOverscroll(modifier = Modifier.padding(padding).fillMaxSize()) {
|
||||||
state = listState,
|
LazyColumn(
|
||||||
modifier = Modifier.padding(padding).fillMaxSize().stretchOverscroll(),
|
state = listState,
|
||||||
contentPadding = PaddingValues(bottom = 16.dp),
|
modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
contentPadding = PaddingValues(bottom = 16.dp),
|
||||||
items(count = rows.size, key = { rows[it].id }) { index ->
|
) {
|
||||||
val row = rows[index]
|
items(count = rows.size, key = { rows[it].id }) { index ->
|
||||||
ReorderableItem(
|
val row = rows[index]
|
||||||
state = reorderState,
|
ReorderableItem(
|
||||||
key = row.id,
|
state = reorderState,
|
||||||
animateItemModifier =
|
key = row.id,
|
||||||
Modifier.animateItem(
|
animateItemModifier =
|
||||||
fadeInSpec = tween(150),
|
Modifier.animateItem(
|
||||||
fadeOutSpec = tween(150),
|
fadeInSpec = tween(150),
|
||||||
placementSpec = tween(200),
|
fadeOutSpec = tween(150),
|
||||||
),
|
placementSpec = tween(200),
|
||||||
) { isDragging ->
|
|
||||||
QueueRowItem(
|
|
||||||
row = row,
|
|
||||||
isPlaying = row.position == currentPosition,
|
|
||||||
floating = isDragging,
|
|
||||||
coverArt = coverArt,
|
|
||||||
dragHandle =
|
|
||||||
Modifier.draggableHandle(
|
|
||||||
onDragStarted = {
|
|
||||||
dragId = row.id
|
|
||||||
dragFrom = row.position
|
|
||||||
},
|
|
||||||
onDragStopped = {
|
|
||||||
val id = dragId
|
|
||||||
val from = dragFrom
|
|
||||||
if (id != null && from != null) {
|
|
||||||
val index = rows.indexOfFirst { it.id == id }
|
|
||||||
val to = if (index < 0) from else dropTarget(rows, index, from)
|
|
||||||
if (to != from) {
|
|
||||||
onMove(from, to)
|
|
||||||
showUndo("Queue reordered")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dragId = null
|
|
||||||
dragFrom = null
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
onClick = { onPlay(row.position) },
|
) { isDragging ->
|
||||||
onRemove = {
|
QueueRowItem(
|
||||||
onRemove(row.position)
|
row = row,
|
||||||
showUndo("Removed from queue")
|
isPlaying = row.position == currentPosition,
|
||||||
},
|
floating = isDragging,
|
||||||
)
|
coverArt = coverArt,
|
||||||
|
dragHandle =
|
||||||
|
Modifier.draggableHandle(
|
||||||
|
onDragStarted = {
|
||||||
|
dragId = row.id
|
||||||
|
dragFrom = row.position
|
||||||
|
},
|
||||||
|
onDragStopped = {
|
||||||
|
val id = dragId
|
||||||
|
val from = dragFrom
|
||||||
|
if (id != null && from != null) {
|
||||||
|
val index = rows.indexOfFirst { it.id == id }
|
||||||
|
val to = if (index < 0) from else dropTarget(rows, index, from)
|
||||||
|
if (to != from) {
|
||||||
|
onMove(from, to)
|
||||||
|
showUndo("Queue reordered")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dragId = null
|
||||||
|
dragFrom = null
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onClick = { onPlay(row.position) },
|
||||||
|
onRemove = {
|
||||||
|
onRemove(row.position)
|
||||||
|
showUndo("Removed from queue")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ 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
|
||||||
@@ -106,7 +105,6 @@ 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,7 +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 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)
|
||||||
@@ -102,57 +102,59 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { padding ->
|
) { padding ->
|
||||||
LazyColumn(Modifier.padding(padding).fillMaxSize().stretchOverscroll()) {
|
StretchOverscroll(modifier = Modifier.padding(padding).fillMaxSize()) {
|
||||||
item { SectionHeader("Servers") }
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
items(sources.size, key = { sources[it].id }) { index ->
|
item { SectionHeader("Servers") }
|
||||||
val source = sources[index]
|
items(sources.size, key = { sources[it].id }) { index ->
|
||||||
ListItem(
|
val source = sources[index]
|
||||||
headlineContent = { Text(source.name) },
|
ListItem(
|
||||||
supportingContent = {
|
headlineContent = { Text(source.name) },
|
||||||
Text(source.address, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
supportingContent = {
|
||||||
},
|
Text(source.address, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||||
leadingContent = {
|
},
|
||||||
RadioButton(
|
leadingContent = {
|
||||||
selected = source.id == activeSourceId,
|
RadioButton(
|
||||||
onClick = { onSelectSource(source.id) },
|
selected = source.id == activeSourceId,
|
||||||
)
|
onClick = { onSelectSource(source.id) },
|
||||||
},
|
)
|
||||||
trailingContent = {
|
},
|
||||||
IconButton(onClick = { onDeleteSource(source.id) }) {
|
trailingContent = {
|
||||||
Icon(Icons.Rounded.Delete, contentDescription = "Remove ${source.name}")
|
IconButton(onClick = { onDeleteSource(source.id) }) {
|
||||||
|
Icon(Icons.Rounded.Delete, contentDescription = "Remove ${source.name}")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.End,
|
||||||
|
) {
|
||||||
|
Button(onClick = onAddServer) {
|
||||||
|
Icon(Icons.Rounded.Add, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Text("Add server")
|
||||||
}
|
}
|
||||||
},
|
|
||||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp),
|
|
||||||
horizontalArrangement = Arrangement.End,
|
|
||||||
) {
|
|
||||||
Button(onClick = onAddServer) {
|
|
||||||
Icon(Icons.Rounded.Add, contentDescription = null, modifier = Modifier.size(18.dp))
|
|
||||||
Spacer(Modifier.width(8.dp))
|
|
||||||
Text("Add server")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
item { SectionHeader("Network") }
|
||||||
item { SectionHeader("Network") }
|
item {
|
||||||
item {
|
ListItem(
|
||||||
ListItem(
|
headlineContent = { Text("Maximum bitrate") },
|
||||||
headlineContent = { Text("Maximum bitrate") },
|
supportingContent = { Text(bitrateLabel(maxBitrate)) },
|
||||||
supportingContent = { Text(bitrateLabel(maxBitrate)) },
|
modifier = Modifier.clickable { dialog = SettingsDialog.Bitrate },
|
||||||
modifier = Modifier.clickable { dialog = SettingsDialog.Bitrate },
|
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
)
|
||||||
)
|
}
|
||||||
}
|
item {
|
||||||
item {
|
ListItem(
|
||||||
ListItem(
|
headlineContent = { Text("Preferred stream format") },
|
||||||
headlineContent = { Text("Preferred stream format") },
|
supportingContent = { Text(streamFormat ?: "Use server default") },
|
||||||
supportingContent = { Text(streamFormat ?: "Use server default") },
|
modifier = Modifier.clickable { dialog = SettingsDialog.Format },
|
||||||
modifier = Modifier.clickable { dialog = SettingsDialog.Format },
|
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
private const val SEED_ART_SIZE_PX = 128
|
private const val SEED_ART_SIZE_PX = 128
|
||||||
private const val GRADIENT_FADE_START = 0.8f / 1.5f
|
private const val GRADIENT_FADE_START = 1.3f / 2.0f
|
||||||
|
|
||||||
data class ArtworkColors(
|
data class ArtworkColors(
|
||||||
val scheme: ColorScheme,
|
val scheme: ColorScheme,
|
||||||
@@ -84,7 +84,7 @@ fun artworkColorsFromSeed(seed: Int): ArtworkColors {
|
|||||||
scheme = scheme,
|
scheme = scheme,
|
||||||
gradientHigh = tone(hue, (s * 0.8f).coerceAtMost(0.60f), 0.30f),
|
gradientHigh = tone(hue, (s * 0.8f).coerceAtMost(0.60f), 0.30f),
|
||||||
gradientLow = tone(hue, s * 0.25f, 0.04f),
|
gradientLow = tone(hue, s * 0.25f, 0.04f),
|
||||||
accents = listOf(tone(hue, s, 0.70f), tone(hue, s * 0.85f, 0.38f)),
|
accents = listOf(tone(hue, s, 0.78f), tone(hue, s * 0.9f, 0.20f)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user