fix: genre loading spinner + NowPlaying back button priority

GenreViewModel: GenreUiState(genres = it) left isLoading=true (default),
so genres loaded but spinner never cleared. Added isLoading=false explicitly.

AppNavigation: replace BackHandler + navController.enableOnBackPressed with a
DisposableEffect that re-adds an OnBackPressedCallback each time NowPlaying
opens. Since callbacks are LIFO, a freshly-added callback always beats the
BackHandler NavHost registers internally, regardless of composition order.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-06-14 22:49:40 +02:00
1 parent f01fc71f54
commit 42c12cbd3a
2 files changed
+15 -10

No files matched your search

@@ -1,6 +1,7 @@
package com.novasonic.app.ui package com.novasonic.app.ui
import androidx.activity.compose.BackHandler import androidx.activity.OnBackPressedCallback
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
@@ -69,10 +70,17 @@ fun AppNavigation() {
val navController = rememberNavController() val navController = rememberNavController()
var showNowPlaying by remember { mutableStateOf(false) } var showNowPlaying by remember { mutableStateOf(false) }
// Disable NavController's own back-stack handler while NowPlaying is open so // Re-register a fresh callback each time NowPlaying opens so it is always
// our BackHandler below takes priority instead of it popping tabs first. // the LAST entry in the dispatcher queue (LIFO = highest priority), beating
LaunchedEffect(showNowPlaying) { // the BackHandler that NavHost registers internally for its own back stack.
navController.enableOnBackPressed(!showNowPlaying) val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
DisposableEffect(showNowPlaying, backDispatcher) {
if (!showNowPlaying || backDispatcher == null) return@DisposableEffect onDispose {}
val cb = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() { showNowPlaying = false }
}
backDispatcher.addCallback(cb)
onDispose { cb.remove() }
} }
var contextMenuSong by remember { mutableStateOf<Song?>(null) } var contextMenuSong by remember { mutableStateOf<Song?>(null) }
@@ -298,9 +306,6 @@ fun AppNavigation() {
} }
} }
// Dismiss NowPlaying with hardware back button
BackHandler(enabled = showNowPlaying) { showNowPlaying = false }
// NowPlaying overlay — outside Scaffold so WindowInsets are not double-consumed // NowPlaying overlay — outside Scaffold so WindowInsets are not double-consumed
AnimatedVisibility( AnimatedVisibility(
visible = showNowPlaying, visible = showNowPlaying,
@@ -26,8 +26,8 @@ class GenreViewModel(app: Application) : AndroidViewModel(app) {
viewModelScope.launch { viewModelScope.launch {
_state.value = GenreUiState(isLoading = true) _state.value = GenreUiState(isLoading = true)
runCatching { repo.getGenres().filter { it.songCount > 0 }.sortedByDescending { it.songCount } } runCatching { repo.getGenres().filter { it.songCount > 0 }.sortedByDescending { it.songCount } }
.onSuccess { _state.value = GenreUiState(genres = it) } .onSuccess { _state.value = GenreUiState(genres = it, isLoading = false) }
.onFailure { _state.value = GenreUiState(error = it.message) } .onFailure { _state.value = GenreUiState(error = it.message, isLoading = false) }
} }
} }
} }