diff --git a/android/app/src/main/java/com/fabledsword/minstrel/nav/MinstrelNavGraph.kt b/android/app/src/main/java/com/fabledsword/minstrel/nav/MinstrelNavGraph.kt index e2bde56a..5baafa16 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/nav/MinstrelNavGraph.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/nav/MinstrelNavGraph.kt @@ -60,42 +60,42 @@ private fun NavGraphBuilder.inShellTopLevel( expandPlayer: () -> Unit, ) { composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { HomeScreen(navController = navController) } } composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { LibraryScreen(navController = navController) } } composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { SearchScreen(navController = navController) } } composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { DiscoverScreen(navController = navController) } } composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { PlaylistsListScreen(navController = navController) } } composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { SettingsScreen(navController = navController) } } composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { AdminLandingScreen(navController = navController) } } composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { RequestsScreen(navController = navController) } } @@ -106,32 +106,32 @@ private fun NavGraphBuilder.inShellDetail( expandPlayer: () -> Unit, ) { composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { AlbumDetailScreen(navController = navController) } } composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { ArtistDetailScreen(navController = navController) } } composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { PlaylistDetailScreen(navController = navController) } } composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { AdminRequestsScreen(navController = navController) } } composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { AdminQuarantineScreen(navController = navController) } } composable { - ShellScaffold(onExpandPlayer = expandPlayer) { + ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) { AdminUsersScreen(navController = navController) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/player/ui/MiniPlayer.kt b/android/app/src/main/java/com/fabledsword/minstrel/player/ui/MiniPlayer.kt index 13e6e4b6..306d410a 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/player/ui/MiniPlayer.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/player/ui/MiniPlayer.kt @@ -32,6 +32,7 @@ import com.composables.icons.lucide.Lucide import com.composables.icons.lucide.Music import com.composables.icons.lucide.Pause import com.composables.icons.lucide.Play +import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton import com.fabledsword.minstrel.theme.FabledSwordFlatTokens private const val MINI_BAR_HEIGHT_DP = 64 @@ -77,6 +78,8 @@ private fun MiniCover(coverUrl: String, contentDescription: String) { @Composable fun MiniPlayer( onExpandClick: () -> Unit, + onNavigateToAlbum: (String) -> Unit, + onNavigateToArtist: (String) -> Unit, modifier: Modifier = Modifier, viewModel: PlayerViewModel = hiltViewModel(), ) { @@ -122,6 +125,14 @@ fun MiniPlayer( tint = MaterialTheme.colorScheme.onSurface, ) } + // hideQueueActions=true: the playing track is itself the + // queue entry, so Play next / Add to queue would be silly. + TrackActionsButton( + track = track, + hideQueueActions = true, + onNavigateToAlbum = onNavigateToAlbum, + onNavigateToArtist = onNavigateToArtist, + ) } } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/ShellScaffold.kt b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/ShellScaffold.kt index 87391fa0..5bfc5a1f 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/ShellScaffold.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/ShellScaffold.kt @@ -4,14 +4,26 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavHostController +import com.fabledsword.minstrel.nav.AlbumDetail +import com.fabledsword.minstrel.nav.ArtistDetail import com.fabledsword.minstrel.player.ui.MiniPlayer +import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel /** * Outer shell wrapper for "in-app" routes — banners on top * (conditional, none implemented yet), routed screen filling the * middle, MiniPlayer pinned at the bottom (auto-hides when no track). + * A shell-scoped SnackbarHost above the MiniPlayer surfaces transient + * messages emitted by the MiniPlayer's TrackActionsButton (snackbars + * for in-screen kebabs continue to live on each screen's own Scaffold). * * Mirrors the Flutter `_ShellWithPlayerBar` (lib/shared/routing.dart). * @@ -21,15 +33,24 @@ import com.fabledsword.minstrel.player.ui.MiniPlayer * intentionally do NOT — they fill the entire screen, often with a * back-only AppBar. * - * `onExpandPlayer` is the navigate-to-NowPlaying callback; passed - * through to MiniPlayer so it doesn't have to know the nav graph. + * [onExpandPlayer] is the navigate-to-NowPlaying callback passed + * through to MiniPlayer's tap-handler. [navController] is needed by + * the MiniPlayer kebab for go-to-album / go-to-artist nav. */ @Composable fun ShellScaffold( + navController: NavHostController, onExpandPlayer: () -> Unit, modifier: Modifier = Modifier, + trackActionsViewModel: TrackActionsViewModel = hiltViewModel(), content: @Composable () -> Unit, ) { + val snackbarHostState = remember { SnackbarHostState() } + LaunchedEffect(Unit) { + trackActionsViewModel.transientMessages.collect { msg -> + snackbarHostState.showSnackbar(msg) + } + } Column(modifier = modifier.fillMaxSize()) { // Banners (VersionTooOld, UpdateBanner) live here once those // mechanisms exist; for now the slot is empty so the layout @@ -37,6 +58,11 @@ fun ShellScaffold( Box(modifier = Modifier.fillMaxWidth().weight(1f)) { content() } - MiniPlayer(onExpandClick = onExpandPlayer) + SnackbarHost(hostState = snackbarHostState) + MiniPlayer( + onExpandClick = onExpandPlayer, + onNavigateToAlbum = { id -> navController.navigate(AlbumDetail(id)) }, + onNavigateToArtist = { id -> navController.navigate(ArtistDetail(id)) }, + ) } }