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:
@ -951,6 +951,195 @@ void main() {
|
||||
expect(active?.metadata.id, 'latest-active');
|
||||
});
|
||||
|
||||
test(
|
||||
'startSetExecution starts set timer score stopwatch and first timed step',
|
||||
() async {
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||
final session = _sessionWithSnapshot(
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
timeEnabled: true,
|
||||
repsEnabled: false,
|
||||
scoreEnabled: true,
|
||||
scoreInputMode: ScoreInputMode.stopwatch,
|
||||
exerciseSteps: [_step(type: ExerciseStepType.time)],
|
||||
);
|
||||
final repository = _FakeActiveSessionRepository()..session = session;
|
||||
final useCase = _activeUseCase(repository, clock);
|
||||
|
||||
final started = await useCase.startSetExecution(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: 0,
|
||||
exerciseIndex: 0,
|
||||
setIndex: 0,
|
||||
);
|
||||
|
||||
expect(started.setTimer?.status, ActiveSetTimerStatus.running);
|
||||
expect(
|
||||
started.scoreStopwatch?.status,
|
||||
ActiveScoreStopwatchStatus.running,
|
||||
);
|
||||
expect(
|
||||
started.stepProgress?.status,
|
||||
ActiveExerciseStepProgressStatus.runningTimer,
|
||||
);
|
||||
expect(repository.setTimerStates.length, 1);
|
||||
expect(repository.scoreStopwatchStates.length, 1);
|
||||
expect(repository.stepProgressStates.length, 1);
|
||||
},
|
||||
);
|
||||
|
||||
test('startSetExecution is idempotent for already running timers', () async {
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||
final session = _sessionWithSnapshot(
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
timeEnabled: true,
|
||||
repsEnabled: false,
|
||||
scoreEnabled: true,
|
||||
scoreInputMode: ScoreInputMode.stopwatch,
|
||||
exerciseSteps: [_step(type: ExerciseStepType.time)],
|
||||
);
|
||||
final repository = _FakeActiveSessionRepository()..session = session;
|
||||
final useCase = _activeUseCase(repository, clock);
|
||||
|
||||
final first = await useCase.startSetExecution(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: 0,
|
||||
exerciseIndex: 0,
|
||||
setIndex: 0,
|
||||
);
|
||||
clock.value = clock.value.add(const Duration(seconds: 3));
|
||||
final second = await useCase.startSetExecution(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: 0,
|
||||
exerciseIndex: 0,
|
||||
setIndex: 0,
|
||||
);
|
||||
|
||||
expect(second.setTimer?.metadata.id, first.setTimer?.metadata.id);
|
||||
expect(second.setTimer?.startedAt, first.setTimer?.startedAt);
|
||||
expect(repository.setTimerStates.length, 1);
|
||||
expect(repository.scoreStopwatchStates.length, 1);
|
||||
expect(repository.stepProgressStates.length, 1);
|
||||
});
|
||||
|
||||
test(
|
||||
'startSetExecution does not start a next timed step left stopped',
|
||||
() async {
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||
final session = _sessionWithSnapshot(
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
timeEnabled: true,
|
||||
repsEnabled: false,
|
||||
exerciseSteps: _twoTimedSteps(),
|
||||
);
|
||||
final repository = _FakeActiveSessionRepository()
|
||||
..session = session
|
||||
..stepProgressStates['step-state'] = ActiveExerciseStepProgressState(
|
||||
metadata: _metadata('step-state'),
|
||||
activeWorkoutSessionId: session.metadata.id,
|
||||
programIndex: 0,
|
||||
exerciseIndex: 0,
|
||||
setIndex: 0,
|
||||
currentPassageIndex: 0,
|
||||
currentStepIndex: 1,
|
||||
currentStepSnapshotId: 'step-2',
|
||||
status: ActiveExerciseStepProgressStatus.stoppedTimer,
|
||||
accumulatedMs: 0,
|
||||
lastTransitionAt: clock.now(),
|
||||
);
|
||||
final useCase = _activeUseCase(repository, clock);
|
||||
|
||||
final started = await useCase.startSetExecution(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: 0,
|
||||
exerciseIndex: 0,
|
||||
setIndex: 0,
|
||||
);
|
||||
|
||||
expect(
|
||||
started.stepProgress?.status,
|
||||
ActiveExerciseStepProgressStatus.stoppedTimer,
|
||||
);
|
||||
expect(started.stepProgress?.currentStepIndex, 1);
|
||||
expect(started.stepProgress?.startedAt, isNull);
|
||||
},
|
||||
);
|
||||
|
||||
test('pause and resume restore running persistent timers', () async {
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||
final session = _sessionWithSnapshot(
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
timeEnabled: true,
|
||||
repsEnabled: false,
|
||||
scoreEnabled: true,
|
||||
scoreInputMode: ScoreInputMode.stopwatch,
|
||||
exerciseSteps: [_step(type: ExerciseStepType.time)],
|
||||
);
|
||||
final repository = _FakeActiveSessionRepository()..session = session;
|
||||
final useCase = _activeUseCase(repository, clock);
|
||||
|
||||
await useCase.startSetExecution(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: 0,
|
||||
exerciseIndex: 0,
|
||||
setIndex: 0,
|
||||
);
|
||||
await useCase.startRestAfterSet(
|
||||
sessionId: session.metadata.id,
|
||||
afterProgramIndex: 0,
|
||||
afterExerciseIndex: 0,
|
||||
afterSetIndex: 0,
|
||||
plannedRestSeconds: 60,
|
||||
);
|
||||
clock.value = clock.value.add(const Duration(seconds: 5));
|
||||
await useCase.pause(session.metadata.id);
|
||||
|
||||
expect(
|
||||
repository.setTimerStates.values.single.status,
|
||||
ActiveSetTimerStatus.paused,
|
||||
);
|
||||
expect(repository.setTimerStates.values.single.accumulatedMs, 5000);
|
||||
expect(
|
||||
repository.scoreStopwatchStates.values.single.status,
|
||||
ActiveScoreStopwatchStatus.paused,
|
||||
);
|
||||
expect(
|
||||
repository.stepProgressStates.values.single.status,
|
||||
ActiveExerciseStepProgressStatus.pausedTimer,
|
||||
);
|
||||
final pausedRest = repository.restStates.values.single;
|
||||
expect(pausedRest.pausedAt, clock.now());
|
||||
expect(pausedRest.remainingMillisecondsAt(clock.now()), 55000);
|
||||
|
||||
clock.value = clock.value.add(const Duration(minutes: 2));
|
||||
await useCase.resume(session.metadata.id);
|
||||
|
||||
expect(
|
||||
repository.setTimerStates.values.single.status,
|
||||
ActiveSetTimerStatus.running,
|
||||
);
|
||||
expect(
|
||||
repository.scoreStopwatchStates.values.single.status,
|
||||
ActiveScoreStopwatchStatus.running,
|
||||
);
|
||||
expect(
|
||||
repository.stepProgressStates.values.single.status,
|
||||
ActiveExerciseStepProgressStatus.runningTimer,
|
||||
);
|
||||
final resumedRest = repository.restStates.values.single;
|
||||
expect(resumedRest.pausedAt, isNull);
|
||||
expect(resumedRest.accumulatedPausedMs, 120000);
|
||||
expect(resumedRest.remainingMillisecondsAt(clock.now()), 55000);
|
||||
});
|
||||
|
||||
test('upsertSetResultAtPosition does not move session cursor', () async {
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||
final session = _sessionWithSnapshot(
|
||||
@ -2830,6 +3019,7 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
ActiveWorkoutSession? session;
|
||||
final results = <ActiveSetResult>[];
|
||||
final restStates = <String, ActiveRestState>{};
|
||||
final setTimerStates = <String, ActiveSetTimerState>{};
|
||||
final scoreStopwatchStates = <String, ActiveScoreStopwatchState>{};
|
||||
final stepProgressStates = <String, ActiveExerciseStepProgressState>{};
|
||||
final stepResults = <ActiveExerciseStepResult>[];
|
||||
@ -2864,6 +3054,25 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
.firstOrNull;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ActiveSetTimerState?> findSetTimerState({
|
||||
required String sessionId,
|
||||
required int programIndex,
|
||||
required int exerciseIndex,
|
||||
required int setIndex,
|
||||
}) async {
|
||||
return setTimerStates.values
|
||||
.where(
|
||||
(state) =>
|
||||
state.activeWorkoutSessionId == sessionId &&
|
||||
state.programIndex == programIndex &&
|
||||
state.exerciseIndex == exerciseIndex &&
|
||||
state.setIndex == setIndex &&
|
||||
state.metadata.deletedAt == null,
|
||||
)
|
||||
.firstOrNull;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ActiveExerciseStepProgressState?> findExerciseStepProgressState({
|
||||
required String sessionId,
|
||||
@ -2910,6 +3119,17 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ActiveSetTimerState>> listSetTimerStates(String sessionId) async {
|
||||
return setTimerStates.values
|
||||
.where(
|
||||
(state) =>
|
||||
state.activeWorkoutSessionId == sessionId &&
|
||||
state.metadata.deletedAt == null,
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ActiveExerciseStepProgressState>> listExerciseStepProgressStates(
|
||||
String sessionId,
|
||||
@ -2938,6 +3158,11 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
restStates[restState.metadata.id] = restState;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveSetTimerState(ActiveSetTimerState state) async {
|
||||
setTimerStates[state.metadata.id] = state;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveScoreStopwatchState(ActiveScoreStopwatchState state) async {
|
||||
scoreStopwatchStates[state.metadata.id] = state;
|
||||
@ -3003,6 +3228,12 @@ ActiveWorkoutSession _sessionWithSnapshot({
|
||||
required int currentSetIndex,
|
||||
int setsCount = 2,
|
||||
int targetReps = 10,
|
||||
bool timeEnabled = false,
|
||||
bool repsEnabled = true,
|
||||
bool scoreEnabled = false,
|
||||
int? targetTimeSeconds,
|
||||
ScoreInputMode scoreInputMode = ScoreInputMode.manual,
|
||||
int? targetScoreTimeMs,
|
||||
List<ExerciseStep> exerciseSteps = const [],
|
||||
bool? autoStartNextTimedStepSnapshot = true,
|
||||
bool? autoStartNextTimedStepOverride,
|
||||
@ -3012,10 +3243,13 @@ ActiveWorkoutSession _sessionWithSnapshot({
|
||||
'id': 'exercise-snapshot-1',
|
||||
'exerciseNameSnapshot': 'Squat',
|
||||
'setsCount': setsCount,
|
||||
'timeEnabled': false,
|
||||
'repsEnabled': true,
|
||||
'scoreEnabled': false,
|
||||
'targetReps': targetReps,
|
||||
'timeEnabled': timeEnabled,
|
||||
'repsEnabled': repsEnabled,
|
||||
'scoreEnabled': scoreEnabled,
|
||||
'targetTimeSeconds': targetTimeSeconds,
|
||||
'targetReps': repsEnabled ? targetReps : null,
|
||||
'targetScoreTimeMs': targetScoreTimeMs,
|
||||
'scoreInputModeSnapshot': scoreInputMode.name,
|
||||
'exerciseStepsSnapshot': exerciseSteps
|
||||
.map((step) => step.toSnapshotJson())
|
||||
.toList(),
|
||||
|
||||
@ -258,6 +258,196 @@ void main() {
|
||||
expect(restored.steps[2].defaultTargetScoreTimeMs, 12000);
|
||||
});
|
||||
|
||||
group('exercise repository round-trips exercise option combinations', () {
|
||||
final now = DateTime.utc(2026, 7, 17, 12);
|
||||
final cases = <_ExerciseRoundTripCase>[
|
||||
_ExerciseRoundTripCase(
|
||||
label: 'reps without score or steps',
|
||||
exercise: Exercise(
|
||||
metadata: _metadata('exercise-combo-reps', now),
|
||||
name: 'Pompes',
|
||||
description: 'Simple reps target',
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: false,
|
||||
defaultTargetReps: 12,
|
||||
),
|
||||
),
|
||||
_ExerciseRoundTripCase(
|
||||
label: 'reps without score and with steps',
|
||||
exercise: Exercise(
|
||||
metadata: _metadata('exercise-combo-reps-steps', now),
|
||||
name: 'Complexe poids du corps',
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: false,
|
||||
defaultTargetReps: 1,
|
||||
steps: [
|
||||
_exerciseStep(
|
||||
id: 'combo-reps-step-1',
|
||||
position: 0,
|
||||
name: 'Pompes',
|
||||
type: ExerciseStepType.reps,
|
||||
defaultTargetValue: 10,
|
||||
),
|
||||
_exerciseStep(
|
||||
id: 'combo-reps-step-2',
|
||||
position: 1,
|
||||
name: 'Gainage',
|
||||
type: ExerciseStepType.time,
|
||||
defaultTargetValue: 30,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_ExerciseRoundTripCase(
|
||||
label: 'manual score without steps',
|
||||
exercise: Exercise(
|
||||
metadata: _metadata('exercise-combo-manual-score', now),
|
||||
name: 'Charge max',
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: false,
|
||||
hasScoreMeasure: true,
|
||||
scoreLabel: 'Charge',
|
||||
scoreUnit: 'kg',
|
||||
defaultTargetScore: 0,
|
||||
),
|
||||
),
|
||||
_ExerciseRoundTripCase(
|
||||
label: 'manual score with unscored steps',
|
||||
exercise: Exercise(
|
||||
metadata: _metadata('exercise-combo-manual-score-steps', now),
|
||||
name: 'Technique haltères',
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: true,
|
||||
scoreLabel: 'Charge',
|
||||
scoreUnit: 'kg',
|
||||
defaultTargetReps: 8,
|
||||
defaultTargetScore: 12.5,
|
||||
steps: [
|
||||
_exerciseStep(
|
||||
id: 'combo-manual-score-step-1',
|
||||
position: 0,
|
||||
name: 'Installation',
|
||||
type: ExerciseStepType.time,
|
||||
defaultTargetValue: 15,
|
||||
),
|
||||
_exerciseStep(
|
||||
id: 'combo-manual-score-step-2',
|
||||
position: 1,
|
||||
name: 'Serie',
|
||||
type: ExerciseStepType.reps,
|
||||
defaultTargetValue: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_ExerciseRoundTripCase(
|
||||
label: 'manual exercise score with manual scored steps',
|
||||
exercise: Exercise(
|
||||
metadata: _metadata('exercise-combo-manual-step-score', now),
|
||||
name: 'Circuit precision',
|
||||
hasTimeMeasure: true,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: true,
|
||||
scoreLabel: 'Qualite',
|
||||
scoreUnit: 'pts',
|
||||
defaultTargetTimeSeconds: 45,
|
||||
defaultTargetReps: 10,
|
||||
defaultTargetScore: 80,
|
||||
autoStartNextTimedStep: false,
|
||||
steps: [
|
||||
_exerciseStep(
|
||||
id: 'combo-manual-step-score-1',
|
||||
position: 0,
|
||||
name: 'Bloc reps',
|
||||
type: ExerciseStepType.reps,
|
||||
defaultTargetValue: 10,
|
||||
hasScore: true,
|
||||
scoreLabel: 'Amplitude',
|
||||
scoreUnit: 'pts',
|
||||
defaultTargetScore: 0,
|
||||
),
|
||||
_exerciseStep(
|
||||
id: 'combo-manual-step-score-2',
|
||||
position: 1,
|
||||
name: 'Bloc temps',
|
||||
type: ExerciseStepType.time,
|
||||
defaultTargetValue: 35,
|
||||
hasScore: true,
|
||||
scoreLabel: 'Tenue',
|
||||
scoreUnit: 's',
|
||||
defaultTargetScore: 35,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_ExerciseRoundTripCase(
|
||||
label: 'stopwatch score without steps',
|
||||
exercise: Exercise(
|
||||
metadata: _metadata('exercise-combo-stopwatch-score', now),
|
||||
name: 'Sprint chrono',
|
||||
hasTimeMeasure: true,
|
||||
hasRepsMeasure: false,
|
||||
hasScoreMeasure: true,
|
||||
scoreInputMode: ScoreInputMode.stopwatch,
|
||||
defaultTargetTimeSeconds: 20,
|
||||
defaultTargetScoreTimeMs: 11500,
|
||||
),
|
||||
),
|
||||
_ExerciseRoundTripCase(
|
||||
label: 'stopwatch score with stopwatch scored steps',
|
||||
exercise: Exercise(
|
||||
metadata: _metadata('exercise-combo-stopwatch-score-steps', now),
|
||||
name: 'Sprint fractionne',
|
||||
imageMediaIds: const ['media-start', 'media-finish'],
|
||||
iconMediaId: 'media-start',
|
||||
videoMediaId: 'video-demo',
|
||||
hasTimeMeasure: true,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: true,
|
||||
scoreInputMode: ScoreInputMode.stopwatch,
|
||||
defaultTargetTimeSeconds: 60,
|
||||
defaultTargetReps: 4,
|
||||
defaultTargetScoreTimeMs: 42000,
|
||||
steps: [
|
||||
_exerciseStep(
|
||||
id: 'combo-stopwatch-step-1',
|
||||
position: 0,
|
||||
name: 'Acceleration',
|
||||
type: ExerciseStepType.time,
|
||||
defaultTargetValue: 10,
|
||||
hasScore: true,
|
||||
scoreInputMode: ScoreInputMode.stopwatch,
|
||||
defaultTargetScoreTimeMs: 9500,
|
||||
),
|
||||
_exerciseStep(
|
||||
id: 'combo-stopwatch-step-2',
|
||||
position: 1,
|
||||
name: 'Recuperation active',
|
||||
type: ExerciseStepType.reps,
|
||||
defaultTargetValue: 6,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
for (final roundTripCase in cases) {
|
||||
test(roundTripCase.label, () async {
|
||||
await exerciseRepository.save(roundTripCase.exercise);
|
||||
|
||||
final restored = await exerciseRepository.findById(
|
||||
roundTripCase.exercise.metadata.id,
|
||||
);
|
||||
|
||||
expect(restored, isNotNull);
|
||||
_expectExerciseEquals(restored!, roundTripCase.exercise);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test(
|
||||
'program exercise step snapshot is preserved when source exercise changes',
|
||||
() async {
|
||||
@ -578,6 +768,94 @@ ProgramExercise _programExercise(
|
||||
);
|
||||
}
|
||||
|
||||
ExerciseStep _exerciseStep({
|
||||
required String id,
|
||||
required int position,
|
||||
required String name,
|
||||
required ExerciseStepType type,
|
||||
required int defaultTargetValue,
|
||||
bool hasScore = false,
|
||||
ScoreInputMode scoreInputMode = ScoreInputMode.manual,
|
||||
String? scoreLabel,
|
||||
String? scoreUnit,
|
||||
double? defaultTargetScore,
|
||||
int? defaultTargetScoreTimeMs,
|
||||
}) {
|
||||
return ExerciseStep(
|
||||
id: id,
|
||||
position: position,
|
||||
name: name,
|
||||
type: type,
|
||||
defaultTargetValue: defaultTargetValue,
|
||||
hasScore: hasScore,
|
||||
scoreInputMode: scoreInputMode,
|
||||
scoreLabel: scoreLabel,
|
||||
scoreUnit: scoreUnit,
|
||||
defaultTargetScore: defaultTargetScore,
|
||||
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
|
||||
);
|
||||
}
|
||||
|
||||
void _expectExerciseEquals(Exercise actual, Exercise expected) {
|
||||
_expectMetadataEquals(actual.metadata, expected.metadata);
|
||||
expect(actual.name, expected.name);
|
||||
expect(actual.description, expected.description);
|
||||
expect(actual.imageMediaIds, expected.imageMediaIds);
|
||||
expect(actual.iconMediaId, expected.iconMediaId);
|
||||
expect(actual.videoMediaId, expected.videoMediaId);
|
||||
expect(actual.hasTimeMeasure, expected.hasTimeMeasure);
|
||||
expect(actual.hasRepsMeasure, expected.hasRepsMeasure);
|
||||
expect(actual.hasScoreMeasure, expected.hasScoreMeasure);
|
||||
expect(actual.scoreInputMode, expected.scoreInputMode);
|
||||
expect(actual.scoreLabel, expected.scoreLabel);
|
||||
expect(actual.scoreUnit, expected.scoreUnit);
|
||||
expect(actual.defaultTargetTimeSeconds, expected.defaultTargetTimeSeconds);
|
||||
expect(actual.defaultTargetReps, expected.defaultTargetReps);
|
||||
expect(actual.defaultTargetScore, expected.defaultTargetScore);
|
||||
expect(actual.defaultTargetScoreTimeMs, expected.defaultTargetScoreTimeMs);
|
||||
expect(actual.autoStartNextTimedStep, expected.autoStartNextTimedStep);
|
||||
expect(actual.archivedAt?.toUtc(), expected.archivedAt?.toUtc());
|
||||
expect(actual.steps, hasLength(expected.steps.length));
|
||||
for (var index = 0; index < expected.steps.length; index++) {
|
||||
_expectExerciseStepEquals(actual.steps[index], expected.steps[index]);
|
||||
}
|
||||
}
|
||||
|
||||
void _expectExerciseStepEquals(ExerciseStep actual, ExerciseStep expected) {
|
||||
expect(actual.id, expected.id);
|
||||
expect(actual.position, expected.position);
|
||||
expect(actual.name, expected.name);
|
||||
expect(actual.type, expected.type);
|
||||
expect(actual.defaultTargetValue, expected.defaultTargetValue);
|
||||
expect(actual.hasScore, expected.hasScore);
|
||||
expect(actual.scoreInputMode, expected.scoreInputMode);
|
||||
expect(actual.scoreLabel, expected.scoreLabel);
|
||||
expect(actual.scoreUnit, expected.scoreUnit);
|
||||
expect(actual.defaultTargetScore, expected.defaultTargetScore);
|
||||
expect(actual.defaultTargetScoreTimeMs, expected.defaultTargetScoreTimeMs);
|
||||
}
|
||||
|
||||
void _expectMetadataEquals(EntityMetadata actual, EntityMetadata expected) {
|
||||
expect(actual.id, expected.id);
|
||||
expect(actual.createdAt.toUtc(), expected.createdAt.toUtc());
|
||||
expect(actual.updatedAt.toUtc(), expected.updatedAt.toUtc());
|
||||
expect(actual.deletedAt?.toUtc(), expected.deletedAt?.toUtc());
|
||||
expect(actual.schemaVersion, expected.schemaVersion);
|
||||
expect(actual.syncState, expected.syncState);
|
||||
expect(actual.localRevision, expected.localRevision);
|
||||
expect(actual.originDeviceId, expected.originDeviceId);
|
||||
expect(actual.futureOwnerProfileId, expected.futureOwnerProfileId);
|
||||
expect(actual.lastSyncedAt?.toUtc(), expected.lastSyncedAt?.toUtc());
|
||||
expect(actual.remoteRevision, expected.remoteRevision);
|
||||
}
|
||||
|
||||
final class _ExerciseRoundTripCase {
|
||||
const _ExerciseRoundTripCase({required this.label, required this.exercise});
|
||||
|
||||
final String label;
|
||||
final Exercise exercise;
|
||||
}
|
||||
|
||||
final class _FakeClock implements Clock {
|
||||
const _FakeClock(this.value);
|
||||
|
||||
|
||||
@ -430,6 +430,7 @@ void main() {
|
||||
|
||||
final saved = exerciseRepository.saved.single;
|
||||
expect(saved.steps, hasLength(1));
|
||||
expect(saved.steps.single.id, isNot(startsWith('step-draft-')));
|
||||
expect(saved.steps.single.name, 'Dribble main droite');
|
||||
expect(saved.steps.single.type, ExerciseStepType.time);
|
||||
expect(saved.steps.single.defaultTargetValue, 12);
|
||||
|
||||
@ -437,6 +437,14 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
required int setIndex,
|
||||
}) async => null;
|
||||
|
||||
@override
|
||||
Future<ActiveSetTimerState?> findSetTimerState({
|
||||
required String sessionId,
|
||||
required int programIndex,
|
||||
required int exerciseIndex,
|
||||
required int setIndex,
|
||||
}) async => null;
|
||||
|
||||
@override
|
||||
Future<ActiveExerciseStepProgressState?> findExerciseStepProgressState({
|
||||
required String sessionId,
|
||||
@ -457,6 +465,11 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
return const [];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ActiveSetTimerState>> listSetTimerStates(String sessionId) async {
|
||||
return const [];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ActiveSetResult>> listSetResults(String sessionId) async {
|
||||
return const [];
|
||||
@ -485,6 +498,9 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
@override
|
||||
Future<void> saveScoreStopwatchState(ActiveScoreStopwatchState state) async {}
|
||||
|
||||
@override
|
||||
Future<void> saveSetTimerState(ActiveSetTimerState state) async {}
|
||||
|
||||
@override
|
||||
Future<void> saveExerciseStepProgressState(
|
||||
ActiveExerciseStepProgressState state,
|
||||
|
||||
@ -474,6 +474,7 @@ final class _FakePendingShareActionRepository
|
||||
final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
ActiveWorkoutSession? session;
|
||||
final restStates = <ActiveRestState>[];
|
||||
final setTimerStates = <ActiveSetTimerState>[];
|
||||
final scoreStopwatchStates = <ActiveScoreStopwatchState>[];
|
||||
final results = <ActiveSetResult>[];
|
||||
|
||||
@ -498,6 +499,16 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ActiveSetTimerState?> findSetTimerState({
|
||||
required String sessionId,
|
||||
required int programIndex,
|
||||
required int exerciseIndex,
|
||||
required int setIndex,
|
||||
}) async {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ActiveExerciseStepProgressState?> findExerciseStepProgressState({
|
||||
required String sessionId,
|
||||
@ -520,6 +531,11 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
return scoreStopwatchStates;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ActiveSetTimerState>> listSetTimerStates(String sessionId) async {
|
||||
return setTimerStates;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ActiveSetResult>> listSetResults(String sessionId) async {
|
||||
return results;
|
||||
@ -554,6 +570,11 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
scoreStopwatchStates.add(state);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveSetTimerState(ActiveSetTimerState state) async {
|
||||
setTimerStates.add(state);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveExerciseStepProgressState(
|
||||
ActiveExerciseStepProgressState state,
|
||||
|
||||
@ -40,7 +40,9 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('00:45'), findsOneWidget);
|
||||
expect(find.text('Squat'), findsOneWidget);
|
||||
expect(find.text('Temps de série : 00:45'), findsOneWidget);
|
||||
expect(find.text('Démarrer l’exercice'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('les répétitions sont initialisées avec la cible', (
|
||||
@ -253,7 +255,7 @@ void main() {
|
||||
|
||||
expect(find.byTooltip('Voir les médias de l’exercice'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Voir médias'));
|
||||
await tester.tap(find.byTooltip('Voir les médias de l’exercice'));
|
||||
await tester.runAsync(() async {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
@ -319,7 +321,7 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
await tester.tap(find.text('Voir médias'));
|
||||
await tester.tap(find.byTooltip('Voir les médias de l’exercice'));
|
||||
await tester.runAsync(() async {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
@ -377,7 +379,7 @@ void main() {
|
||||
expect(find.text('Chrono score'), findsOneWidget);
|
||||
expect(find.text('00:00.0'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Démarrer'));
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Démarrer'));
|
||||
await tester.pump();
|
||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 0, 100);
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
@ -394,7 +396,7 @@ void main() {
|
||||
|
||||
expect(find.text('00:01.2'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Arrêter'));
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Arrêter'));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('00:01.2'), findsOneWidget);
|
||||
@ -443,9 +445,15 @@ void main() {
|
||||
await tester.tap(find.text('Terminer la série'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Aucun temps chronométré'), findsOneWidget);
|
||||
expect(find.text("Tu n'as pas démarré le chrono score."), findsOneWidget);
|
||||
expect(find.text('Démarrer le chrono'), findsOneWidget);
|
||||
expect(find.text('Chrono non lancé'), findsOneWidget);
|
||||
expect(
|
||||
find.text("Tu n’as pas démarré le chrono de cette série."),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.widgetWithText(TextButton, 'Démarrer l’exercice'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('Terminer sans chrono'), findsOneWidget);
|
||||
expect(repository.results, isEmpty);
|
||||
});
|
||||
@ -487,7 +495,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Démarrer'));
|
||||
await tester.tap(find.text('Démarrer l’exercice'));
|
||||
await tester.pump();
|
||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 2, 500);
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
@ -565,6 +573,59 @@ void main() {
|
||||
expect(repository.restStates.single.skippedAt, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('un repos en pause est repris sans compter la durée de pause', (
|
||||
tester,
|
||||
) async {
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||
final repository = _FakeActiveSessionRepository();
|
||||
final session = ActiveWorkoutSession(
|
||||
metadata: _metadata('session-1'),
|
||||
sourceWorkoutTemplateId: 'template-1',
|
||||
status: ActiveWorkoutStatus.running,
|
||||
startedAt: DateTime.utc(2026, 7, 17, 12),
|
||||
lastPersistedAt: DateTime.utc(2026, 7, 17, 12),
|
||||
elapsedActiveMs: 0,
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
resolvedTemplateSnapshotJson: _sessionSnapshot(
|
||||
setsCount: 2,
|
||||
restSeconds: 60,
|
||||
),
|
||||
);
|
||||
repository.session = session;
|
||||
final now = DateTime.now().toUtc();
|
||||
repository.restStates.add(
|
||||
ActiveRestState(
|
||||
metadata: _metadata('rest-1'),
|
||||
activeWorkoutSessionId: 'session-1',
|
||||
afterProgramIndex: 0,
|
||||
afterExerciseIndex: 0,
|
||||
afterSetIndex: 0,
|
||||
plannedRestSeconds: 60,
|
||||
adjustedRestSeconds: 60,
|
||||
startedAt: now.subtract(const Duration(minutes: 10, seconds: 15)),
|
||||
pausedAt: now.subtract(const Duration(minutes: 10)),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: _activeUseCases(repository, clock),
|
||||
closeUseCase: _closeUseCase(repository, clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Repos avant la prochaine série'), findsOneWidget);
|
||||
expect(find.text('00:45'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('la mesure temps enregistre une durée réelle de série', (
|
||||
tester,
|
||||
) async {
|
||||
@ -601,11 +662,14 @@ void main() {
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.tap(find.text('Démarrer l’exercice'));
|
||||
await tester.pump();
|
||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 2);
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
await tester.tap(find.text('Terminer la série'));
|
||||
await tester.pump();
|
||||
|
||||
expect(repository.results.single.actualTimeMs, greaterThan(0));
|
||||
expect(repository.results.single.actualTimeMs, 2000);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
@ -650,9 +714,12 @@ void main() {
|
||||
);
|
||||
|
||||
expect(find.text('Programme 1/1 · Exercice 1/1'), findsOneWidget);
|
||||
expect(find.text('SÉRIE'), findsOneWidget);
|
||||
expect(find.text('2 / 3'), findsOneWidget);
|
||||
expect(find.text('30 s'), findsOneWidget);
|
||||
expect(find.text('Série 2/3'), findsOneWidget);
|
||||
expect(find.text('Squat'), findsOneWidget);
|
||||
expect(find.text('Temps de série : 00:30'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Démarrer l’exercice'));
|
||||
await tester.pump();
|
||||
|
||||
for (var i = 0; i < 2; i++) {
|
||||
await tester.tap(find.byIcon(Icons.remove));
|
||||
@ -672,7 +739,7 @@ void main() {
|
||||
expect(repository.results.single.actualScore, 5);
|
||||
expect(repository.session?.currentSetIndex, 2);
|
||||
expect(find.text('Programme 1/1 · Exercice 1/1'), findsOneWidget);
|
||||
expect(find.text('3 / 3'), findsOneWidget);
|
||||
expect(find.text('Série 3/3'), findsOneWidget);
|
||||
expect(find.text('5'), findsOneWidget);
|
||||
},
|
||||
);
|
||||
@ -959,7 +1026,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Programme 1/1 · Exercice 1/1'), findsOneWidget);
|
||||
expect(find.text('3 / 4'), findsOneWidget);
|
||||
expect(find.text('Série 3/4'), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
@ -1005,7 +1072,7 @@ void main() {
|
||||
expect(find.text('Plan de séance'), findsNothing);
|
||||
expect(find.text('Modifier la série'), findsNothing);
|
||||
expect(find.text('Programme 1/1 · Exercice 1/1'), findsOneWidget);
|
||||
expect(find.text('2 / 3'), findsOneWidget);
|
||||
expect(find.text('Série 2/3'), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
@ -1052,6 +1119,7 @@ void main() {
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(ListView), findsNothing);
|
||||
expect(find.text('SÉQUENCE'), findsOneWidget);
|
||||
expect(find.text('Passage 1 / 10'), findsOneWidget);
|
||||
expect(find.text('ÉTAPE 1 / 2'), findsOneWidget);
|
||||
@ -1147,7 +1215,7 @@ void main() {
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('00:05'), findsOneWidget);
|
||||
await tester.tap(find.text('Démarrer la séquence'));
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Démarrer la séquence'));
|
||||
await tester.pump();
|
||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 2);
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
@ -1156,6 +1224,68 @@ void main() {
|
||||
expect(audio.shortBeeps, 1);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'terminer sans démarrer la première étape temps affiche une confirmation',
|
||||
(tester) async {
|
||||
await tester.binding.setSurfaceSize(const Size(400, 1600));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||
final repository = _FakeActiveSessionRepository();
|
||||
final audio = _FakeStepAudioCuePlayer();
|
||||
final session = ActiveWorkoutSession(
|
||||
metadata: _metadata('session-1'),
|
||||
sourceWorkoutTemplateId: 'template-1',
|
||||
status: ActiveWorkoutStatus.running,
|
||||
startedAt: DateTime.utc(2026, 7, 17, 12),
|
||||
lastPersistedAt: DateTime.utc(2026, 7, 17, 12),
|
||||
elapsedActiveMs: 0,
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
resolvedTemplateSnapshotJson: _sessionSnapshot(
|
||||
timeEnabled: false,
|
||||
scoreEnabled: false,
|
||||
targetReps: 1,
|
||||
exerciseSteps: [
|
||||
_step('step-1', 0, 'Gainage', ExerciseStepType.time, 5),
|
||||
],
|
||||
),
|
||||
);
|
||||
repository.session = session;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: _activeUseCases(repository, clock),
|
||||
stepUseCases: _stepUseCases(repository, clock),
|
||||
stepAudioCuePlayer: audio,
|
||||
closeUseCase: _closeUseCase(repository, clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.text('Terminer la série'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Chrono non lancé'), findsOneWidget);
|
||||
expect(
|
||||
find.text("Tu n’as pas démarré le chrono de cette série."),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.widgetWithText(TextButton, 'Démarrer l’exercice'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('Terminer sans chrono'), findsOneWidget);
|
||||
expect(repository.results, isEmpty);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('deux étapes temps consécutives s’enchaînent automatiquement', (
|
||||
tester,
|
||||
) async {
|
||||
@ -1200,7 +1330,7 @@ void main() {
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.text('Démarrer la séquence'));
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Démarrer la séquence'));
|
||||
await tester.pump();
|
||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 1, 200);
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
@ -1262,7 +1392,9 @@ void main() {
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.text('Démarrer la séquence'));
|
||||
await tester.tap(
|
||||
find.widgetWithText(FilledButton, 'Démarrer la séquence'),
|
||||
);
|
||||
await tester.pump();
|
||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 1, 200);
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
@ -1280,7 +1412,7 @@ void main() {
|
||||
);
|
||||
expect(repository.stepProgressStates.single.startedAt, isNull);
|
||||
|
||||
await tester.tap(find.text('Démarrer le chrono'));
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Démarrer le chrono'));
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
@ -1438,6 +1570,8 @@ void main() {
|
||||
expect(repository.stepResults.single.stepIndex, 0);
|
||||
expect(repository.stepResults.single.status, SetResultStatus.skipped);
|
||||
|
||||
await tester.tap(find.byTooltip('Plus d’actions'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Passer ce passage'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Passer ce passage'));
|
||||
@ -1445,6 +1579,8 @@ void main() {
|
||||
|
||||
expect(find.text('Passage 2 / 2'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byTooltip('Plus d’actions'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Passer la séquence'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Passer la séquence'));
|
||||
@ -1529,6 +1665,9 @@ WorkoutTemplateUseCases _workoutTemplateUseCases() {
|
||||
String _sessionSnapshot({
|
||||
int setsCount = 3,
|
||||
int restSeconds = 0,
|
||||
bool timeEnabled = true,
|
||||
bool repsEnabled = true,
|
||||
bool scoreEnabled = true,
|
||||
int targetTimeSeconds = 45,
|
||||
int targetReps = 10,
|
||||
double targetScore = 80,
|
||||
@ -1552,9 +1691,9 @@ String _sessionSnapshot({
|
||||
'id': 'exercise-1',
|
||||
'exerciseNameSnapshot': 'Squat',
|
||||
'setsCount': setsCount,
|
||||
'timeEnabled': true,
|
||||
'repsEnabled': true,
|
||||
'scoreEnabled': true,
|
||||
'timeEnabled': timeEnabled,
|
||||
'repsEnabled': repsEnabled,
|
||||
'scoreEnabled': scoreEnabled,
|
||||
'targetTimeSeconds': targetTimeSeconds,
|
||||
'targetReps': targetReps,
|
||||
'targetScore': targetScore,
|
||||
@ -1660,6 +1799,7 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
final results = <ActiveSetResult>[];
|
||||
final restStates = <ActiveRestState>[];
|
||||
final scoreStopwatchStates = <ActiveScoreStopwatchState>[];
|
||||
final setTimerStates = <ActiveSetTimerState>[];
|
||||
final stepProgressStates = <ActiveExerciseStepProgressState>[];
|
||||
final stepResults = <ActiveExerciseStepResult>[];
|
||||
|
||||
@ -1700,6 +1840,25 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ActiveSetTimerState?> findSetTimerState({
|
||||
required String sessionId,
|
||||
required int programIndex,
|
||||
required int exerciseIndex,
|
||||
required int setIndex,
|
||||
}) async {
|
||||
for (final state in setTimerStates) {
|
||||
if (state.activeWorkoutSessionId == sessionId &&
|
||||
state.programIndex == programIndex &&
|
||||
state.exerciseIndex == exerciseIndex &&
|
||||
state.setIndex == setIndex &&
|
||||
state.metadata.deletedAt == null) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ActiveExerciseStepProgressState?> findExerciseStepProgressState({
|
||||
required String sessionId,
|
||||
@ -1740,6 +1899,13 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
return results;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ActiveSetTimerState>> listSetTimerStates(String sessionId) async {
|
||||
return setTimerStates
|
||||
.where((state) => state.activeWorkoutSessionId == sessionId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ActiveExerciseStepProgressState>> listExerciseStepProgressStates(
|
||||
String sessionId,
|
||||
@ -1787,6 +1953,22 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveSetTimerState(ActiveSetTimerState state) async {
|
||||
final index = setTimerStates.indexWhere(
|
||||
(saved) =>
|
||||
saved.activeWorkoutSessionId == state.activeWorkoutSessionId &&
|
||||
saved.programIndex == state.programIndex &&
|
||||
saved.exerciseIndex == state.exerciseIndex &&
|
||||
saved.setIndex == state.setIndex,
|
||||
);
|
||||
if (index == -1) {
|
||||
setTimerStates.add(state);
|
||||
} else {
|
||||
setTimerStates[index] = state;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveExerciseStepProgressState(
|
||||
ActiveExerciseStepProgressState state,
|
||||
|
||||
@ -523,6 +523,16 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ActiveSetTimerState?> findSetTimerState({
|
||||
required String sessionId,
|
||||
required int programIndex,
|
||||
required int exerciseIndex,
|
||||
required int setIndex,
|
||||
}) async {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ActiveExerciseStepProgressState?> findExerciseStepProgressState({
|
||||
required String sessionId,
|
||||
@ -545,6 +555,11 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
return const [];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ActiveSetTimerState>> listSetTimerStates(String sessionId) async {
|
||||
return const [];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ActiveSetResult>> listSetResults(String sessionId) async {
|
||||
return const [];
|
||||
@ -576,6 +591,9 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
@override
|
||||
Future<void> saveScoreStopwatchState(ActiveScoreStopwatchState state) async {}
|
||||
|
||||
@override
|
||||
Future<void> saveSetTimerState(ActiveSetTimerState state) async {}
|
||||
|
||||
@override
|
||||
Future<void> saveExerciseStepProgressState(
|
||||
ActiveExerciseStepProgressState state,
|
||||
|
||||
Reference in New Issue
Block a user