import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class AppShell extends StatefulWidget { final Widget child; const AppShell({super.key, required this.child}); @override State createState() => _AppShellState(); } class _AppShellState extends State { static const double _railBreakpoint = 800; // tablet and up bool _railExtended = true; // start "open" on tablet/desktop final FocusNode _contentFocus = FocusNode(debugLabel: 'contentFocus'); @override void dispose() { _contentFocus.dispose(); super.dispose(); } // --- NAV ITEMS ------------------------------------------------------------- final _items = const < ({IconData icon, IconData? selectedIcon, String label, String route}) >[ ( icon: Icons.dashboard_outlined, selectedIcon: Icons.dashboard, label: 'Dashboard', route: '/home', ), ( icon: Icons.account_balance_wallet_outlined, selectedIcon: Icons.account_balance_wallet, label: 'Budgets', route: '/budget', ), ( icon: Icons.inventory_2_outlined, selectedIcon: Icons.inventory_2, label: 'Inventar', route: '/inventory', ), ( icon: Icons.bar_chart_outlined, selectedIcon: Icons.bar_chart, label: 'Reports', route: '/reports', ), ( icon: Icons.settings, selectedIcon: Icons.settings_outlined, label: 'Settings', route: '/settings', ), ]; int _indexForPath(String p) { for (var i = 0; i < _items.length; i++) { if (p.startsWith(_items[i].route)) return i; } return 0; } void _goForIndex(BuildContext ctx, int i) => ctx.go(_items[i].route); @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; final isRail = width >= _railBreakpoint; final currentPath = GoRouterState.of(context).matchedLocation; // keep focus on right/content pane on wide layouts if (isRail) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted && !_contentFocus.hasFocus) _contentFocus.requestFocus(); }); } final appBar = AppBar( title: _LogoHeader(), leading: isRail ? null : Builder( builder: (ctx) => IconButton( icon: const Icon(Icons.menu), onPressed: () => Scaffold.of(ctx).openDrawer(), tooltip: 'MenĂ¼', ), ), actions: [ IconButton( tooltip: 'Benachrichtigungen', onPressed: () {}, icon: const Icon(Icons.notifications_none), ), IconButton( tooltip: 'Benutzer-Einstellungen', onPressed: () => context.push('/settings'), icon: const Icon(Icons.account_circle_outlined), ), ], ); if (!isRail) { // ------------------- MOBILE: Drawer ------------------- final selectedIndex = _indexForPath(currentPath); return Scaffold( appBar: appBar, drawer: _AppDrawer(items: _items, selectedIndex: selectedIndex), body: SafeArea(child: widget.child), ); } // ------------------- TABLET/DESKTOP: NavigationRail ------------------- final selected = _indexForPath(currentPath); final scheme = Theme.of(context).colorScheme; return Scaffold( appBar: appBar, body: Row( children: [ NavigationRailTheme( data: NavigationRailThemeData( groupAlignment: -1.0, // align to top useIndicator: true, indicatorColor: scheme.secondaryContainer, // background for selected "button" indicatorShape: const StadiumBorder(), selectedIconTheme: IconThemeData( color: scheme.onSecondaryContainer, ), selectedLabelTextStyle: TextStyle( color: scheme.onSecondaryContainer, fontWeight: FontWeight.w600, ), ), child: NavigationRail( extended: _railExtended, selectedIndex: selected, onDestinationSelected: (i) => _goForIndex(context, i), // leading: const Padding( // padding: EdgeInsets.only(top: 8), // child: _LogoHeader(), // ), destinations: [ for (final it in _items) NavigationRailDestination( icon: Icon(it.icon), selectedIcon: Icon(it.selectedIcon ?? it.icon), label: Text(it.label), ), ], trailingAtBottom: true, trailing: Column( children: [ const SizedBox(height: 8), const Divider(height: 1), // toggle lives at the very bottom so layout doesn't jump IconButton( tooltip: _railExtended ? 'Leiste verkleinern' : 'Leiste erweitern', onPressed: () => setState(() => _railExtended = !_railExtended), icon: Icon( _railExtended ? Icons.keyboard_double_arrow_left : Icons.keyboard_double_arrow_right, ), ), ], ), ), ), const VerticalDivider(width: 1), Expanded( child: SafeArea( child: Focus( focusNode: _contentFocus, autofocus: true, child: widget.child, ), ), ), ], ), ); } } class _AppDrawer extends StatelessWidget { final List< ({IconData icon, IconData? selectedIcon, String label, String route}) > items; final int selectedIndex; const _AppDrawer({required this.items, required this.selectedIndex}); @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; return Drawer( child: SafeArea( child: ListTileTheme( selectedColor: scheme.onSecondaryContainer, selectedTileColor: scheme.secondaryContainer, child: ListView( padding: const EdgeInsets.symmetric(vertical: 8), children: [ const _LogoHeader(), const Divider(), for (var i = 0; i < items.length; i++) ListTile( selected: i == selectedIndex, leading: Icon( i == selectedIndex ? (items[i].selectedIcon ?? items[i].icon) : items[i].icon, ), title: Text(items[i].label), onTap: () { Navigator.of(context).maybePop(); context.go(items[i].route); }, ), const Divider(), ListTile( leading: const Icon(Icons.settings_outlined), title: const Text('Einstellungen'), onTap: () { Navigator.of(context).maybePop(); context.go('/settings'); }, ), ], ), ), ), ); } } class _LogoHeader extends StatelessWidget { const _LogoHeader(); @override Widget build(BuildContext context) { return const Padding( padding: EdgeInsets.all(16), child: Row( children: [ Icon(Icons.account_balance_wallet, size: 28), SizedBox(width: 8), ], ), ); } }