import 'dart:math'; import 'dart:ui'; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:trainhub_flutter/core/constants/ui_constants.dart'; import 'package:trainhub_flutter/core/theme/app_colors.dart'; import 'package:trainhub_flutter/presentation/workout_session/workout_session_controller.dart'; import 'package:trainhub_flutter/presentation/workout_session/workout_session_state.dart'; import 'package:trainhub_flutter/presentation/workout_session/widgets/activity_card.dart'; import 'package:trainhub_flutter/presentation/workout_session/widgets/session_controls.dart'; import 'package:trainhub_flutter/presentation/workout_session/widgets/session_progress_bar.dart'; @RoutePage() class WorkoutSessionPage extends ConsumerWidget { final String planId; const WorkoutSessionPage({ super.key, @PathParam('planId') required this.planId, }); @override Widget build(BuildContext context, WidgetRef ref) { final asyncState = ref.watch(workoutSessionControllerProvider(planId)); return Scaffold( backgroundColor: AppColors.zinc950, body: asyncState.when( loading: () => const Center( child: CircularProgressIndicator( color: AppColors.accent, strokeWidth: 2, ), ), error: (err, stack) => Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.error_outline_rounded, color: AppColors.destructive, size: 48, ), const SizedBox(height: UIConstants.spacing16), Text( 'Failed to load workout', style: TextStyle( color: AppColors.textPrimary, fontSize: 18, fontWeight: FontWeight.w600, ), ), const SizedBox(height: UIConstants.spacing8), Text( '$err', style: TextStyle( color: AppColors.textMuted, fontSize: 14, ), textAlign: TextAlign.center, ), ], ), ), data: (state) { final controller = ref.read( workoutSessionControllerProvider(planId).notifier, ); if (state.isFinished) { return _CompletionScreen( totalTimeElapsed: state.totalTimeElapsed, ); } final isRest = state.currentActivity?.isRest ?? false; return _ActiveSessionView( state: state, isRest: isRest, controller: controller, ); }, ), ); } } // --------------------------------------------------------------------------- // Active session view (gradient background + timer + controls) // --------------------------------------------------------------------------- class _ActiveSessionView extends StatelessWidget { final WorkoutSessionState state; final bool isRest; final WorkoutSessionController controller; const _ActiveSessionView({ required this.state, required this.isRest, required this.controller, }); @override Widget build(BuildContext context) { // Compute the time progress for the circular ring. final activity = state.currentActivity; final double timeProgress; if (activity != null && activity.duration > 0) { timeProgress = 1.0 - (state.timeRemaining / activity.duration); } else { timeProgress = 0.0; } final accentTint = isRest ? AppColors.info.withValues(alpha: 0.06) : AppColors.accent.withValues(alpha: 0.06); final ringColor = isRest ? AppColors.info : AppColors.accent; return Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ AppColors.zinc950, accentTint, AppColors.zinc950, ], stops: const [0.0, 0.5, 1.0], ), ), child: Column( children: [ // -- Top progress bar -- SessionProgressBar(progress: state.progress), // -- Elapsed time badge -- Padding( padding: const EdgeInsets.only( top: UIConstants.spacing16, right: UIConstants.spacing24, ), child: Align( alignment: Alignment.centerRight, child: Container( padding: const EdgeInsets.symmetric( horizontal: UIConstants.spacing12, vertical: UIConstants.spacing4, ), decoration: BoxDecoration( color: AppColors.zinc800.withValues(alpha: 0.6), borderRadius: BorderRadius.circular(20), border: Border.all( color: AppColors.border.withValues(alpha: 0.5), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.timer_outlined, size: 14, color: AppColors.textMuted, ), const SizedBox(width: 4), Text( _formatDuration(state.totalTimeElapsed), style: const TextStyle( color: AppColors.textSecondary, fontSize: 13, fontWeight: FontWeight.w500, fontFeatures: [FontFeature.tabularFigures()], ), ), ], ), ), ), ), // -- Central content -- Expanded( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.symmetric( horizontal: UIConstants.spacing24, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Activity info card if (activity != null) ActivityCard(activity: activity), const SizedBox(height: UIConstants.spacing32), // Circular progress ring + timer _CircularTimerDisplay( timeRemaining: state.timeRemaining, progress: timeProgress, ringColor: ringColor, isRunning: state.isRunning, isTimeBased: activity?.isTimeBased ?? false, ), const SizedBox(height: UIConstants.spacing24), // "Up next" pill if (state.nextActivity != null) _UpNextPill( nextActivityName: state.nextActivity!.name, isNextRest: state.nextActivity!.isRest, ), ], ), ), ), ), // -- Bottom controls -- SessionControls( isRunning: state.isRunning, isFinished: state.isFinished, onPause: controller.pauseTimer, onPlay: controller.startTimer, onNext: controller.next, onPrevious: controller.previous, ), const SizedBox(height: UIConstants.spacing24), ], ), ); } String _formatDuration(int seconds) { final m = seconds ~/ 60; final s = seconds % 60; return '${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; } } // --------------------------------------------------------------------------- // Circular timer with arc progress ring // --------------------------------------------------------------------------- class _CircularTimerDisplay extends StatelessWidget { final int timeRemaining; final double progress; final Color ringColor; final bool isRunning; final bool isTimeBased; const _CircularTimerDisplay({ required this.timeRemaining, required this.progress, required this.ringColor, required this.isRunning, required this.isTimeBased, }); @override Widget build(BuildContext context) { const double size = 220; const double strokeWidth = 6.0; return SizedBox( width: size, height: size, child: Stack( alignment: Alignment.center, children: [ // Background track SizedBox( width: size, height: size, child: CircularProgressIndicator( value: 1.0, strokeWidth: strokeWidth, color: AppColors.zinc800.withValues(alpha: 0.5), strokeCap: StrokeCap.round, ), ), // Progress arc if (isTimeBased) SizedBox( width: size, height: size, child: CircularProgressIndicator( value: progress.clamp(0.0, 1.0), strokeWidth: strokeWidth, color: ringColor, backgroundColor: Colors.transparent, strokeCap: StrokeCap.round, ), ), // Glow behind the timer text Container( width: size * 0.7, height: size * 0.7, decoration: BoxDecoration( shape: BoxShape.circle, boxShadow: [ BoxShadow( color: ringColor.withValues(alpha: 0.08), blurRadius: 40, spreadRadius: 10, ), ], ), ), // Timer text Column( mainAxisSize: MainAxisSize.min, children: [ Text( _formatTime(timeRemaining), style: TextStyle( color: AppColors.textPrimary, fontSize: 52, fontWeight: FontWeight.w300, letterSpacing: 2, fontFeatures: const [FontFeature.tabularFigures()], fontFamily: 'monospace', ), ), if (!isTimeBased) Padding( padding: const EdgeInsets.only(top: 4), child: Text( 'UNTIMED', style: TextStyle( color: AppColors.textMuted, fontSize: 11, fontWeight: FontWeight.w600, letterSpacing: 2, ), ), ), if (isTimeBased && !isRunning) Padding( padding: const EdgeInsets.only(top: 4), child: Text( 'PAUSED', style: TextStyle( color: AppColors.textMuted, fontSize: 11, fontWeight: FontWeight.w600, letterSpacing: 2, ), ), ), ], ), ], ), ); } String _formatTime(int seconds) { final m = seconds ~/ 60; final s = seconds % 60; return '${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; } } // --------------------------------------------------------------------------- // "Up next" pill // --------------------------------------------------------------------------- class _UpNextPill extends StatelessWidget { final String nextActivityName; final bool isNextRest; const _UpNextPill({ required this.nextActivityName, required this.isNextRest, }); @override Widget build(BuildContext context) { final pillColor = isNextRest ? AppColors.info.withValues(alpha: 0.12) : AppColors.accent.withValues(alpha: 0.12); final pillBorderColor = isNextRest ? AppColors.info.withValues(alpha: 0.25) : AppColors.accent.withValues(alpha: 0.25); final labelColor = isNextRest ? AppColors.info : AppColors.accent; return Container( padding: const EdgeInsets.symmetric( horizontal: UIConstants.spacing16, vertical: UIConstants.spacing8, ), decoration: BoxDecoration( color: pillColor, borderRadius: BorderRadius.circular(24), border: Border.all(color: pillBorderColor), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( 'UP NEXT', style: TextStyle( color: labelColor, fontSize: 10, fontWeight: FontWeight.w700, letterSpacing: 1.5, ), ), const SizedBox(width: UIConstants.spacing8), Container( width: 3, height: 3, decoration: BoxDecoration( color: AppColors.textMuted, shape: BoxShape.circle, ), ), const SizedBox(width: UIConstants.spacing8), Flexible( child: Text( nextActivityName, style: const TextStyle( color: AppColors.textSecondary, fontSize: 13, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ), ], ), ); } } // --------------------------------------------------------------------------- // Completion screen // --------------------------------------------------------------------------- class _CompletionScreen extends StatelessWidget { final int totalTimeElapsed; const _CompletionScreen({required this.totalTimeElapsed}); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ AppColors.zinc950, AppColors.success.withValues(alpha: 0.06), AppColors.zinc950, ], stops: const [0.0, 0.45, 1.0], ), ), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ // Checkmark circle Container( width: 96, height: 96, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColors.success.withValues(alpha: 0.12), border: Border.all( color: AppColors.success.withValues(alpha: 0.3), width: 2, ), boxShadow: [ BoxShadow( color: AppColors.success.withValues(alpha: 0.15), blurRadius: 40, spreadRadius: 8, ), ], ), child: const Icon( Icons.check_rounded, color: AppColors.success, size: 48, ), ), const SizedBox(height: UIConstants.spacing24), const Text( 'Workout Complete', style: TextStyle( color: AppColors.textPrimary, fontSize: 28, fontWeight: FontWeight.w600, letterSpacing: -0.5, ), ), const SizedBox(height: UIConstants.spacing8), Text( 'Great job! You crushed it.', style: TextStyle( color: AppColors.textSecondary, fontSize: 15, ), ), const SizedBox(height: UIConstants.spacing32), // Total time card Container( padding: const EdgeInsets.symmetric( horizontal: UIConstants.spacing24, vertical: UIConstants.spacing16, ), decoration: BoxDecoration( color: AppColors.surfaceContainer.withValues(alpha: 0.6), borderRadius: BorderRadius.circular(UIConstants.borderRadius), border: Border.all( color: AppColors.border.withValues(alpha: 0.5), ), ), child: Column( children: [ Text( 'TOTAL TIME', style: TextStyle( color: AppColors.textMuted, fontSize: 11, fontWeight: FontWeight.w600, letterSpacing: 1.5, ), ), const SizedBox(height: UIConstants.spacing4), Text( _formatDuration(totalTimeElapsed), style: TextStyle( color: AppColors.textPrimary, fontSize: 36, fontWeight: FontWeight.w300, fontFeatures: const [FontFeature.tabularFigures()], fontFamily: 'monospace', ), ), ], ), ), const SizedBox(height: UIConstants.spacing32), // Finish button SizedBox( width: 200, height: 48, child: FilledButton( onPressed: () => context.router.maybePop(), style: FilledButton.styleFrom( backgroundColor: AppColors.success, foregroundColor: AppColors.zinc950, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), textStyle: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, ), ), child: const Text('Finish'), ), ), ], ), ), ); } String _formatDuration(int seconds) { final h = seconds ~/ 3600; final m = (seconds % 3600) ~/ 60; final s = seconds % 60; if (h > 0) { return '${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; } return '${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; } }