import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:trainhub_flutter/core/constants/ui_constants.dart'; import 'package:trainhub_flutter/core/router/app_router.dart'; import 'package:trainhub_flutter/core/theme/app_colors.dart'; @RoutePage() class ShellPage extends StatelessWidget { const ShellPage({super.key}); @override Widget build(BuildContext context) { return AutoTabsRouter( routes: const [ HomeRoute(), TrainingsRoute(), AnalysisRoute(), CalendarRoute(), ChatRoute(), ], builder: (context, child) { final tabsRouter = AutoTabsRouter.of(context); return Scaffold( body: Row( children: [ _Sidebar( activeIndex: tabsRouter.activeIndex, onDestinationSelected: tabsRouter.setActiveIndex, ), Expanded(child: child), ], ), ); }, ); } } // --------------------------------------------------------------------------- // Sidebar // --------------------------------------------------------------------------- class _NavItemData { final IconData icon; final IconData activeIcon; final String label; const _NavItemData({ required this.icon, required this.activeIcon, required this.label, }); } class _Sidebar extends StatelessWidget { final int activeIndex; final ValueChanged onDestinationSelected; const _Sidebar({ required this.activeIndex, required this.onDestinationSelected, }); static const _items = [ _NavItemData( icon: Icons.dashboard_outlined, activeIcon: Icons.dashboard_rounded, label: 'Home', ), _NavItemData( icon: Icons.fitness_center_outlined, activeIcon: Icons.fitness_center, label: 'Trainings', ), _NavItemData( icon: Icons.video_library_outlined, activeIcon: Icons.video_library, label: 'Analysis', ), _NavItemData( icon: Icons.calendar_today_outlined, activeIcon: Icons.calendar_today, label: 'Calendar', ), _NavItemData( icon: Icons.chat_bubble_outline, activeIcon: Icons.chat_bubble, label: 'AI Chat', ), ]; @override Widget build(BuildContext context) { return Container( width: 200, decoration: const BoxDecoration( color: AppColors.surfaceContainer, border: Border(right: BorderSide(color: AppColors.border)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // --- Logo --- Padding( padding: const EdgeInsets.fromLTRB(16, 20, 16, 20), child: Row( children: [ Container( width: 34, height: 34, decoration: BoxDecoration( color: AppColors.accentMuted, borderRadius: BorderRadius.circular(8), ), child: const Icon( Icons.fitness_center, color: AppColors.accent, size: 18, ), ), const SizedBox(width: 10), Text( 'TrainHub', style: GoogleFonts.inter( fontSize: 15, fontWeight: FontWeight.w700, color: AppColors.textPrimary, letterSpacing: -0.3, ), ), ], ), ), const Padding( padding: EdgeInsets.symmetric(horizontal: 12), child: Divider(height: 1), ), const SizedBox(height: UIConstants.spacing8), // --- Nav items --- for (int i = 0; i < _items.length; i++) _NavItem( data: _items[i], isActive: activeIndex == i, onTap: () => onDestinationSelected(i), ), const Spacer(), // --- Footer --- Padding( padding: const EdgeInsets.fromLTRB(12, 8, 12, 12), child: Row( children: [ Text( 'v2.0.0', style: GoogleFonts.inter( fontSize: 11, color: AppColors.textMuted, ), ), const Spacer(), const _SettingsButton(), ], ), ), ], ), ); } } class _NavItem extends StatefulWidget { final _NavItemData data; final bool isActive; final VoidCallback onTap; const _NavItem({ required this.data, required this.isActive, required this.onTap, }); @override State<_NavItem> createState() => _NavItemState(); } class _NavItemState extends State<_NavItem> { bool _isHovered = false; @override Widget build(BuildContext context) { final active = widget.isActive; return MouseRegion( onEnter: (_) => setState(() => _isHovered = true), onExit: (_) => setState(() => _isHovered = false), child: GestureDetector( onTap: widget.onTap, child: AnimatedContainer( duration: UIConstants.animationDuration, height: 40, margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 1), decoration: BoxDecoration( color: active ? AppColors.zinc800 : _isHovered ? AppColors.zinc900 : Colors.transparent, borderRadius: BorderRadius.circular(6), ), child: Row( children: [ // Left accent indicator AnimatedContainer( duration: UIConstants.animationDuration, width: 3, height: active ? 20 : 0, margin: const EdgeInsets.only(left: 4, right: 8), decoration: BoxDecoration( color: AppColors.accent, borderRadius: BorderRadius.circular(2), ), ), if (!active) const SizedBox(width: 15), Icon( active ? widget.data.activeIcon : widget.data.icon, size: 17, color: active ? AppColors.textPrimary : AppColors.textMuted, ), const SizedBox(width: 9), Text( widget.data.label, style: GoogleFonts.inter( fontSize: 13, fontWeight: active ? FontWeight.w600 : FontWeight.w400, color: active ? AppColors.textPrimary : _isHovered ? AppColors.textSecondary : AppColors.textMuted, ), ), ], ), ), ), ); } } // --------------------------------------------------------------------------- // Settings icon button in sidebar footer // --------------------------------------------------------------------------- class _SettingsButton extends StatefulWidget { const _SettingsButton(); @override State<_SettingsButton> createState() => _SettingsButtonState(); } class _SettingsButtonState extends State<_SettingsButton> { bool _hovered = false; @override Widget build(BuildContext context) { return Tooltip( message: 'Settings', child: MouseRegion( onEnter: (_) => setState(() => _hovered = true), onExit: (_) => setState(() => _hovered = false), child: GestureDetector( onTap: () => context.router.push(const SettingsRoute()), child: AnimatedContainer( duration: UIConstants.animationDuration, width: 28, height: 28, decoration: BoxDecoration( color: _hovered ? AppColors.zinc800 : Colors.transparent, borderRadius: BorderRadius.circular(6), ), child: Icon( Icons.settings_outlined, size: 16, color: _hovered ? AppColors.textSecondary : AppColors.textMuted, ), ), ), ), ); } }