make the now playing slider seekable and show full artwork

This commit is contained in:
austinried
2026-10-04 00:29:54 +00:00
parent d7434b80cc
commit 92ef7bfcfb
5 changed files with 74 additions and 14 deletions
@@ -376,7 +376,10 @@ class PlaybackController(
fun previous() = skip(-1)
fun seekTo(positionMs: Long) {
player?.seekTo(positionMs)
val player = player ?: return
player.ensurePrepared()
player.seekTo(positionMs)
player.play()
}
fun coverArt(
@@ -1,8 +1,10 @@
package com.subtracks.ui.components
import android.content.Context
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator
@@ -19,7 +21,9 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import coil3.compose.AsyncImagePainter
import coil3.compose.LocalPlatformContext
import coil3.compose.rememberAsyncImagePainter
import coil3.request.ImageRequest
import coil3.request.crossfade
import com.subtracks.data.model.CoverArtRef
@@ -31,11 +35,21 @@ fun CoverArt(
modifier: Modifier = Modifier,
thumbnailRef: CoverArtRef? = null,
showPlaceholder: Boolean = true,
square: Boolean = true,
) {
val context = LocalPlatformContext.current
var failed by remember(ref, thumbnailRef) { mutableStateOf(false) }
var thumbnailLoaded by remember(ref, thumbnailRef) { mutableStateOf(false) }
Box(modifier = modifier.background(MaterialTheme.colorScheme.surfaceVariant)) {
val model = remember(ref, thumbnailRef) { ref?.let { imageRequest(context, it, crossfade = thumbnailRef != null) } }
val painter = rememberAsyncImagePainter(model)
val failed = painter.state is AsyncImagePainter.State.Error
val intrinsic = painter.intrinsicSize
val ratio =
if (!square && intrinsic.width > 0f && intrinsic.height > 0f && intrinsic.width.isFinite() && intrinsic.height.isFinite()) {
intrinsic.width / intrinsic.height
} else {
1f
}
Box(modifier = modifier.aspectRatio(ratio).background(MaterialTheme.colorScheme.surfaceVariant)) {
if (showPlaceholder || (failed && !thumbnailLoaded)) {
Text(
text = name.trim().take(1).uppercase(),
@@ -48,17 +62,16 @@ fun CoverArt(
AsyncImage(
model = remember(thumbnailRef) { imageRequest(context, thumbnailRef, crossfade = false) },
contentDescription = null,
contentScale = ContentScale.Crop,
contentScale = if (square) ContentScale.Crop else ContentScale.Fit,
onSuccess = { thumbnailLoaded = true },
modifier = Modifier.fillMaxSize(),
)
}
if (ref != null) {
AsyncImage(
model = remember(ref, thumbnailRef) { imageRequest(context, ref, crossfade = thumbnailRef != null) },
Image(
painter = painter,
contentDescription = null,
contentScale = ContentScale.Crop,
onError = { failed = true },
contentScale = if (square) ContentScale.Crop else ContentScale.Fit,
modifier = Modifier.fillMaxSize(),
)
}
@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -234,10 +233,10 @@ fun HeroHeader(
ref = art,
name = name,
thumbnailRef = thumbnailRef,
square = false,
modifier =
Modifier
.fillMaxWidth(0.86f)
.aspectRatio(1f)
.shadow(elevation = 3.dp, shape = RoundedCornerShape(2.dp), clip = false)
.clip(RoundedCornerShape(2.dp)),
)
@@ -30,6 +30,7 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
@@ -46,6 +47,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.PlatformTextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -125,6 +127,14 @@ fun NowPlayingScreen(
) {
val playButtonSize = 96.dp
val playCircleDiameter = playButtonSize * 20f / 24f
val titleStyle =
MaterialTheme.typography.headlineSmall.copy(
platformStyle = PlatformTextStyle(includeFontPadding = false),
)
val subtitleStyle =
MaterialTheme.typography.bodyMedium.copy(
platformStyle = PlatformTextStyle(includeFontPadding = false),
)
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
ArtworkTheme(artwork) {
@@ -172,23 +182,23 @@ fun NowPlayingScreen(
ref = coverArt,
name = state.item?.title.orEmpty(),
thumbnailRef = thumbnailRef,
square = false,
modifier =
Modifier
.fillMaxHeight()
.aspectRatio(1f)
.shadow(elevation = 3.dp, shape = RoundedCornerShape(2.dp), clip = false)
.clip(RoundedCornerShape(2.dp)),
)
}
Text(
text = state.item?.title.orEmpty(),
style = MaterialTheme.typography.headlineSmall,
style = titleStyle,
maxLines = 1,
modifier = Modifier.padding(top = 28.dp).basicMarquee(),
)
Text(
text = listOfNotNull(state.item?.artist, state.item?.album).joinToString(" • "),
style = MaterialTheme.typography.bodyMedium,
style = subtitleStyle,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
modifier = Modifier.padding(top = 6.dp).basicMarquee(),
@@ -206,7 +216,13 @@ fun NowPlayingScreen(
dragging = false
},
valueRange = 0f..state.durationMs.toFloat().coerceAtLeast(1f),
enabled = state.durationMs > 0,
track = { sliderState ->
SliderDefaults.Track(
sliderState = sliderState,
thumbTrackGapSize = 0.dp,
drawStopIndicator = {},
)
},
modifier = Modifier.padding(top = 28.dp),
)
Row(
@@ -0,0 +1,29 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="299.84"
android:viewportHeight="219.51">
<group android:scaleX="0.43"
android:scaleY="0.3147989"
android:translateX="85.4544"
android:translateY="75.20425">
<path
android:pathData="m23.84,-0c-12.76,0 -23.44,13 -23.65,25.12 -0.42,79.03 0,160.73 0,170.4 0,12.59 9.8,23.99 23.41,23.99l253.58,0c11.4,0 22.66,-12.38 22.66,-22.79L299.84,24.12c0,-10.8 -10.24,-23.81 -21.84,-23.81 -11.6,0 -254.16,-0.31 -254.16,-0.31zM39.05,25.28c0,0 217.71,0.71 223.67,0.71 6.07,0 12.17,6.83 12.17,12.06l0,142.25c0,8.97 -6.97,14.63 -12.48,14.64l-33.42,0.07c-5.32,0.01 -9.93,-7.19 -11.35,-10.61 -1.89,-4.55 -11.08,-25.75 -12.1,-28.1 -0.6,-1.38 -2.9,-2.83 -5.89,-2.83L98.33,153.45c-2.68,0 -4.89,2.87 -5.67,4.71 -1.12,2.66 -9.02,21.39 -11.63,27.57 -2.73,6.46 -4.37,9.06 -11.47,9.06l-30.97,0c-7.44,0 -13.69,-7.17 -13.67,-15.18l0.28,-139.51c0,-6.88 5.54,-14.83 13.85,-14.83z"
android:strokeWidth="0.9"
android:fillColor="#FFFFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M90.09,95.03m-32.1,0a32.42,32.1 90,1 1,64.21 0a32.42,32.1 90,1 1,-64.21 0"
android:strokeWidth="0.86"
android:fillColor="#FFFFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M209.94,95.03m-32.1,0a32.42,32.1 90,1 1,64.21 0a32.42,32.1 90,1 1,-64.21 0"
android:strokeWidth="0.86"
android:fillColor="#FFFFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</group>
</vector>