fix(web): /register reachable for bootstrap admin (closes #376) #32

Merged
bvandeusen merged 262 commits from dev into main 2026-05-08 18:01:39 -04:00
3 changed files with 54 additions and 17 deletions
Showing only changes of commit 4a3215cc07 - Show all commits
@@ -60,42 +60,42 @@ private fun NavGraphBuilder.inShellTopLevel(
expandPlayer: () -> Unit, expandPlayer: () -> Unit,
) { ) {
composable<Home> { composable<Home> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
HomeScreen(navController = navController) HomeScreen(navController = navController)
} }
} }
composable<Library> { composable<Library> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
LibraryScreen(navController = navController) LibraryScreen(navController = navController)
} }
} }
composable<Search> { composable<Search> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
SearchScreen(navController = navController) SearchScreen(navController = navController)
} }
} }
composable<Discover> { composable<Discover> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
DiscoverScreen(navController = navController) DiscoverScreen(navController = navController)
} }
} }
composable<Playlists> { composable<Playlists> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
PlaylistsListScreen(navController = navController) PlaylistsListScreen(navController = navController)
} }
} }
composable<Settings> { composable<Settings> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
SettingsScreen(navController = navController) SettingsScreen(navController = navController)
} }
} }
composable<Admin> { composable<Admin> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
AdminLandingScreen(navController = navController) AdminLandingScreen(navController = navController)
} }
} }
composable<Requests> { composable<Requests> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
RequestsScreen(navController = navController) RequestsScreen(navController = navController)
} }
} }
@@ -106,32 +106,32 @@ private fun NavGraphBuilder.inShellDetail(
expandPlayer: () -> Unit, expandPlayer: () -> Unit,
) { ) {
composable<AlbumDetail> { composable<AlbumDetail> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
AlbumDetailScreen(navController = navController) AlbumDetailScreen(navController = navController)
} }
} }
composable<ArtistDetail> { composable<ArtistDetail> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
ArtistDetailScreen(navController = navController) ArtistDetailScreen(navController = navController)
} }
} }
composable<PlaylistDetail> { composable<PlaylistDetail> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
PlaylistDetailScreen(navController = navController) PlaylistDetailScreen(navController = navController)
} }
} }
composable<AdminRequests> { composable<AdminRequests> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
AdminRequestsScreen(navController = navController) AdminRequestsScreen(navController = navController)
} }
} }
composable<AdminQuarantine> { composable<AdminQuarantine> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
AdminQuarantineScreen(navController = navController) AdminQuarantineScreen(navController = navController)
} }
} }
composable<AdminUsers> { composable<AdminUsers> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(navController = navController, onExpandPlayer = expandPlayer) {
AdminUsersScreen(navController = navController) AdminUsersScreen(navController = navController)
} }
} }
@@ -32,6 +32,7 @@ import com.composables.icons.lucide.Lucide
import com.composables.icons.lucide.Music import com.composables.icons.lucide.Music
import com.composables.icons.lucide.Pause import com.composables.icons.lucide.Pause
import com.composables.icons.lucide.Play import com.composables.icons.lucide.Play
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
import com.fabledsword.minstrel.theme.FabledSwordFlatTokens import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
private const val MINI_BAR_HEIGHT_DP = 64 private const val MINI_BAR_HEIGHT_DP = 64
@@ -77,6 +78,8 @@ private fun MiniCover(coverUrl: String, contentDescription: String) {
@Composable @Composable
fun MiniPlayer( fun MiniPlayer(
onExpandClick: () -> Unit, onExpandClick: () -> Unit,
onNavigateToAlbum: (String) -> Unit,
onNavigateToArtist: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: PlayerViewModel = hiltViewModel(), viewModel: PlayerViewModel = hiltViewModel(),
) { ) {
@@ -122,6 +125,14 @@ fun MiniPlayer(
tint = MaterialTheme.colorScheme.onSurface, 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.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth 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.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier 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.player.ui.MiniPlayer
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
/** /**
* Outer shell wrapper for "in-app" routes — banners on top * Outer shell wrapper for "in-app" routes — banners on top
* (conditional, none implemented yet), routed screen filling the * (conditional, none implemented yet), routed screen filling the
* middle, MiniPlayer pinned at the bottom (auto-hides when no track). * 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). * 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 * intentionally do NOT — they fill the entire screen, often with a
* back-only AppBar. * back-only AppBar.
* *
* `onExpandPlayer` is the navigate-to-NowPlaying callback; passed * [onExpandPlayer] is the navigate-to-NowPlaying callback passed
* through to MiniPlayer so it doesn't have to know the nav graph. * through to MiniPlayer's tap-handler. [navController] is needed by
* the MiniPlayer kebab for go-to-album / go-to-artist nav.
*/ */
@Composable @Composable
fun ShellScaffold( fun ShellScaffold(
navController: NavHostController,
onExpandPlayer: () -> Unit, onExpandPlayer: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
trackActionsViewModel: TrackActionsViewModel = hiltViewModel(),
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
trackActionsViewModel.transientMessages.collect { msg ->
snackbarHostState.showSnackbar(msg)
}
}
Column(modifier = modifier.fillMaxSize()) { Column(modifier = modifier.fillMaxSize()) {
// Banners (VersionTooOld, UpdateBanner) live here once those // Banners (VersionTooOld, UpdateBanner) live here once those
// mechanisms exist; for now the slot is empty so the layout // mechanisms exist; for now the slot is empty so the layout
@@ -37,6 +58,11 @@ fun ShellScaffold(
Box(modifier = Modifier.fillMaxWidth().weight(1f)) { Box(modifier = Modifier.fillMaxWidth().weight(1f)) {
content() content()
} }
MiniPlayer(onExpandClick = onExpandPlayer) SnackbarHost(hostState = snackbarHostState)
MiniPlayer(
onExpandClick = onExpandPlayer,
onNavigateToAlbum = { id -> navController.navigate(AlbumDetail(id)) },
onNavigateToArtist = { id -> navController.navigate(ArtistDetail(id)) },
)
} }
} }