feat(android): MiniPlayer TrackActions kebab + shell-level snackbar
Closes the deferred MiniPlayer follow-up from the TrackActions slice. MiniPlayer gains a TrackActionsButton next to play/pause with hideQueueActions=true (the playing track is the queue entry itself). Shell architecture: ShellScaffold now takes navController + owns a shell-scoped SnackbarHost backed by a TrackActionsViewModel hiltViewModel() at the shell level. Snackbars triggered by the MiniPlayer's kebab surface there; per-screen kebabs continue to flow through each screen's own Scaffold SnackbarHost (independent collectors so the two never compete). All 14 ShellScaffold call sites in MinstrelNavGraph updated to pass navController; mechanical sweep. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,42 +60,42 @@ private fun NavGraphBuilder.inShellTopLevel(
|
||||
expandPlayer: () -> Unit,
|
||||
) {
|
||||
composable<Home> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
HomeScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable<Library> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
LibraryScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable<Search> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
SearchScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable<Discover> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
DiscoverScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable<Playlists> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
PlaylistsListScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable<Settings> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
SettingsScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable<Admin> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
AdminLandingScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable<Requests> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
RequestsScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
@@ -106,32 +106,32 @@ private fun NavGraphBuilder.inShellDetail(
|
||||
expandPlayer: () -> Unit,
|
||||
) {
|
||||
composable<AlbumDetail> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
AlbumDetailScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable<ArtistDetail> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
ArtistDetailScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable<PlaylistDetail> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
PlaylistDetailScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable<AdminRequests> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
AdminRequestsScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable<AdminQuarantine> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
AdminQuarantineScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable<AdminUsers> {
|
||||
ShellScaffold(onExpandPlayer = expandPlayer) {
|
||||
ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
|
||||
AdminUsersScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user