fix queue overlay, focus and artwork loading glitches

- scope QueueViewModel to the queue overlay so its rows are released on close
  and a reopened queue cannot show two overlapping lists; key open() on the
  overlay visibility so a fast reopen still rescans and refocuses
- reuse row ids by song identity when a window (re)loads so Compose does not
  cross-fade the old rows out while the new ones fade in
- fade the artist name scrim in and key its busy check on the thumbnail, the
  same ref the theme already uses, so a cached seed shows it immediately
- start detail gradients from the neutral base artwork until the seed resolves
  instead of inheriting the now playing theme and snapping to the artist colours
- fade the now playing and detail status bar scrims by putting the graphicsLayer
  before statusBarScrim(), which otherwise drew the background outside the layer
- decode full-size and hero covers with FilterQuality.High to stop the moire
  during overscroll stretch
This commit is contained in:
austinried
2026-10-04 00:29:59 +00:00
parent 5fb38a7c9b
commit da71bccb58
7 changed files with 78 additions and 27 deletions
@@ -1,5 +1,7 @@
package com.subtracks.ui.library
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
@@ -61,6 +63,7 @@ import com.subtracks.ui.theme.ArtworkColors
import com.subtracks.ui.theme.ArtworkTheme
import com.subtracks.ui.theme.HeroGradient
import com.subtracks.ui.theme.PrefetchArtworkSeeds
import com.subtracks.ui.theme.baseArtworkColors
import com.subtracks.ui.theme.heroBarColor
import com.subtracks.ui.theme.rememberArtworkColors
import com.subtracks.ui.theme.rememberOverlaidNameBusy
@@ -71,6 +74,7 @@ private val ART_HEIGHT = 420.dp
private val TITLE_INSET = 16.dp
private val FADE_LEAD = 24.dp
private const val THEME_TRANSITION_MS = 100
private const val SCRIM_FADE_MS = 180
internal const val ARTIST_NAME_SCRIM_TAG = "artistNameScrim"
@Composable
@@ -144,16 +148,18 @@ fun ArtistDetailScreen(
blurRadius = 10f,
),
)
val nameBusy = rememberOverlaidNameBusy(art ?: artThumbnail)
val nameBusy = rememberOverlaidNameBusy(artThumbnail ?: art)
val nameScrimAlpha by animateFloatAsState(if (nameBusy) 1f else 0f, tween(SCRIM_FADE_MS), label = "artistNameScrim")
val effectiveArtwork = artwork ?: baseArtworkColors
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
ArtworkTheme(artwork) {
ArtworkTheme(effectiveArtwork) {
BoxWithConstraints(modifier.fillMaxSize().background(Color.Black)) {
val screenHeightPx = with(density) { maxHeight.toPx() }
val barHeightPx = with(density) { barHeight.toPx() }
val barColor = heroBarColor(artwork, 0f, barHeightPx, screenHeightPx)
val barColor = heroBarColor(effectiveArtwork, 0f, barHeightPx, screenHeightPx)
HeroGradient(
colors = artwork,
colors = effectiveArtwork,
scrollPx = { 0f },
modifier = Modifier.fillMaxSize(),
)
@@ -174,13 +180,14 @@ fun ArtistDetailScreen(
showPlaceholder = art == null,
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
)
if (nameBusy) {
if (nameScrimAlpha > 0f) {
Box(
modifier =
Modifier
.align(Alignment.BottomStart)
.fillMaxWidth()
.height(ART_HEIGHT * 0.4f)
.graphicsLayer { alpha = nameScrimAlpha }
.background(
Brush.verticalGradient(
listOf(Color.Transparent, Color.Black.copy(alpha = 0.6f)),