import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:trainhub_flutter/core/constants/ui_constants.dart'; import 'package:trainhub_flutter/core/theme/app_colors.dart'; import 'package:trainhub_flutter/domain/entities/program_workout.dart'; import 'package:trainhub_flutter/presentation/common/widgets/app_empty_state.dart'; import 'package:trainhub_flutter/presentation/common/widgets/app_stat_card.dart'; import 'package:trainhub_flutter/presentation/home/home_controller.dart'; import 'package:trainhub_flutter/presentation/home/home_state.dart'; @RoutePage() class HomePage extends ConsumerWidget { const HomePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final asyncState = ref.watch(homeControllerProvider); return asyncState.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center( child: Padding( padding: const EdgeInsets.all(UIConstants.pagePadding), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.error_outline, size: 48, color: AppColors.destructive, ), const SizedBox(height: UIConstants.spacing16), Text( 'Something went wrong', style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), const SizedBox(height: UIConstants.spacing8), Text( '$e', style: GoogleFonts.inter( fontSize: 13, color: AppColors.textMuted, ), textAlign: TextAlign.center, ), const SizedBox(height: UIConstants.spacing24), FilledButton.icon( onPressed: () => ref.read(homeControllerProvider.notifier).refresh(), icon: const Icon(Icons.refresh, size: 18), label: const Text('Retry'), ), ], ), ), ), data: (data) { if (data.activeProgramName == null) { return AppEmptyState( icon: Icons.calendar_today_outlined, title: 'No active program', subtitle: 'Head to Calendar to create or select a training program to get started.', actionLabel: 'Go to Calendar', onAction: () { AutoTabsRouter.of(context).setActiveIndex(3); }, ); } return _HomeContent(data: data); }, ); } } class _HomeContent extends StatelessWidget { final HomeState data; const _HomeContent({required this.data}); @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.all(UIConstants.pagePadding), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // -- Welcome header -- _WelcomeHeader(programName: data.activeProgramName!), const SizedBox(height: UIConstants.spacing24), // -- Stat cards row -- _StatCardsRow( completed: data.completedWorkouts, total: data.totalWorkouts, ), const SizedBox(height: UIConstants.spacing24), // -- Next workout banner -- if (data.nextWorkoutName != null) ...[ _NextWorkoutBanner(workoutName: data.nextWorkoutName!), const SizedBox(height: UIConstants.spacing24), ], // -- Quick actions -- _QuickActionsRow(), const SizedBox(height: UIConstants.spacing32), // -- Recent activity -- _RecentActivitySection(activity: data.recentActivity), ], ), ); } } // --------------------------------------------------------------------------- // Welcome header // --------------------------------------------------------------------------- class _WelcomeHeader extends StatelessWidget { final String programName; const _WelcomeHeader({required this.programName}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Welcome back', style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.textMuted, ), ), const SizedBox(height: UIConstants.spacing4), Row( children: [ Expanded( child: Text( programName, style: GoogleFonts.inter( fontSize: 28, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), ), Container( padding: const EdgeInsets.symmetric( horizontal: UIConstants.spacing12, vertical: 6, ), decoration: BoxDecoration( color: AppColors.accentMuted, borderRadius: UIConstants.smallCardBorderRadius, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.fitness_center, size: 14, color: AppColors.accent, ), const SizedBox(width: 6), Text( 'Active Program', style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w600, color: AppColors.accent, ), ), ], ), ), ], ), ], ); } } // --------------------------------------------------------------------------- // Stat cards row // --------------------------------------------------------------------------- class _StatCardsRow extends StatelessWidget { final int completed; final int total; const _StatCardsRow({required this.completed, required this.total}); @override Widget build(BuildContext context) { final progress = total == 0 ? 0 : (completed / total * 100).round(); return Row( children: [ Expanded( child: AppStatCard( title: 'Completed', value: '$completed', icon: Icons.check_circle_outline, accentColor: AppColors.success, ), ), const SizedBox(width: UIConstants.spacing16), Expanded( child: AppStatCard( title: 'Total Workouts', value: '$total', icon: Icons.list_alt, accentColor: AppColors.info, ), ), const SizedBox(width: UIConstants.spacing16), Expanded( child: AppStatCard( title: 'Progress', value: '$progress%', icon: Icons.trending_up, accentColor: AppColors.purple, ), ), ], ); } } // --------------------------------------------------------------------------- // Next workout banner // --------------------------------------------------------------------------- class _NextWorkoutBanner extends StatelessWidget { final String workoutName; const _NextWorkoutBanner({required this.workoutName}); @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(UIConstants.cardPadding), decoration: BoxDecoration( color: AppColors.surfaceContainer, borderRadius: UIConstants.cardBorderRadius, border: Border.all(color: AppColors.border), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: AppColors.accentMuted, borderRadius: UIConstants.smallCardBorderRadius, ), child: const Icon( Icons.play_arrow_rounded, color: AppColors.accent, size: 22, ), ), const SizedBox(width: UIConstants.spacing12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Up Next', style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w500, color: AppColors.textMuted, ), ), const SizedBox(height: 2), Text( workoutName, style: GoogleFonts.inter( fontSize: 15, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), ], ), ), Icon( Icons.chevron_right, color: AppColors.textMuted, size: 20, ), ], ), ); } } // --------------------------------------------------------------------------- // Quick actions row // --------------------------------------------------------------------------- class _QuickActionsRow extends StatelessWidget { @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Quick Actions', style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), const SizedBox(height: UIConstants.spacing12), Row( children: [ _QuickActionButton( icon: Icons.play_arrow_rounded, label: 'New Workout', color: AppColors.accent, onTap: () { AutoTabsRouter.of(context).setActiveIndex(1); }, ), const SizedBox(width: UIConstants.spacing12), _QuickActionButton( icon: Icons.description_outlined, label: 'View Plans', color: AppColors.info, onTap: () { AutoTabsRouter.of(context).setActiveIndex(1); }, ), const SizedBox(width: UIConstants.spacing12), _QuickActionButton( icon: Icons.chat_bubble_outline, label: 'AI Chat', color: AppColors.purple, onTap: () { AutoTabsRouter.of(context).setActiveIndex(4); }, ), ], ), ], ); } } class _QuickActionButton extends StatefulWidget { final IconData icon; final String label; final Color color; final VoidCallback onTap; const _QuickActionButton({ required this.icon, required this.label, required this.color, required this.onTap, }); @override State<_QuickActionButton> createState() => _QuickActionButtonState(); } class _QuickActionButtonState extends State<_QuickActionButton> { bool _isHovered = false; @override Widget build(BuildContext context) { return MouseRegion( onEnter: (_) => setState(() => _isHovered = true), onExit: (_) => setState(() => _isHovered = false), child: AnimatedContainer( duration: UIConstants.animationDuration, decoration: BoxDecoration( color: _isHovered ? widget.color.withValues(alpha: 0.08) : Colors.transparent, borderRadius: UIConstants.smallCardBorderRadius, border: Border.all( color: _isHovered ? widget.color.withValues(alpha: 0.4) : AppColors.border, ), ), child: Material( color: Colors.transparent, child: InkWell( onTap: widget.onTap, borderRadius: UIConstants.smallCardBorderRadius, child: Padding( padding: const EdgeInsets.symmetric( horizontal: UIConstants.spacing24, vertical: UIConstants.spacing12, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( widget.icon, size: 18, color: widget.color, ), const SizedBox(width: UIConstants.spacing8), Text( widget.label, style: GoogleFonts.inter( fontSize: 13, fontWeight: FontWeight.w500, color: _isHovered ? widget.color : AppColors.textSecondary, ), ), ], ), ), ), ), ), ); } } // --------------------------------------------------------------------------- // Recent activity section // --------------------------------------------------------------------------- class _RecentActivitySection extends StatelessWidget { final List activity; const _RecentActivitySection({required this.activity}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( 'Recent Activity', style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), ), if (activity.isNotEmpty) Text( '${activity.length} workout${activity.length == 1 ? '' : 's'}', style: GoogleFonts.inter( fontSize: 12, color: AppColors.textMuted, ), ), ], ), const SizedBox(height: UIConstants.spacing12), if (activity.isEmpty) _EmptyActivity() else _ActivityList(activity: activity), ], ); } } class _EmptyActivity extends StatelessWidget { @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.symmetric( vertical: 40, horizontal: UIConstants.spacing24, ), decoration: BoxDecoration( color: AppColors.surfaceContainer, borderRadius: UIConstants.cardBorderRadius, border: Border.all(color: AppColors.border), ), child: Column( children: [ Icon( Icons.history, size: 32, color: AppColors.textMuted, ), const SizedBox(height: UIConstants.spacing12), Text( 'No completed workouts yet', style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.textSecondary, ), ), const SizedBox(height: UIConstants.spacing4), Text( 'Your recent workout history will appear here.', style: GoogleFonts.inter( fontSize: 13, color: AppColors.textMuted, ), ), ], ), ); } } class _ActivityList extends StatelessWidget { final List activity; const _ActivityList({required this.activity}); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: AppColors.surfaceContainer, borderRadius: UIConstants.cardBorderRadius, border: Border.all(color: AppColors.border), ), child: ClipRRect( borderRadius: UIConstants.cardBorderRadius, child: Column( children: [ for (int i = 0; i < activity.length; i++) ...[ if (i > 0) const Divider( height: 1, thickness: 1, color: AppColors.border, ), _ActivityItem(workout: activity[i]), ], ], ), ), ); } } class _ActivityItem extends StatefulWidget { final ProgramWorkoutEntity workout; const _ActivityItem({required this.workout}); @override State<_ActivityItem> createState() => _ActivityItemState(); } class _ActivityItemState extends State<_ActivityItem> { bool _isHovered = false; Color get _typeColor { switch (widget.workout.type.toLowerCase()) { case 'strength': return AppColors.accent; case 'cardio': return AppColors.info; case 'flexibility': case 'mobility': return AppColors.purple; case 'rest': return AppColors.textMuted; default: return AppColors.success; } } IconData get _typeIcon { switch (widget.workout.type.toLowerCase()) { case 'strength': return Icons.fitness_center; case 'cardio': return Icons.directions_run; case 'flexibility': case 'mobility': return Icons.self_improvement; case 'rest': return Icons.bedtime_outlined; default: return Icons.check_circle; } } @override Widget build(BuildContext context) { return MouseRegion( onEnter: (_) => setState(() => _isHovered = true), onExit: (_) => setState(() => _isHovered = false), child: AnimatedContainer( duration: UIConstants.animationDuration, color: _isHovered ? AppColors.surfaceContainerHigh.withValues(alpha: 0.5) : Colors.transparent, padding: const EdgeInsets.symmetric( horizontal: UIConstants.cardPadding, vertical: UIConstants.spacing12, ), child: Row( children: [ // Leading icon with color coding Container( width: 36, height: 36, decoration: BoxDecoration( color: widget.workout.completed ? _typeColor.withValues(alpha: 0.15) : AppColors.zinc800, borderRadius: UIConstants.smallCardBorderRadius, ), child: Icon( widget.workout.completed ? _typeIcon : Icons.circle_outlined, size: 18, color: widget.workout.completed ? _typeColor : AppColors.textMuted, ), ), const SizedBox(width: UIConstants.spacing12), // Workout info Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.workout.name ?? 'Workout', style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.textPrimary, ), ), const SizedBox(height: 2), Text( 'Week ${widget.workout.weekId} ยท Day ${widget.workout.day}', style: GoogleFonts.inter( fontSize: 12, color: AppColors.textMuted, ), ), ], ), ), // Type badge Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _typeColor.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(6), ), child: Text( widget.workout.type, style: GoogleFonts.inter( fontSize: 11, fontWeight: FontWeight.w500, color: _typeColor, ), ), ), const SizedBox(width: UIConstants.spacing12), // Status indicator if (widget.workout.completed) const Icon( Icons.check_circle, size: 18, color: AppColors.success, ) else const Icon( Icons.radio_button_unchecked, size: 18, color: AppColors.textMuted, ), ], ), ), ); } }