import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/api/common_providers.dart'; import '../home/providers.dart' show dataQualityProvider; import 'nav_destinations.dart'; /// The Grimmory-style sidebar shown at the extended breakpoint (>=1200): destinations /// grouped under muted caps headers, a left accent bar on the active row, a counter next /// to a destination where one is meaningful, and the signed-in account pinned at the /// bottom. Below 1200 the shell falls back to a plain [NavigationRail] — there is no room /// for group headers or a profile block in an icon-only rail. class NavSidebar extends ConsumerWidget { const NavSidebar({required this.selectedIndex, required this.onSelect, super.key}); final int selectedIndex; final ValueChanged onSelect; @override Widget build(BuildContext context, WidgetRef ref) { final issueCount = ref.watch(dataQualityProvider).valueOrNull?.data.length; final byGroup = >{}; for (var i = 0; i < navDestinations.length; i++) { byGroup.putIfAbsent(navDestinations[i].group, () => []).add(i); } return Container( width: 240, color: Theme.of(context).colorScheme.surfaceContainerLow, child: Column( children: [ Expanded( child: ListView( padding: const EdgeInsets.symmetric(vertical: 12), children: [ for (final group in NavGroup.values) if (byGroup[group] case final indices?) _NavGroupSection( label: navGroupLabels[group]!, children: [ for (final i in indices) _NavRow( destination: navDestinations[i], selected: i == selectedIndex, counter: navDestinations[i].path == '/health' ? issueCount : null, onTap: () => onSelect(i), ), ], ), ], ), ), const Divider(height: 1), const _ProfileFooter(), ], ), ); } } class _NavGroupSection extends StatelessWidget { const _NavGroupSection({required this.label, required this.children}); final String label; final List children; @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.fromLTRB(20, 12, 12, 6), child: Text( label, style: Theme.of(context).textTheme.labelSmall?.copyWith( color: scheme.onSurfaceVariant, letterSpacing: 1.1, fontWeight: FontWeight.w600, ), ), ), ...children, ], ); } } class _NavRow extends StatelessWidget { const _NavRow({required this.destination, required this.selected, required this.onTap, this.counter}); final NavDestination destination; final bool selected; final VoidCallback onTap; final int? counter; @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.fromLTRB(8, 0, 12, 2), child: Material( color: selected ? scheme.primaryContainer.withValues(alpha: 0.35) : Colors.transparent, borderRadius: BorderRadius.circular(10), child: InkWell( borderRadius: BorderRadius.circular(10), onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric(vertical: 9), child: Row( children: [ Container( width: 3, height: 18, margin: const EdgeInsets.only(right: 9), decoration: BoxDecoration( color: selected ? scheme.primary : Colors.transparent, borderRadius: BorderRadius.circular(2), ), ), Icon( selected ? destination.selectedIcon : destination.icon, size: 20, color: selected ? scheme.primary : scheme.onSurfaceVariant, ), const SizedBox(width: 12), Expanded( child: Text( destination.label, overflow: TextOverflow.ellipsis, style: TextStyle( color: selected ? scheme.primary : scheme.onSurface, fontWeight: selected ? FontWeight.w600 : FontWeight.w400, ), ), ), if (counter != null && counter! > 0) Text( '$counter', style: Theme.of(context).textTheme.bodySmall?.copyWith(color: scheme.onSurfaceVariant), ), ], ), ), ), ), ); } } class _ProfileFooter extends ConsumerWidget { const _ProfileFooter(); @override Widget build(BuildContext context, WidgetRef ref) { final scheme = Theme.of(context).colorScheme; final theme = Theme.of(context); final email = ref.watch(meProvider).valueOrNull?.email; final name = email?.split('@').first; final initial = (name?.isNotEmpty ?? false) ? name![0].toUpperCase() : '?'; return Padding( padding: const EdgeInsets.all(16), child: Row( children: [ CircleAvatar( radius: 18, backgroundColor: scheme.primaryContainer, child: Text( initial, style: TextStyle(color: scheme.onPrimaryContainer, fontWeight: FontWeight.w700), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( name ?? '…', overflow: TextOverflow.ellipsis, style: theme.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600), ), Text( email ?? '', overflow: TextOverflow.ellipsis, style: theme.textTheme.bodySmall?.copyWith(color: scheme.onSurfaceVariant), ), ], ), ), ], ), ); } }