From 42c12cbd3a30a1492fe360c3d229c88df0a28887 Mon Sep 17 00:00:00 2001 From: Honey Date: Sun, 14 Jun 2026 22:49:40 +0200 Subject: [PATCH] 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 --- .../com/novasonic/app/ui/AppNavigation.kt | 21 ++++++++++++------- .../novasonic/app/viewmodel/GenreViewModel.kt | 4 ++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/android/app/src/main/kotlin/com/novasonic/app/ui/AppNavigation.kt b/android/app/src/main/kotlin/com/novasonic/app/ui/AppNavigation.kt index fc043b6..72f8a2a 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/ui/AppNavigation.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/ui/AppNavigation.kt @@ -1,6 +1,7 @@ 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.core.FastOutSlowInEasing import androidx.compose.animation.core.tween @@ -69,10 +70,17 @@ fun AppNavigation() { val navController = rememberNavController() var showNowPlaying by remember { mutableStateOf(false) } - // Disable NavController's own back-stack handler while NowPlaying is open so - // our BackHandler below takes priority instead of it popping tabs first. - LaunchedEffect(showNowPlaying) { - navController.enableOnBackPressed(!showNowPlaying) + // Re-register a fresh callback each time NowPlaying opens so it is always + // the LAST entry in the dispatcher queue (LIFO = highest priority), beating + // the BackHandler that NavHost registers internally for its own back stack. + 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(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 AnimatedVisibility( visible = showNowPlaying, diff --git a/android/app/src/main/kotlin/com/novasonic/app/viewmodel/GenreViewModel.kt b/android/app/src/main/kotlin/com/novasonic/app/viewmodel/GenreViewModel.kt index 856e251..ba9c8eb 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/viewmodel/GenreViewModel.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/viewmodel/GenreViewModel.kt @@ -26,8 +26,8 @@ class GenreViewModel(app: Application) : AndroidViewModel(app) { viewModelScope.launch { _state.value = GenreUiState(isLoading = true) runCatching { repo.getGenres().filter { it.songCount > 0 }.sortedByDescending { it.songCount } } - .onSuccess { _state.value = GenreUiState(genres = it) } - .onFailure { _state.value = GenreUiState(error = it.message) } + .onSuccess { _state.value = GenreUiState(genres = it, isLoading = false) } + .onFailure { _state.value = GenreUiState(error = it.message, isLoading = false) } } } }