import 'dart:async'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:trainhub_flutter/domain/entities/training_plan.dart'; import 'package:trainhub_flutter/domain/entities/workout_activity.dart'; import 'package:trainhub_flutter/presentation/workout_session/workout_session_state.dart'; import 'package:trainhub_flutter/injection.dart'; import 'package:trainhub_flutter/domain/repositories/training_plan_repository.dart'; part 'workout_session_controller.g.dart'; @riverpod class WorkoutSessionController extends _$WorkoutSessionController { Timer? _timer; @override Future build(String planId) async { final planRepo = getIt(); final plan = await planRepo.getById(planId); final activities = _buildSequence(plan); ref.onDispose(() => _timer?.cancel()); if (activities.isEmpty) { return WorkoutSessionState(activities: activities); } final first = activities.first; final initialState = WorkoutSessionState( activities: activities, timeRemaining: first.duration, ); if (first.isTimeBased) { Future.microtask(startTimer); } return initialState; } List _buildSequence(TrainingPlanEntity plan) { final List seq = []; for (final section in plan.sections) { for (final ex in section.exercises) { for (int s = 1; s <= ex.sets; s++) { seq.add( WorkoutActivityEntity( id: '${ex.instanceId}-s$s-work', name: ex.name, type: 'work', duration: ex.isTime ? ex.value : 0, originalExercise: ex, sectionName: section.name, setIndex: s, totalSets: ex.sets, ), ); final bool isLastOfWorkout = s == ex.sets && section.exercises.last == ex && plan.sections.last == section; if (ex.rest > 0 && !isLastOfWorkout) { seq.add( WorkoutActivityEntity( id: '${ex.instanceId}-s$s-rest', name: 'Rest', type: 'rest', duration: ex.rest, sectionName: section.name, ), ); } } } } return seq; } void startTimer() { if (_timer != null && _timer!.isActive) return; _timer = Timer.periodic(const Duration(seconds: 1), _tick); final currentState = state.value; if (currentState != null) { state = AsyncValue.data(currentState.copyWith(isRunning: true)); } } void pauseTimer() { _timer?.cancel(); _timer = null; final currentState = state.value; if (currentState != null) { state = AsyncValue.data(currentState.copyWith(isRunning: false)); } } void rewindSeconds(int amount) { final currentState = state.value; if (currentState == null) return; final maxDuration = currentState.currentActivity?.duration ?? 0; final newRemaining = (currentState.timeRemaining + amount).clamp( 0, maxDuration, ); state = AsyncValue.data( currentState.copyWith(timeRemaining: newRemaining), ); } void _tick(Timer timer) { if (state.value?.isFinished ?? true) return; var currentState = state.value!; var newState = currentState.copyWith( totalTimeElapsed: currentState.totalTimeElapsed + 1, ); final activity = newState.currentActivity; if (activity != null && activity.duration > 0 && newState.isRunning) { if (newState.timeRemaining > 0) { newState = newState.copyWith(timeRemaining: newState.timeRemaining - 1); } else { state = AsyncValue.data(newState); _goNext(newState); return; } } state = AsyncValue.data(newState); } void next() { final currentState = state.value; if (currentState != null) _goNext(currentState); } void _goNext(WorkoutSessionState currentState) { if (currentState.currentIndex < currentState.activities.length - 1) { final nextIndex = currentState.currentIndex + 1; final nextActivity = currentState.activities[nextIndex]; final newState = currentState.copyWith( currentIndex: nextIndex, timeRemaining: nextActivity.duration, ); state = AsyncValue.data(newState); if (nextActivity.isTimeBased) { startTimer(); } else { pauseTimer(); } } else { _finish(); } } void previous() { final currentState = state.value; if (currentState != null && currentState.currentIndex > 0) { final prevIndex = currentState.currentIndex - 1; final prevActivity = currentState.activities[prevIndex]; state = AsyncValue.data( currentState.copyWith( currentIndex: prevIndex, timeRemaining: prevActivity.duration, ), ); pauseTimer(); } } void jumpTo(int index) { final currentState = state.value; if (currentState == null) return; if (index < 0 || index >= currentState.activities.length) return; final activity = currentState.activities[index]; state = AsyncValue.data( currentState.copyWith( currentIndex: index, timeRemaining: activity.duration, ), ); if (activity.isTimeBased) { startTimer(); } else { pauseTimer(); } } void _finish() { pauseTimer(); final currentState = state.value; if (currentState != null) { state = AsyncValue.data(currentState.copyWith(isFinished: true)); } } }