import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../providers/session_provider.dart'; import '../../providers/shell_backdrop_provider.dart'; import '../constants/breakpoints.dart'; import '../theme/app_theme.dart'; import 'app_bottom_nav.dart'; import 'shell_backdrop_layer.dart'; import 'sidebar.dart'; import 'window_chrome.dart'; import 'window_chrome_overlay.dart'; class AppShell extends ConsumerStatefulWidget { final StatefulNavigationShell navigationShell; final String location; const AppShell({ super.key, required this.navigationShell, required this.location, }); @override ConsumerState createState() => _AppShellState(); } class _AppShellState extends ConsumerState { @override void initState() { super.initState(); initSidebarState(ref); } @override Widget build(BuildContext context) { final immersive = widget.location == '/' || widget.location == '/discover' || widget.location.startsWith('/library/'); final sessionAsync = ref.watch(sessionProvider); final unauthenticatedHome = widget.location == '/' && sessionAsync.hasValue && ref.watch(activeSessionProvider) == null; final effectiveImmersive = immersive && !unauthenticatedHome; WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; ref.read(shellLocationProvider.notifier).set(widget.location); }); final themeBrightness = Theme.of(context).brightness; final overlayStyle = effectiveImmersive ? const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.light, statusBarBrightness: Brightness.dark, systemNavigationBarColor: Colors.transparent, systemNavigationBarIconBrightness: Brightness.light, ) : SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: themeBrightness == Brightness.dark ? Brightness.light : Brightness.dark, statusBarBrightness: themeBrightness, systemNavigationBarColor: Colors.transparent, systemNavigationBarIconBrightness: themeBrightness == Brightness.dark ? Brightness.light : Brightness.dark, ); return AnnotatedRegion( value: overlayStyle, child: Scaffold( backgroundColor: Colors.transparent, body: LayoutBuilder( builder: (context, constraints) { final isCompact = Breakpoints.isCompact(constraints.maxWidth); return Stack( children: [ Positioned.fill( child: ShellBackdropLayer(active: effectiveImmersive), ), if (isCompact) _CompactBody(navigationShell: widget.navigationShell) else Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Sidebar( selectedBranchIndex: widget.navigationShell.currentIndex, onNavigateBranch: (index, {initialLocation = false}) => widget.navigationShell.goBranch( index, initialLocation: initialLocation, ), location: widget.location, immersiveRoute: effectiveImmersive, ), Expanded( child: RepaintBoundary(child: widget.navigationShell), ), ], ), _AppTitleBarOverlay(immersive: effectiveImmersive), ], ); }, ), ), ); } } class _CompactBody extends StatelessWidget { final StatefulNavigationShell navigationShell; const _CompactBody({required this.navigationShell}); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.transparent, extendBody: true, body: RepaintBoundary(child: navigationShell), bottomNavigationBar: AppBottomNav(navigationShell: navigationShell), ); } } class _AppTitleBarOverlay extends ConsumerWidget { final bool immersive; const _AppTitleBarOverlay({required this.immersive}); @override Widget build(BuildContext context, WidgetRef ref) { if (!WindowChrome.isDesktop) { return const SizedBox.shrink(); } final tokens = context.appTokens; final immersiveBackdrop = immersive && ref.watch(shellBackdropProvider) != null; if (!immersiveBackdrop) { return const WindowChromeOverlay(); } return Positioned( top: 0, left: 0, right: 0, height: tokens.titleBarHeight, child: const Stack( children: [ Positioned.fill( child: IgnorePointer( child: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0x2E000000), Color(0x00000000)], ), ), ), ), ), WindowChromeOverlay(onDarkSurface: true), ], ), ); } }