202 lines
5.5 KiB
Dart
202 lines
5.5 KiB
Dart
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<WorkoutSessionState> build(String planId) async {
|
|
final planRepo = getIt<TrainingPlanRepository>();
|
|
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<WorkoutActivityEntity> _buildSequence(TrainingPlanEntity plan) {
|
|
final List<WorkoutActivityEntity> 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));
|
|
}
|
|
}
|
|
}
|