diff --git a/flutter_client/lib/shared/routing.dart b/flutter_client/lib/shared/routing.dart index d66985bb..9d8b55fe 100644 --- a/flutter_client/lib/shared/routing.dart +++ b/flutter_client/lib/shared/routing.dart @@ -55,6 +55,34 @@ GoRouter buildRouter(Ref ref) { GoRoute(path: '/', redirect: (_, __) => '/home'), GoRoute(path: '/server-url', builder: (_, __) => const ServerUrlScreen()), GoRoute(path: '/login', builder: (_, __) => const LoginScreen()), + // /now-playing lives outside the ShellRoute on purpose. The full + // player IS the player UI when active, and we don't want the mini + // bar from the shell underneath fighting for the bottom strip. + // Pushing this route unmounts the shell entirely; the slide-up + // transition still feels right because the shell stays painted + // for the duration of the animation. + GoRoute( + path: '/now-playing', + pageBuilder: (_, __) => CustomTransitionPage( + child: const NowPlayingScreen(), + transitionDuration: const Duration(milliseconds: 280), + reverseTransitionDuration: const Duration(milliseconds: 240), + transitionsBuilder: (_, anim, __, child) { + final eased = CurvedAnimation( + parent: anim, + curve: Curves.easeOutCubic, + reverseCurve: Curves.easeInCubic, + ); + return SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: Offset.zero, + ).animate(eased), + child: child, + ); + }, + ), + ), ShellRoute( builder: (ctx, state, child) => VersionGate(child: _ShellWithPlayerBar(child: child)), routes: [ @@ -67,32 +95,6 @@ GoRouter buildRouter(Ref ref) { path: '/albums/:id', builder: (_, s) => AlbumDetailScreen(id: s.pathParameters['id']!), ), - GoRoute( - path: '/now-playing', - // Slide-up transition: full player ascends from the bottom - // edge (matching where the mini player sits) into a full - // screen view. Drag-down dismissal in NowPlayingScreen - // mirrors this with a slide back down via Navigator.pop. - pageBuilder: (_, __) => CustomTransitionPage( - child: const NowPlayingScreen(), - transitionDuration: const Duration(milliseconds: 280), - reverseTransitionDuration: const Duration(milliseconds: 240), - transitionsBuilder: (_, anim, __, child) { - final eased = CurvedAnimation( - parent: anim, - curve: Curves.easeOutCubic, - reverseCurve: Curves.easeInCubic, - ); - return SlideTransition( - position: Tween( - begin: const Offset(0, 1), - end: Offset.zero, - ).animate(eased), - child: child, - ); - }, - ), - ), GoRoute(path: '/queue', builder: (_, __) => const QueueScreen()), GoRoute(path: '/search', builder: (_, __) => const SearchScreen()), GoRoute(path: '/library', builder: (_, __) => const LibraryScreen()), @@ -126,11 +128,6 @@ class _ShellWithPlayerBar extends ConsumerWidget { // When the banner is showing, strip that inset from the child so // its AppBar lands directly under the banner. final hasBanner = ref.watch(shouldShowUpdateBannerProvider) != null; - // Hide the mini player when the full player is on top — the full - // player IS the player UI in that state, and overlapping them - // would just fight for visual space. - final loc = GoRouterState.of(context).matchedLocation; - final hideMini = loc == '/now-playing'; return Column( children: [ const UpdateBanner(), @@ -143,7 +140,7 @@ class _ShellWithPlayerBar extends ConsumerWidget { ) : child, ), - if (!hideMini) const PlayerBar(), + const PlayerBar(), ], ); }