a05a279508
You correctly diagnosed the symptom: the AppBar gets taller when the banner is present. Why: the shell is [Banner, Expanded(child), PlayerBar] and the banner uses SafeArea(bottom:false) to clear the status bar. But MediaQuery.padding is screen-relative — the child's Scaffold/AppBar reads MediaQuery.padding.top and applies its own status-bar inset on top of the banner, doubling the gap. Watch shouldShowUpdateBannerProvider in the shell. When it returns non-null (banner visible), wrap the child in MediaQuery.removePadding removeTop: true so the AppBar mounts directly under the banner. When the banner is hidden, child gets its normal top inset.
122 lines
5.0 KiB
Dart
122 lines
5.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import '../auth/auth_provider.dart';
|
|
import '../auth/login_screen.dart';
|
|
import '../auth/server_url_screen.dart';
|
|
import '../library/album_detail_screen.dart';
|
|
import '../library/artist_detail_screen.dart';
|
|
import '../discover/discover_screen.dart';
|
|
import '../library/home_screen.dart';
|
|
import '../library/library_screen.dart';
|
|
import '../player/now_playing_screen.dart';
|
|
import '../player/player_bar.dart';
|
|
import '../player/queue_screen.dart';
|
|
import '../playlists/playlist_detail_screen.dart';
|
|
import '../playlists/playlists_list_screen.dart';
|
|
import '../requests/requests_screen.dart';
|
|
import '../search/search_screen.dart';
|
|
import '../settings/settings_screen.dart';
|
|
import '../update/client_update_provider.dart';
|
|
import '../update/update_banner.dart';
|
|
import '../admin/admin_landing_screen.dart';
|
|
import '../admin/admin_requests_screen.dart';
|
|
import '../admin/admin_quarantine_screen.dart';
|
|
import '../admin/admin_users_screen.dart';
|
|
import 'widgets/version_gate.dart';
|
|
|
|
/// Exposed as a Provider so its single argument is a real `Ref` (the
|
|
/// constructor's redirect closures use `ref.read`). Widgets consume the
|
|
/// router via `ref.watch(routerProvider)` instead of constructing it
|
|
/// themselves with a `WidgetRef`, which can't satisfy the `Ref` type.
|
|
final routerProvider = Provider<GoRouter>((ref) => buildRouter(ref));
|
|
|
|
GoRouter buildRouter(Ref ref) {
|
|
return GoRouter(
|
|
initialLocation: '/',
|
|
redirect: (ctx, state) async {
|
|
final url = await ref.read(serverUrlProvider.future);
|
|
if (url == null) return '/server-url';
|
|
final user = await ref.read(authControllerProvider.future);
|
|
final loc = state.matchedLocation;
|
|
if (user == null && loc != '/login' && loc != '/server-url') {
|
|
return '/login';
|
|
}
|
|
if (user != null && (loc == '/login' || loc == '/server-url')) {
|
|
return '/home';
|
|
}
|
|
if (loc.startsWith('/admin') && !user!.isAdmin) {
|
|
return '/home';
|
|
}
|
|
return null;
|
|
},
|
|
routes: [
|
|
GoRoute(path: '/', redirect: (_, __) => '/home'),
|
|
GoRoute(path: '/server-url', builder: (_, __) => const ServerUrlScreen()),
|
|
GoRoute(path: '/login', builder: (_, __) => const LoginScreen()),
|
|
ShellRoute(
|
|
builder: (ctx, state, child) => VersionGate(child: _ShellWithPlayerBar(child: child)),
|
|
routes: [
|
|
GoRoute(path: '/home', builder: (_, __) => const HomeScreen()),
|
|
GoRoute(
|
|
path: '/artists/:id',
|
|
builder: (_, s) => ArtistDetailScreen(id: s.pathParameters['id']!),
|
|
),
|
|
GoRoute(
|
|
path: '/albums/:id',
|
|
builder: (_, s) => AlbumDetailScreen(id: s.pathParameters['id']!),
|
|
),
|
|
GoRoute(path: '/now-playing', builder: (_, __) => const NowPlayingScreen()),
|
|
GoRoute(path: '/queue', builder: (_, __) => const QueueScreen()),
|
|
GoRoute(path: '/search', builder: (_, __) => const SearchScreen()),
|
|
GoRoute(path: '/library', builder: (_, __) => const LibraryScreen()),
|
|
GoRoute(path: '/discover', builder: (_, __) => const DiscoverScreen()),
|
|
GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()),
|
|
GoRoute(path: '/playlists', builder: (_, __) => const PlaylistsListScreen()),
|
|
GoRoute(
|
|
path: '/playlists/:id',
|
|
builder: (_, s) => PlaylistDetailScreen(id: s.pathParameters['id']!),
|
|
),
|
|
GoRoute(path: '/requests', builder: (_, __) => const RequestsScreen()),
|
|
GoRoute(path: '/admin', builder: (_, __) => const AdminLandingScreen()),
|
|
GoRoute(path: '/admin/requests', builder: (_, __) => const AdminRequestsScreen()),
|
|
GoRoute(path: '/admin/quarantine', builder: (_, __) => const AdminQuarantineScreen()),
|
|
GoRoute(path: '/admin/users', builder: (_, __) => const AdminUsersScreen()),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
class _ShellWithPlayerBar extends ConsumerWidget {
|
|
const _ShellWithPlayerBar({required this.child});
|
|
final Widget child;
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
// The banner sits above the routed child and uses SafeArea to clear
|
|
// the system status bar. MediaQuery.padding is screen-relative
|
|
// though, so the child's Scaffold/AppBar would still apply its own
|
|
// top status-bar inset on top of the banner — doubling the gap.
|
|
// 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;
|
|
return Column(
|
|
children: [
|
|
const UpdateBanner(),
|
|
Expanded(
|
|
child: hasBanner
|
|
? MediaQuery.removePadding(
|
|
context: context,
|
|
removeTop: true,
|
|
child: child,
|
|
)
|
|
: child,
|
|
),
|
|
const PlayerBar(),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|