feat(library): extract track duration via ffprobe #13

Merged
bvandeusen merged 262 commits from dev into main 2026-04-20 08:11:10 -04:00
Showing only changes of commit c33c1178ab - Show all commits
@@ -5,6 +5,7 @@ import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
@@ -28,85 +29,82 @@ fun MinstrelNavGraph(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val expandPlayer = { navController.navigate(NowPlaying) } val expandPlayer = { navController.navigate(NowPlaying) }
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = Library, startDestination = Library,
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
) { ) {
// ── In-shell top-level destinations ─────────────────────────── inShellTopLevel(navController, expandPlayer)
inShellDetail(expandPlayer)
outsideShell()
}
}
private fun NavGraphBuilder.inShellTopLevel(
navController: NavHostController,
expandPlayer: () -> Unit,
) {
composable<Home> { composable<Home> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(onExpandPlayer = expandPlayer) {
ComingSoon("Home", "Lands in the next design-drift sub-task.") ComingSoon("Home", "Lands in the next design-drift sub-task.")
} }
} }
composable<Library> { composable<Library> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(onExpandPlayer = expandPlayer) {
LibraryScreen(navController = navController) LibraryScreen(navController = navController)
} }
} }
composable<Search> { composable<Search> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(onExpandPlayer = expandPlayer) {
ComingSoon("Search", "Lands in a later phase.") ComingSoon("Search", "Lands in a later phase.")
} }
} }
composable<Discover> { composable<Discover> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(onExpandPlayer = expandPlayer) {
ComingSoon("Discover", "Lands in Phase 10 (Lidarr requests).") ComingSoon("Discover", "Lands in Phase 10 (Lidarr requests).")
} }
} }
composable<Playlists> { composable<Playlists> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(onExpandPlayer = expandPlayer) {
ComingSoon("Playlists", "Lands in Phase 7.") ComingSoon("Playlists", "Lands in Phase 7.")
} }
} }
composable<Settings> { composable<Settings> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(onExpandPlayer = expandPlayer) {
ComingSoon("Settings", "Lands in Phase 11.") ComingSoon("Settings", "Lands in Phase 11.")
} }
} }
composable<Admin> { composable<Admin> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(onExpandPlayer = expandPlayer) {
ComingSoon("Admin", "Admin landing — lands in a later phase.") ComingSoon("Admin", "Admin landing — lands in a later phase.")
} }
} }
composable<Requests> { composable<Requests> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(onExpandPlayer = expandPlayer) {
ComingSoon("Requests", "Your Lidarr requests — Phase 10.") ComingSoon("Requests", "Your Lidarr requests — Phase 10.")
} }
} }
}
// ── In-shell detail screens (push-on-top) ───────────────────── private fun NavGraphBuilder.inShellDetail(expandPlayer: () -> Unit) {
composable<AlbumDetail> { backStackEntry -> composable<AlbumDetail> { backStackEntry ->
val route: AlbumDetail = backStackEntry.toRoute() val route: AlbumDetail = backStackEntry.toRoute()
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(onExpandPlayer = expandPlayer) {
ComingSoon("Album", "Album detail for ${route.id} — later 5.x slice.") ComingSoon("Album", "Album detail for ${route.id} — later 5.x slice.")
} }
} }
composable<ArtistDetail> { backStackEntry -> composable<ArtistDetail> { backStackEntry ->
val route: ArtistDetail = backStackEntry.toRoute() val route: ArtistDetail = backStackEntry.toRoute()
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(onExpandPlayer = expandPlayer) {
ComingSoon("Artist", "Artist detail for ${route.id} — later 5.x slice.") ComingSoon("Artist", "Artist detail for ${route.id} — later 5.x slice.")
} }
} }
composable<PlaylistDetail> { backStackEntry -> composable<PlaylistDetail> { backStackEntry ->
val route: PlaylistDetail = backStackEntry.toRoute() val route: PlaylistDetail = backStackEntry.toRoute()
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(onExpandPlayer = expandPlayer) {
ComingSoon("Playlist", "Playlist detail for ${route.id} — Phase 7.") ComingSoon("Playlist", "Playlist detail for ${route.id} — Phase 7.")
} }
} }
composable<AdminRequests> { composable<AdminRequests> {
ShellScaffold(onExpandPlayer = expandPlayer) { ShellScaffold(onExpandPlayer = expandPlayer) {
ComingSoon("Admin · Requests", "Lands with admin slice.") ComingSoon("Admin · Requests", "Lands with admin slice.")
@@ -122,9 +120,9 @@ fun MinstrelNavGraph(
ComingSoon("Admin · Users", "Lands with admin slice.") ComingSoon("Admin · Users", "Lands with admin slice.")
} }
} }
}
// ── Outside-shell full-screen routes ────────────────────────── private fun NavGraphBuilder.outsideShell() {
composable<NowPlaying>( composable<NowPlaying>(
// Slide up from the bottom on enter; back down on dismiss. // Slide up from the bottom on enter; back down on dismiss.
// Mirrors the Flutter CustomTransitionPage (280ms easeOutCubic). // Mirrors the Flutter CustomTransitionPage (280ms easeOutCubic).
@@ -135,16 +133,11 @@ fun MinstrelNavGraph(
) { ) {
NowPlayingScreen() NowPlayingScreen()
} }
composable<Queue> { ComingSoon("Queue", "Lands in a later 6.x slice.") } composable<Queue> { ComingSoon("Queue", "Lands in a later 6.x slice.") }
composable<ServerUrl> { composable<ServerUrl> {
ComingSoon("Connect to your Minstrel", "Server-URL entry — Phase 11.") ComingSoon("Connect to your Minstrel", "Server-URL entry — Phase 11.")
} }
composable<Login> { composable<Login> { ComingSoon("Sign in", "Login flow — Phase 11.") }
ComingSoon("Sign in", "Login flow — Phase 11.")
}
}
} }
@Composable @Composable