diff --git a/app/src/main/kotlin/com/subtracks/ui/theme/AnimatedArtworkColors.kt b/app/src/main/kotlin/com/subtracks/ui/theme/AnimatedArtworkColors.kt index 7162b914..a6020c08 100644 --- a/app/src/main/kotlin/com/subtracks/ui/theme/AnimatedArtworkColors.kt +++ b/app/src/main/kotlin/com/subtracks/ui/theme/AnimatedArtworkColors.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.lerp import kotlin.math.roundToInt @@ -40,21 +41,23 @@ fun rememberAnimatedArtworkColors( ): ArtworkColors { var from by remember { mutableStateOf(target ?: base) } var to by remember { mutableStateOf(target ?: base) } + var onBase by remember { mutableStateOf(target == null) } val progress = remember { Animatable(1f) } + val currentBase by rememberUpdatedState(base) - LaunchedEffect(target, base, durationMillis) { - val next = target ?: base - if (next !== to) { - if (to === base) { - from = next - to = next - } else { - from = lerpArtworkColors(from, to, progress.value) - to = next - progress.snapTo(0f) - progress.animateTo(1f, tween(durationMillis = durationMillis)) - } + LaunchedEffect(target, durationMillis) { + val next = target ?: currentBase + if (next === to) return@LaunchedEffect + if (onBase) { + from = next + to = next + } else { + from = lerpArtworkColors(from, to, progress.value) + to = next + progress.snapTo(0f) + progress.animateTo(1f, tween(durationMillis = durationMillis)) } + onBase = target == null } if (from === to) return to diff --git a/app/src/test/kotlin/com/subtracks/ui/theme/ArtworkExtractionTest.kt b/app/src/test/kotlin/com/subtracks/ui/theme/ArtworkExtractionTest.kt index 901499b5..bb1e2609 100644 --- a/app/src/test/kotlin/com/subtracks/ui/theme/ArtworkExtractionTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/theme/ArtworkExtractionTest.kt @@ -7,8 +7,10 @@ import androidx.activity.ComponentActivity import androidx.compose.material3.Text import androidx.compose.runtime.MutableState import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.luminance import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule import androidx.test.core.app.ApplicationProvider @@ -89,6 +91,47 @@ class ArtworkExtractionTest { assertEquals(artworkColorsFromSeed(placeholderSeed("Kid A")).scheme.primary, colors?.scheme?.primary) } + @Test + fun fadesFromThePlaceholderToTheArtworkSeed() { + val placeholder = artworkColorsFromSeed(placeholderSeed("Kid A")) + val art = artworkColorsFromSeed(0xFF3A7BD5.toInt()) + val artwork = mutableStateOf(placeholder) + val seen = mutableListOf() + composeRule.setContent { + SubtracksTheme { + val colors = rememberAnimatedArtworkColors(artwork.value, baseArtworkColors()) + SideEffect { seen += colors.scheme.background } + } + } + composeRule.waitForIdle() + composeRule.runOnIdle { artwork.value = art } + composeRule.waitForIdle() + assertEquals(art.scheme.background, seen.last()) + } + + @Test + fun aChangingBaseSchemeDoesNotStrandTheArtworkTransition() { + val placeholder = artworkColorsFromSeed(placeholderSeed("Kid A")) + val art = artworkColorsFromSeed(0xFF3A7BD5.toInt()) + val artwork = mutableStateOf(placeholder) + var baseTick by mutableStateOf(0) + val seen = mutableListOf() + composeRule.setContent { + SubtracksTheme { + val base = remember(baseTick) { artworkColorsFromSeed(baseTick) } + val colors = rememberAnimatedArtworkColors(artwork.value, base) + SideEffect { + seen += colors.scheme.background + if (artwork.value !== placeholder && baseTick < 20) baseTick++ + } + } + } + composeRule.waitForIdle() + composeRule.runOnIdle { artwork.value = art } + composeRule.waitForIdle() + assertEquals(art.scheme.background, seen.last()) + } + @Test fun keepsContentStateWhenArtworkArrives() { val artwork = mutableStateOf(null)