fix(android): hold previous NowPlaying gradient color across track change

rememberDominantColor was keyed on coverUrl, so each track change reset the extracted color to Transparent and the gradient dipped toward the fallback before tweening to the new color. Drop the key so the held color stays on the previous track's dominant until the new palette resolves -- the gradient now tweens directly to the new color. Combined with CoverPrefetcher warming the next cover, this matches Flutter's preload-then-atomic-swap UX (no flash on track change) via native Compose mechanisms rather than a literal preload pipeline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 15:12:33 -04:00
parent 61cf07a3cf
commit 68891f1df9
@@ -27,15 +27,21 @@ private const val GRADIENT_TWEEN_MS = 600
* androidx.palette on a background thread, and animates the result
* via [animateColorAsState] so track changes tween smoothly.
*
* Returns [Color.Transparent] while the bitmap is loading or on
* any failure — callers should layer a fallback (e.g.
* `MaterialTheme.colorScheme.surface`) underneath the gradient
* tint so the screen never sits on a flat black.
* The held color is NOT reset when [coverUrl] changes — it stays on
* the previous track's dominant until the new palette resolves, so the
* gradient tweens old→new directly instead of dipping toward the
* fallback mid-swap. Mirrors `now_playing_screen.dart`'s preload-then-
* swap ("keep the previous dominant"); the cover image swaps smoothly
* via `CoverPrefetcher`, which warms the next track's bytes into Coil.
*
* Starts at [Color.Transparent] (cold mount) and resets to it only
* when a track has no cover — callers layer a base color underneath so
* the screen never sits on flat black.
*/
@Composable
fun rememberDominantColor(coverUrl: String?): Color {
val context = LocalContext.current
var extracted by remember(coverUrl) { mutableStateOf(Color.Transparent) }
var extracted by remember { mutableStateOf(Color.Transparent) }
LaunchedEffect(coverUrl) {
if (coverUrl.isNullOrEmpty()) {
@@ -47,6 +53,7 @@ fun rememberDominantColor(coverUrl: String?): Color {
.allowHardware(false)
.build()
val result = SingletonImageLoader.get(context).execute(request)
// Keep the previous dominant on load failure rather than dropping.
if (result !is SuccessResult) return@LaunchedEffect
val bitmap = result.image.toBitmap()
val palette = withContext(Dispatchers.Default) {