fix(execution): reprise en temps réel de la séance en cours (ticket #14)
Corrige la reprise de séance sur workout_execution_screen.dart suite aux retours de QA. flutter analyze propre, 26/26 tests verts, build APK debug validé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -36,6 +36,8 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
Timer? _ticker;
|
||||
Timer? _restTicker;
|
||||
WorkoutHistory? _completedHistory;
|
||||
String? _activeRestStateId;
|
||||
late DateTime _seriesStartedAt;
|
||||
var _reps = 0;
|
||||
final _scoreController = TextEditingController();
|
||||
var _remainingRestSeconds = 0;
|
||||
@ -56,9 +58,11 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
_mode = _session.status == ActiveWorkoutStatus.running
|
||||
? WorkoutExecutionMode.active
|
||||
: WorkoutExecutionMode.paused;
|
||||
_seriesStartedAt = DateTime.now().toUtc();
|
||||
_ticker = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
unawaited(_restoreActiveRest());
|
||||
}
|
||||
|
||||
@override
|
||||
@ -158,20 +162,14 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => setState(() {
|
||||
_remainingRestSeconds = (_remainingRestSeconds - 15)
|
||||
.clamp(0, 9999)
|
||||
.toInt();
|
||||
}),
|
||||
onPressed: () => _adjustRest(-15),
|
||||
child: const Text('-15 s'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => setState(() {
|
||||
_remainingRestSeconds += 15;
|
||||
}),
|
||||
onPressed: () => _adjustRest(15),
|
||||
child: const Text('+15 s'),
|
||||
),
|
||||
),
|
||||
@ -267,6 +265,8 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
Future<void> _resume() async {
|
||||
_session = await widget.activeUseCases.resume(_session.metadata.id);
|
||||
if (!mounted) return;
|
||||
final restoredRest = await _restoreActiveRest();
|
||||
if (!mounted || restoredRest) return;
|
||||
setState(() => _mode = WorkoutExecutionMode.active);
|
||||
}
|
||||
|
||||
@ -302,6 +302,10 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
|
||||
Future<void> _finishSet({required bool skipped}) async {
|
||||
final score = double.tryParse(_scoreController.text.trim());
|
||||
final actualTimeMs = DateTime.now()
|
||||
.toUtc()
|
||||
.difference(_seriesStartedAt)
|
||||
.inMilliseconds;
|
||||
await widget.activeUseCases.recordCurrentSetResult(
|
||||
sessionId: _session.metadata.id,
|
||||
programSnapshotId: _plan.programAt(_position).id,
|
||||
@ -312,7 +316,9 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
actualReps: skipped ? null : (_exercise.repsEnabled ? _reps : null),
|
||||
actualScore: skipped ? null : (_exercise.scoreEnabled ? score : null),
|
||||
scoreUnitSnapshot: _exercise.scoreUnit,
|
||||
actualTimeMs: skipped ? null : (_exercise.timeEnabled ? 0 : null),
|
||||
actualTimeMs: skipped
|
||||
? null
|
||||
: (_exercise.timeEnabled ? actualTimeMs : null),
|
||||
);
|
||||
|
||||
final next = _plan.nextPosition(_position);
|
||||
@ -322,7 +328,7 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
}
|
||||
final shouldRest = _plan.shouldShowRestAfter(_position);
|
||||
if (shouldRest) {
|
||||
await widget.activeUseCases.startRestAfterSet(
|
||||
final rest = await widget.activeUseCases.startRestAfterSet(
|
||||
sessionId: _session.metadata.id,
|
||||
afterProgramIndex: _position.programIndex,
|
||||
afterExerciseIndex: _position.exerciseIndex,
|
||||
@ -330,6 +336,7 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
plannedRestSeconds: _exercise.restSeconds,
|
||||
);
|
||||
if (!mounted) return;
|
||||
_activeRestStateId = rest.metadata.id;
|
||||
_remainingRestSeconds = _exercise.restSeconds;
|
||||
_startRestTicker(next);
|
||||
setState(() => _mode = WorkoutExecutionMode.rest);
|
||||
@ -344,16 +351,73 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
if (!mounted) return;
|
||||
if (_remainingRestSeconds <= 1) {
|
||||
timer.cancel();
|
||||
unawaited(_moveTo(next));
|
||||
unawaited(_finishRestAndMove(next));
|
||||
} else {
|
||||
setState(() => _remainingRestSeconds -= 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> _restoreActiveRest() async {
|
||||
final rest = await widget.activeUseCases.findActiveRest(
|
||||
sessionId: _session.metadata.id,
|
||||
);
|
||||
if (!mounted || rest == null) {
|
||||
return false;
|
||||
}
|
||||
final after = ExecutionPosition(
|
||||
programIndex: rest.afterProgramIndex,
|
||||
exerciseIndex: rest.afterExerciseIndex,
|
||||
setIndex: rest.afterSetIndex,
|
||||
);
|
||||
final next = _plan.nextPosition(after);
|
||||
final remaining = _remainingSecondsFor(rest);
|
||||
_activeRestStateId = rest.metadata.id;
|
||||
if (next == null || remaining <= 0) {
|
||||
await _finishRestAndMove(next);
|
||||
return true;
|
||||
}
|
||||
_remainingRestSeconds = remaining;
|
||||
_startRestTicker(next);
|
||||
if (!mounted) return true;
|
||||
setState(() => _mode = WorkoutExecutionMode.rest);
|
||||
return true;
|
||||
}
|
||||
|
||||
int _remainingSecondsFor(ActiveRestState rest) {
|
||||
final elapsedSeconds = DateTime.now()
|
||||
.toUtc()
|
||||
.difference(rest.startedAt.toUtc())
|
||||
.inSeconds;
|
||||
return (rest.adjustedRestSeconds - elapsedSeconds).clamp(0, 9999).toInt();
|
||||
}
|
||||
|
||||
Future<void> _adjustRest(int deltaSeconds) async {
|
||||
final restStateId = _activeRestStateId;
|
||||
setState(() {
|
||||
_remainingRestSeconds = (_remainingRestSeconds + deltaSeconds)
|
||||
.clamp(0, 9999)
|
||||
.toInt();
|
||||
});
|
||||
if (restStateId == null) return;
|
||||
await widget.activeUseCases.adjustRestSeconds(
|
||||
restStateId: restStateId,
|
||||
deltaSeconds: deltaSeconds,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _skipRest() async {
|
||||
_restTicker?.cancel();
|
||||
final next = _plan.nextPosition(_position);
|
||||
await _finishRestAndMove(next);
|
||||
}
|
||||
|
||||
Future<void> _finishRestAndMove(ExecutionPosition? next) async {
|
||||
final restStateId = _activeRestStateId;
|
||||
_activeRestStateId = null;
|
||||
if (restStateId != null) {
|
||||
await widget.activeUseCases.skipRest(restStateId: restStateId);
|
||||
}
|
||||
if (next == null) {
|
||||
await _complete();
|
||||
return;
|
||||
@ -364,6 +428,8 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
Future<void> _moveTo(ExecutionPosition position) async {
|
||||
_scoreController.clear();
|
||||
_reps = 0;
|
||||
_activeRestStateId = null;
|
||||
_seriesStartedAt = DateTime.now().toUtc();
|
||||
_session = await widget.activeUseCases.updateProgress(
|
||||
sessionId: _session.metadata.id,
|
||||
programIndex: position.programIndex,
|
||||
|
||||
Reference in New Issue
Block a user