mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +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.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.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -17,10 +20,13 @@ import androidx.compose.ui.unit.Velocity
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.abs
|
||||
|
||||
private const val MAX_STRETCH_FRACTION = 0.35f
|
||||
private const val MAX_STRETCH_FRACTION = 0.30f
|
||||
|
||||
@Composable
|
||||
fun Modifier.stretchOverscroll(): Modifier {
|
||||
fun StretchOverscroll(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val overscroll = remember { Animatable(0f) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
@@ -58,13 +64,16 @@ fun Modifier.stretchOverscroll(): Modifier {
|
||||
consumed: Velocity,
|
||||
available: Velocity,
|
||||
): Velocity {
|
||||
overscroll.animateTo(0f, spring(stiffness = Spring.StiffnessMediumLow))
|
||||
overscroll.animateTo(0f, spring(stiffness = Spring.StiffnessMedium))
|
||||
return available
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
CompositionLocalProvider(LocalOverscrollFactory provides null) {
|
||||
Box(
|
||||
modifier =
|
||||
modifier
|
||||
.nestedScroll(connection)
|
||||
.graphicsLayer {
|
||||
val raw = overscroll.value
|
||||
@@ -79,5 +88,9 @@ fun Modifier.stretchOverscroll(): Modifier {
|
||||
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.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
@@ -71,7 +72,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.components.StretchOverscroll
|
||||
import com.subtracks.ui.theme.ArtworkColors
|
||||
import com.subtracks.ui.theme.ArtworkTheme
|
||||
import com.subtracks.ui.theme.HeroGradient
|
||||
@@ -82,7 +83,7 @@ import org.koin.core.parameter.parametersOf
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
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
|
||||
|
||||
@Composable
|
||||
@@ -129,6 +130,7 @@ fun AlbumDetailScreen(
|
||||
val density = LocalDensity.current
|
||||
val statusBarTop = WindowInsets.statusBars.getTop(density)
|
||||
val navBarBottom = WindowInsets.navigationBars.getBottom(density)
|
||||
val statusBarDp = with(density) { statusBarTop.toDp() }
|
||||
val barHeightPx = statusBarTop + with(density) { TopAppBarDefaults.TopAppBarExpandedHeight.toPx() }
|
||||
val fadeDistancePx = with(density) { FADE_DISTANCE_DP.dp.toPx() }
|
||||
|
||||
@@ -167,13 +169,14 @@ fun AlbumDetailScreen(
|
||||
.alpha(gradientAlpha),
|
||||
)
|
||||
|
||||
StretchOverscroll(modifier = Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
contentPadding =
|
||||
PaddingValues(
|
||||
bottom = 16.dp + with(density) { navBarBottom.toDp() },
|
||||
),
|
||||
modifier = Modifier.fillMaxSize().stretchOverscroll(),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
item {
|
||||
AlbumHeader(
|
||||
@@ -184,6 +187,7 @@ fun AlbumDetailScreen(
|
||||
onShuffle = onShuffle,
|
||||
onDownload = onDownload,
|
||||
onMore = onMore,
|
||||
topInset = statusBarDp,
|
||||
titleModifier = Modifier.onGloballyPositioned { titleTopPx = it.positionInParent().y },
|
||||
modifier = Modifier.onSizeChanged { headerHeightPx = it.height.toFloat() },
|
||||
)
|
||||
@@ -199,12 +203,13 @@ fun AlbumDetailScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.fillMaxWidth()
|
||||
.height(with(density) { (barHeightPx + 24.dp.toPx()).toDp() })
|
||||
.height(statusBarDp + 8.dp)
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
listOf(Color.Black.copy(alpha = 0.8f), Color.Transparent),
|
||||
@@ -216,6 +221,7 @@ fun AlbumDetailScreen(
|
||||
title = {
|
||||
Text(
|
||||
text = album?.name.orEmpty(),
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.alpha(barFraction),
|
||||
@@ -250,6 +256,7 @@ private fun AlbumHeader(
|
||||
onShuffle: () -> Unit,
|
||||
onDownload: () -> Unit,
|
||||
onMore: () -> Unit,
|
||||
topInset: Dp,
|
||||
titleModifier: Modifier = Modifier,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
@@ -257,7 +264,7 @@ private fun AlbumHeader(
|
||||
modifier =
|
||||
modifier
|
||||
.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,
|
||||
) {
|
||||
CoverArt(
|
||||
@@ -268,7 +275,7 @@ private fun AlbumHeader(
|
||||
Spacer(Modifier.height(20.dp))
|
||||
Text(
|
||||
text = album?.name.orEmpty(),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
||||
@@ -23,7 +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
|
||||
import com.subtracks.ui.components.StretchOverscroll
|
||||
|
||||
const val ALBUM_COVER_TAG = "album-cover"
|
||||
|
||||
@@ -45,12 +45,13 @@ fun AlbumsContent(
|
||||
}
|
||||
|
||||
else -> {
|
||||
StretchOverscroll(modifier = modifier.fillMaxSize()) {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(3),
|
||||
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().stretchOverscroll(),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
|
||||
val album = items[index]
|
||||
@@ -71,4 +72,5 @@ fun AlbumsContent(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +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 com.subtracks.ui.components.StretchOverscroll
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
@@ -123,13 +123,14 @@ fun ArtistDetailScreen(
|
||||
)
|
||||
|
||||
Box(modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
|
||||
StretchOverscroll(modifier = Modifier.fillMaxSize()) {
|
||||
LazyVerticalGrid(
|
||||
state = listState,
|
||||
columns = GridCells.Fixed(2),
|
||||
contentPadding = PaddingValues(bottom = 16.dp + navBarBottom),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier.fillMaxSize().stretchOverscroll(),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
Box(Modifier.fillMaxWidth().padding(bottom = 12.dp)) {
|
||||
@@ -178,6 +179,7 @@ fun ArtistDetailScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier =
|
||||
|
||||
@@ -24,7 +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
|
||||
import com.subtracks.ui.components.StretchOverscroll
|
||||
|
||||
@Composable
|
||||
fun ArtistsContent(
|
||||
@@ -44,8 +44,9 @@ fun ArtistsContent(
|
||||
}
|
||||
|
||||
else -> {
|
||||
StretchOverscroll(modifier = modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize().stretchOverscroll(),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = bottomInset),
|
||||
) {
|
||||
items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
|
||||
@@ -81,4 +82,5 @@ fun ArtistsContent(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +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 com.subtracks.ui.components.StretchOverscroll
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
@@ -84,7 +84,8 @@ fun PlaylistDetailScreen(
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
LazyColumn(Modifier.padding(padding).fillMaxSize().stretchOverscroll()) {
|
||||
StretchOverscroll(modifier = Modifier.padding(padding).fillMaxSize()) {
|
||||
LazyColumn(Modifier.fillMaxSize()) {
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||
@@ -120,4 +121,5 @@ fun PlaylistDetailScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +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
|
||||
import com.subtracks.ui.components.StretchOverscroll
|
||||
|
||||
@Composable
|
||||
fun PlaylistsContent(
|
||||
@@ -44,8 +44,9 @@ fun PlaylistsContent(
|
||||
}
|
||||
|
||||
else -> {
|
||||
StretchOverscroll(modifier = modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize().stretchOverscroll(),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = bottomInset),
|
||||
) {
|
||||
items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
|
||||
@@ -81,4 +82,5 @@ fun PlaylistsContent(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +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
|
||||
import com.subtracks.ui.components.StretchOverscroll
|
||||
|
||||
@Composable
|
||||
fun SongsContent(
|
||||
@@ -53,8 +53,9 @@ fun SongsContent(
|
||||
}
|
||||
|
||||
else -> {
|
||||
StretchOverscroll(modifier = modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize().stretchOverscroll(),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = bottomInset),
|
||||
) {
|
||||
items(count = items.itemCount, key = items.itemKey { it.song.id }) { index ->
|
||||
@@ -72,6 +73,7 @@ fun SongsContent(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -51,7 +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.components.StretchOverscroll
|
||||
import com.subtracks.ui.library.SongRow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -339,9 +339,10 @@ fun QueueScreen(
|
||||
}
|
||||
|
||||
else -> {
|
||||
StretchOverscroll(modifier = Modifier.padding(padding).fillMaxSize()) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.padding(padding).fillMaxSize().stretchOverscroll(),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 16.dp),
|
||||
) {
|
||||
items(count = rows.size, key = { rows[it].id }) { index ->
|
||||
@@ -394,6 +395,7 @@ fun QueueScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun dropTarget(
|
||||
|
||||
@@ -32,7 +32,6 @@ 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
|
||||
@@ -106,7 +105,6 @@ fun AddSourceScreen(
|
||||
Modifier
|
||||
.padding(padding)
|
||||
.fillMaxSize()
|
||||
.stretchOverscroll()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.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.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.subtracks.data.model.Source
|
||||
import com.subtracks.ui.components.stretchOverscroll
|
||||
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)
|
||||
@@ -102,7 +102,8 @@ fun SettingsScreen(
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
LazyColumn(Modifier.padding(padding).fillMaxSize().stretchOverscroll()) {
|
||||
StretchOverscroll(modifier = Modifier.padding(padding).fillMaxSize()) {
|
||||
LazyColumn(Modifier.fillMaxSize()) {
|
||||
item { SectionHeader("Servers") }
|
||||
items(sources.size, key = { sources[it].id }) { index ->
|
||||
val source = sources[index]
|
||||
@@ -156,6 +157,7 @@ fun SettingsScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (dialog) {
|
||||
SettingsDialog.Bitrate -> {
|
||||
|
||||
@@ -27,7 +27,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
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(
|
||||
val scheme: ColorScheme,
|
||||
@@ -84,7 +84,7 @@ fun artworkColorsFromSeed(seed: Int): ArtworkColors {
|
||||
scheme = scheme,
|
||||
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)),
|
||||
accents = listOf(tone(hue, s, 0.78f), tone(hue, s * 0.9f, 0.20f)),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user