diff --git a/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt b/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt index 067062fc..d1251537 100644 --- a/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt +++ b/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt @@ -52,7 +52,6 @@ data class PlaybackState( val hasPrevious: Boolean = false, val shuffle: Boolean = false, val repeat: RepeatMode = RepeatMode.Off, - val restored: Boolean = false, ) private sealed interface QueueUndo { @@ -128,7 +127,6 @@ class PlaybackController( private var repeatMode = RepeatMode.Off private var endedHandled = false private var lastSavedPositionMs = 0L - private var restored = false init { scope.launch { @@ -152,6 +150,9 @@ class PlaybackController( scope.cancel() } + private val _ready = MutableStateFlow(false) + val ready: StateFlow = _ready + fun connect() { if (player != null || connecting) return connecting = true @@ -170,12 +171,14 @@ class PlaybackController( shuffle != null -> startShuffled(shuffle) else -> restore() } + _ready.value = true } }, onDisconnected = { player = null connecting = false scope.launch { stop() } + _ready.value = true }, ) } @@ -228,7 +231,6 @@ class PlaybackController( windowEnd = -1 lastEdit = null endedHandled = false - restored = false val previous = snapshot val previousEntry = previous?.entries?.firstOrNull()?.entry val same = previousEntry != null && previousEntry.kind == entry.kind && previousEntry.refId == entry.refId @@ -604,7 +606,6 @@ class PlaybackController( windowEnd = -1 lastEdit = null endedHandled = false - restored = false val modes = queueRepository.modes() shuffleEnabled = !disableShuffle && modes.shuffle repeatMode = modes.repeat.toRepeatMode() @@ -665,7 +666,6 @@ class PlaybackController( this.queueSourceId = queueSourceId val position = queueRepository.cursor().coerceIn(0, snapshot.size - 1) lastSavedPositionMs = queueRepository.cursorPositionMs().coerceAtLeast(0) - restored = true loadWindow(position, autoplay = false, startPositionMs = lastSavedPositionMs) } @@ -681,7 +681,6 @@ class PlaybackController( windowStart = 0 windowEnd = -1 lastEdit = null - restored = false player?.run { stop() clear() @@ -943,7 +942,6 @@ class PlaybackController( hasPrevious = true, shuffle = shuffleEnabled, repeat = repeatMode, - restored = restored, ) if (player.isPlaying) startPositionTicker() else stopPositionTicker() } diff --git a/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt b/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt index 6ce91a2a..dcd7cf6c 100644 --- a/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt +++ b/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt @@ -4,11 +4,8 @@ import android.net.Uri import androidx.activity.OnBackPressedCallback import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition import androidx.compose.animation.core.animate import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.snap import androidx.compose.animation.core.tween import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn @@ -143,9 +140,20 @@ internal fun focusPops( @Composable fun SubtracksRoot(root: RootViewModel = koinViewModel()) { when (val hasSource = root.hasSource.collectAsStateWithLifecycle().value) { - null -> LoadingState() - false -> AddSourceRoute(onSaved = {}, onBack = null) - true -> MainNavigation() + null -> { + LoadingState() + } + + false -> { + AddSourceRoute(onSaved = {}, onBack = null) + } + + true -> { + val playbackController = koinInject() + LaunchedEffect(Unit) { playbackController.connect() } + val playbackReady by playbackController.ready.collectAsStateWithLifecycle() + if (playbackReady) MainNavigation() else LoadingState() + } } } @@ -219,7 +227,6 @@ private fun MainNavigation() { else -> repeat(pops) { navController.popBackStack() } } } - LaunchedEffect(Unit) { playbackController.connect() } LaunchedEffect(Unit) { if (nowPlayingOpen) nowPlayingProgress = 1f } val playerVisible = playback.item != null @@ -236,7 +243,7 @@ private fun MainNavigation() { val showMiniPlayer = playerVisible && !keyboardUp val bottomInset by animateDpAsState( targetValue = if (showMiniPlayer) 0.dp else navBarInset, - animationSpec = if (playback.restored) snap() else tween(MINI_PLAYER_ANIM_MS), + animationSpec = tween(MINI_PLAYER_ANIM_MS), label = "libraryBottomInset", ) @@ -379,18 +386,8 @@ private fun MainNavigation() { AnimatedVisibility( visible = showMiniPlayer, - enter = - if (playback.restored) { - EnterTransition.None - } else { - expandVertically(tween(MINI_PLAYER_ANIM_MS), expandFrom = Alignment.Bottom) - }, - exit = - if (playback.restored) { - ExitTransition.None - } else { - shrinkVertically(tween(MINI_PLAYER_ANIM_MS), shrinkTowards = Alignment.Bottom) - }, + enter = expandVertically(tween(MINI_PLAYER_ANIM_MS), expandFrom = Alignment.Bottom), + exit = shrinkVertically(tween(MINI_PLAYER_ANIM_MS), shrinkTowards = Alignment.Bottom), ) { val positionMs by playbackController.positionMs.collectAsStateWithLifecycle() val miniArt = playbackController.coverArt(playback.item, thumbnail = true) diff --git a/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt b/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt index bf886e3a..43d945e5 100644 --- a/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt +++ b/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt @@ -28,6 +28,7 @@ import okhttp3.OkHttpClient import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -161,17 +162,16 @@ class PlaybackControllerTest { } @Test - fun restoringMarksTheStateUntilPlaybackStarts() { + fun theReadyStateResolvesWithTheRestoredItemAlreadyInPlace() { seedAlbum(5, sourceId = 1) runBlocking { queues.replace(listOf(queues.albumEntry(1, "al1"))) } + assertFalse(controller.ready.value) + controller.connect() - await { handle.items.isNotEmpty() } + await { controller.ready.value } - assertTrue(controller.state.value.restored) - - controller.playAlbum(1, "al1", 0) - await { !controller.state.value.restored } + assertNotNull("the mini player must be drawable once ready", controller.state.value.item) } @Test