import 'dart:io' show Platform; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../theme/app_theme.dart'; class AppBottomNav extends StatelessWidget { final StatefulNavigationShell navigationShell; const AppBottomNav({super.key, required this.navigationShell}); static const _destinations = <_BottomDestination>[ _BottomDestination( branchIndex: 0, icon: Icons.video_library_outlined, selectedIcon: Icons.video_library, label: '媒体', ), _BottomDestination( branchIndex: 2, icon: Icons.history_outlined, selectedIcon: Icons.history, label: '观看', ), _BottomDestination( branchIndex: 1, icon: Icons.dns_outlined, selectedIcon: Icons.dns, label: '资源', ), _BottomDestination( branchIndex: 5, icon: Icons.settings_outlined, selectedIcon: Icons.settings, label: '设置', ), ]; @override Widget build(BuildContext context) { final tokens = context.appTokens; final theme = Theme.of(context); final currentBranch = navigationShell.currentIndex; final bottomPadding = MediaQuery.paddingOf(context).bottom; return SizedBox( height: 64 + bottomPadding + 28, child: Stack( clipBehavior: Clip.none, children: [ Positioned( left: 0, right: 0, bottom: 0, child: ClipRRect( borderRadius: BorderRadius.vertical( top: Radius.circular( Platform.isAndroid ? 24 : tokens.bottomNavRadius, ), ), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: Container( color: tokens.shellBg.withValues( alpha: Platform.isAndroid ? 0.88 : 0.78, ), padding: EdgeInsets.only(bottom: bottomPadding), height: 64 + bottomPadding, child: Row( children: [ for (var i = 0; i < _destinations.length; i++) ...[ if (i == 2) const SizedBox(width: 64), Expanded( child: _NavItem( destination: _destinations[i], selected: _destinations[i].branchIndex == currentBranch, onTap: () { final target = _destinations[i].branchIndex; navigationShell.goBranch( target, initialLocation: target == navigationShell.currentIndex, ); }, theme: theme, tokens: tokens, ), ), ], ], ), ), ), ), ), Positioned( bottom: 64 + bottomPadding - 28, left: 0, right: 0, child: Center( child: _SearchFab( onTap: () => context.push('/global-search'), theme: theme, ), ), ), ], ), ); } } class _NavItem extends StatelessWidget { final _BottomDestination destination; final bool selected; final VoidCallback onTap; final ThemeData theme; final AppTokens tokens; const _NavItem({ required this.destination, required this.selected, required this.onTap, required this.theme, required this.tokens, }); @override Widget build(BuildContext context) { final color = selected ? theme.colorScheme.primary : tokens.mutedForeground; return GestureDetector( behavior: HitTestBehavior.opaque, onTap: onTap, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( selected ? destination.selectedIcon : destination.icon, size: 24, color: color, ), const SizedBox(height: 4), Text( destination.label, style: TextStyle( fontSize: 10, fontWeight: selected ? FontWeight.w600 : FontWeight.w400, color: color, ), ), ], ), ); } } class _SearchFab extends StatelessWidget { final VoidCallback onTap; final ThemeData theme; const _SearchFab({required this.onTap, required this.theme}); @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Container( width: 56, height: 56, decoration: BoxDecoration( shape: BoxShape.circle, color: theme.colorScheme.onSurface, boxShadow: [ BoxShadow( color: theme.colorScheme.onSurface.withValues(alpha: 0.25), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Icon( Icons.search_rounded, size: 26, color: theme.colorScheme.surface, ), ), ); } } class _BottomDestination { final int branchIndex; final IconData icon; final IconData selectedIcon; final String label; const _BottomDestination({ required this.branchIndex, required this.icon, required this.selectedIcon, required this.label, }); }