feat(exécution): refonte des chronos de séance et carte exercice actif (ticket #90)
Réorganise l'écran d'exécution autour d'une carte "Exercice actif" sous le compteur SÉRIE X/Y (nom, médias, temps de série, action "Démarrer l'exercice"). Le timer de série devient un état persistant dédié (plus un DateTime UI volatile), pause-aware, et "Démarrer l'exercice" lance en une action tous les chronos qui doivent démarrer en début de série (série, première étape temps, score chrono si stopwatch). Implémentation restée non commitée depuis sa réalisation ; QA a validé les vérifications statiques exécutables en sandbox (dart analyze, git diff --check). flutter analyze / flutter test restent à relancer dans un environnement avec cache Flutter SDK accessible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -14,7 +14,9 @@ enum ActiveWorkoutStatus { running, paused, savedExit, completed, abandoned }
|
||||
|
||||
enum SetResultStatus { completed, skipped }
|
||||
|
||||
enum ActiveScoreStopwatchStatus { running, stopped }
|
||||
enum ActiveSetTimerStatus { running, paused, stopped, skipped }
|
||||
|
||||
enum ActiveScoreStopwatchStatus { running, paused, stopped }
|
||||
|
||||
enum ShareResourceType { program, workoutTemplate }
|
||||
|
||||
@ -1031,7 +1033,7 @@ final class ActiveScoreStopwatchState {
|
||||
final DateTime? stoppedAt;
|
||||
|
||||
int elapsedMillisecondsAt(DateTime now) {
|
||||
if (status == ActiveScoreStopwatchStatus.stopped) {
|
||||
if (status != ActiveScoreStopwatchStatus.running) {
|
||||
return accumulatedMs;
|
||||
}
|
||||
return accumulatedMs + now.difference(startedAt).inMilliseconds;
|
||||
@ -1060,6 +1062,80 @@ final class ActiveScoreStopwatchState {
|
||||
}
|
||||
}
|
||||
|
||||
final class ActiveSetTimerState {
|
||||
ActiveSetTimerState({
|
||||
required this.metadata,
|
||||
required this.activeWorkoutSessionId,
|
||||
required this.programIndex,
|
||||
required this.exerciseIndex,
|
||||
required this.setIndex,
|
||||
required this.status,
|
||||
this.startedAt,
|
||||
required this.accumulatedMs,
|
||||
this.stoppedAt,
|
||||
this.skippedAt,
|
||||
}) {
|
||||
_requireNonNegative(programIndex, 'Program index');
|
||||
_requireNonNegative(exerciseIndex, 'Exercise index');
|
||||
_requireNonNegative(setIndex, 'Set index');
|
||||
_requireNonNegative(accumulatedMs, 'Accumulated milliseconds');
|
||||
if (status == ActiveSetTimerStatus.running && startedAt == null) {
|
||||
throw const DomainException(
|
||||
'Running set timer requires a start timestamp.',
|
||||
);
|
||||
}
|
||||
if (stoppedAt != null && skippedAt != null) {
|
||||
throw const DomainException('Set timer cannot be stopped and skipped.');
|
||||
}
|
||||
}
|
||||
|
||||
final EntityMetadata metadata;
|
||||
final String activeWorkoutSessionId;
|
||||
final int programIndex;
|
||||
final int exerciseIndex;
|
||||
final int setIndex;
|
||||
final ActiveSetTimerStatus status;
|
||||
final DateTime? startedAt;
|
||||
final int accumulatedMs;
|
||||
final DateTime? stoppedAt;
|
||||
final DateTime? skippedAt;
|
||||
|
||||
int elapsedMillisecondsAt(DateTime now) {
|
||||
if (status != ActiveSetTimerStatus.running || startedAt == null) {
|
||||
return accumulatedMs;
|
||||
}
|
||||
return accumulatedMs + now.difference(startedAt!).inMilliseconds;
|
||||
}
|
||||
|
||||
ActiveSetTimerState copyWith({
|
||||
EntityMetadata? metadata,
|
||||
ActiveSetTimerStatus? status,
|
||||
Object? startedAt = _unchanged,
|
||||
int? accumulatedMs,
|
||||
Object? stoppedAt = _unchanged,
|
||||
Object? skippedAt = _unchanged,
|
||||
}) {
|
||||
return ActiveSetTimerState(
|
||||
metadata: metadata ?? this.metadata,
|
||||
activeWorkoutSessionId: activeWorkoutSessionId,
|
||||
programIndex: programIndex,
|
||||
exerciseIndex: exerciseIndex,
|
||||
setIndex: setIndex,
|
||||
status: status ?? this.status,
|
||||
startedAt: startedAt == _unchanged
|
||||
? this.startedAt
|
||||
: startedAt as DateTime?,
|
||||
accumulatedMs: accumulatedMs ?? this.accumulatedMs,
|
||||
stoppedAt: stoppedAt == _unchanged
|
||||
? this.stoppedAt
|
||||
: stoppedAt as DateTime?,
|
||||
skippedAt: skippedAt == _unchanged
|
||||
? this.skippedAt
|
||||
: skippedAt as DateTime?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class ActiveRestState {
|
||||
const ActiveRestState({
|
||||
required this.metadata,
|
||||
@ -1072,6 +1148,8 @@ final class ActiveRestState {
|
||||
required this.startedAt,
|
||||
this.endedAt,
|
||||
this.skippedAt,
|
||||
this.pausedAt,
|
||||
this.accumulatedPausedMs = 0,
|
||||
});
|
||||
|
||||
final EntityMetadata metadata;
|
||||
@ -1084,12 +1162,28 @@ final class ActiveRestState {
|
||||
final DateTime startedAt;
|
||||
final DateTime? endedAt;
|
||||
final DateTime? skippedAt;
|
||||
final DateTime? pausedAt;
|
||||
final int accumulatedPausedMs;
|
||||
|
||||
int elapsedMillisecondsAt(DateTime now) {
|
||||
final effectiveNow = pausedAt ?? now;
|
||||
final elapsed =
|
||||
effectiveNow.difference(startedAt).inMilliseconds - accumulatedPausedMs;
|
||||
return elapsed < 0 ? 0 : elapsed;
|
||||
}
|
||||
|
||||
int remainingMillisecondsAt(DateTime now) {
|
||||
final remaining = adjustedRestSeconds * 1000 - elapsedMillisecondsAt(now);
|
||||
return remaining < 0 ? 0 : remaining;
|
||||
}
|
||||
|
||||
ActiveRestState copyWith({
|
||||
EntityMetadata? metadata,
|
||||
int? adjustedRestSeconds,
|
||||
Object? endedAt = _unchanged,
|
||||
Object? skippedAt = _unchanged,
|
||||
Object? pausedAt = _unchanged,
|
||||
int? accumulatedPausedMs,
|
||||
}) {
|
||||
return ActiveRestState(
|
||||
metadata: metadata ?? this.metadata,
|
||||
@ -1104,6 +1198,8 @@ final class ActiveRestState {
|
||||
skippedAt: skippedAt == _unchanged
|
||||
? this.skippedAt
|
||||
: skippedAt as DateTime?,
|
||||
pausedAt: pausedAt == _unchanged ? this.pausedAt : pausedAt as DateTime?,
|
||||
accumulatedPausedMs: accumulatedPausedMs ?? this.accumulatedPausedMs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user