Files
GameTime/test/application/watch_companion_projection_test.dart
Blomios 65d43b9768 feat(watch): clôture lot #91 - fréquence cardiaque live, notifications de séance et finitions montre
Consolide le lot applicatif watch companion validé :
- télémétrie fréquence cardiaque live remontée montre -> téléphone
  (collecteur watch, adapter Wear Data Layer, persistance Drift,
  propagation aux écrans historique/programme/profil/exécution)
- notifications de séance en arrière-plan côté téléphone (service
  foreground de statut + passerelle applicative)
- finitions montre : chrono d'étape, score d'étape, retrait du bouton
  "lancer une séance", thème, icônes et polices watch_app

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 05:56:12 +02:00

863 lines
27 KiB
Dart

import 'dart:convert';
import 'package:gametime/application/application.dart';
import 'package:gametime/domain/domain.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:watch_bridge_contract/watch_bridge_contract.dart';
void main() {
test('projects noActiveSession without an open session', () async {
final projector = _projector(_FakeActiveSessionRepository(), _clock());
final projection = await projector.project(revision: 1);
expect(projection.phase, WatchSessionPhase.noActiveSession);
expect(projection.primaryAction, WatchPrimaryAction.none);
expect(projection.phoneReachable, isTrue);
});
test('projects ready at the first set before timers start', () async {
final repository = _FakeActiveSessionRepository()
..session = _session(
currentSetIndex: 0,
timeEnabled: true,
targetTimeSeconds: 20,
steps: [_step(defaultTargetValue: 20)],
);
final projector = _projector(repository, _clock());
final projection = await projector.project(revision: 1);
expect(projection.phase, WatchSessionPhase.ready);
expect(projection.seriesIndex, 1);
expect(projection.seriesTotal, 2);
expect(projection.exerciseName, 'Squat');
expect(projection.stepIndex, 1);
expect(projection.stepTotal, 1);
expect(projection.dominantTimer?.kind, WatchTimerKind.step);
expect(projection.dominantTimer?.runState, WatchTimerRunState.stopped);
expect(projection.dominantTimer?.targetMs, 20000);
expect(projection.primaryAction, WatchPrimaryAction.startCurrentExercise);
});
test(
'projects running with dominant step timer and secondary timers',
() async {
final now = DateTime.utc(2026, 7, 25, 12);
final session = _session(
timeEnabled: true,
scoreEnabled: true,
scoreInputMode: ScoreInputMode.stopwatch,
steps: [_step(defaultTargetValue: 30)],
);
final repository = _FakeActiveSessionRepository()
..session = session
..stepProgressStates['step-state'] = _stepState(
sessionId: session.metadata.id,
status: ActiveExerciseStepProgressStatus.runningTimer,
startedAt: now.subtract(const Duration(seconds: 5)),
)
..scoreStopwatchStates['score'] = _scoreStopwatch(
sessionId: session.metadata.id,
startedAt: now.subtract(const Duration(seconds: 4)),
)
..setTimerStates['set'] = _setTimer(
sessionId: session.metadata.id,
startedAt: now.subtract(const Duration(seconds: 6)),
);
final projector = _projector(repository, _clock(now));
final projection = await projector.project(revision: 1);
expect(projection.phase, WatchSessionPhase.running);
expect(projection.dominantTimer?.kind, WatchTimerKind.step);
expect(projection.dominantTimer?.accumulatedMs, 5000);
expect(
projection.dominantTimer?.startedAtEpochMs,
now.millisecondsSinceEpoch,
);
expect(projection.secondaryTimers.map((timer) => timer.kind), [
WatchTimerKind.scoreStopwatch,
WatchTimerKind.setTimer,
]);
expect(projection.primaryAction, WatchPrimaryAction.pauseSession);
expect(
projection.secondaryActions,
contains(WatchSecondaryAction.finishCurrentSet),
);
},
);
test('projects manual score state for the current set', () async {
final session = _session(scoreEnabled: true, targetScore: 8);
final repository = _FakeActiveSessionRepository()
..session = session
..manualScoreStates['manual-score'] = ActiveManualScoreState(
metadata: _metadata('manual-score'),
activeWorkoutSessionId: session.metadata.id,
programIndex: 0,
exerciseIndex: 0,
setIndex: 0,
value: 3,
updatedAt: DateTime.utc(2026, 7, 25, 12),
);
final projector = _projector(repository, _clock());
final projection = await projector.project(revision: 1);
expect(projection.hasManualScore, isTrue);
expect(projection.currentManualScoreValue, 3);
expect(projection.canDecrementScore, isTrue);
expect(projection.manualScoreTargetValue, 8);
expect(projection.manualScoreTargetLabel, 'Cible');
expect(projection.manualScoreScope, WatchManualScoreScope.series);
});
test(
'projects independent current step manual score before series score',
() async {
final session = _session(
scoreEnabled: true,
targetScore: 8,
steps: [
_step(
hasScore: true,
scoreLabel: 'Réussites',
scoreUnit: 'pts',
defaultTargetScore: 5,
),
],
);
final repository = _FakeActiveSessionRepository()
..session = session
..stepProgressStates['step-state'] = _stepState(
sessionId: session.metadata.id,
status: ActiveExerciseStepProgressStatus.waitingManual,
)
..stepResults.add(
ActiveExerciseStepResult(
metadata: _metadata('step-score'),
activeWorkoutSessionId: session.metadata.id,
programSnapshotId: 'program-snapshot-1',
exerciseSnapshotId: 'exercise-snapshot-1',
programIndex: 0,
exerciseIndex: 0,
setIndex: 0,
passageIndex: 0,
stepIndex: 0,
stepSnapshotId: 'step-1',
stepNameSnapshot: 'Step 1',
stepTypeSnapshot: ExerciseStepType.time,
targetValueSnapshot: 1,
hasScoreSnapshot: true,
scoreInputModeSnapshot: ScoreInputMode.manual,
scoreLabelSnapshot: 'Réussites',
scoreUnitSnapshot: 'pts',
targetScoreSnapshot: 5,
status: SetResultStatus.completed,
actualScore: 2,
),
);
final projector = _projector(repository, _clock());
final projection = await projector.project(revision: 1);
expect(projection.hasManualScore, isTrue);
expect(projection.currentManualScoreValue, 2);
expect(projection.manualScoreTargetValue, 5);
expect(projection.manualScoreScope, WatchManualScoreScope.step);
},
);
test('does not project manual score controls for stopwatch score', () async {
final repository = _FakeActiveSessionRepository()
..session = _session(
scoreEnabled: true,
scoreInputMode: ScoreInputMode.stopwatch,
);
final projector = _projector(repository, _clock());
final projection = await projector.project(revision: 1);
expect(projection.hasManualScore, isFalse);
expect(projection.currentManualScoreValue, isNull);
expect(projection.canDecrementScore, isFalse);
expect(projection.manualScoreTargetValue, isNull);
});
test('projects paused after a running session is paused', () async {
final now = DateTime.utc(2026, 7, 25, 12);
final session = _session(
status: ActiveWorkoutStatus.paused,
pausedAt: now,
timeEnabled: true,
steps: [_step()],
);
final repository = _FakeActiveSessionRepository()
..session = session
..stepProgressStates['step-state'] = _stepState(
sessionId: session.metadata.id,
status: ActiveExerciseStepProgressStatus.pausedTimer,
accumulatedMs: 5000,
lastTransitionAt: now,
);
final projector = _projector(repository, _clock(now));
final projection = await projector.project(revision: 2);
expect(projection.phase, WatchSessionPhase.paused);
expect(projection.primaryAction, WatchPrimaryAction.resumeSession);
expect(projection.statusLabel, 'Séance en pause');
expect(projection.dominantTimer?.runState, WatchTimerRunState.paused);
});
test(
'projects nextTimerReady after an elapsed timer with chaining disabled',
() async {
final now = DateTime.utc(2026, 7, 25, 12);
final session = _session(
autoStartNextTimedStepSnapshot: false,
steps: [
_step(id: 'step-1'),
_step(id: 'step-2', position: 1),
],
);
final repository = _FakeActiveSessionRepository()
..session = session
..stepProgressStates['step-state'] = _stepState(
sessionId: session.metadata.id,
stepId: 'step-1',
status: ActiveExerciseStepProgressStatus.runningTimer,
startedAt: now.subtract(const Duration(milliseconds: 1500)),
);
final projector = _projector(repository, _clock(now));
final projection = await projector.project(revision: 3);
expect(projection.phase, WatchSessionPhase.nextTimerReady);
expect(projection.stepIndex, 2);
expect(projection.stepName, 'Step 2');
expect(projection.statusLabel, 'Chrono suivant prêt');
expect(
projection.primaryAction,
WatchPrimaryAction.startPreparedTimedStep,
);
expect(projection.dominantTimer?.runState, WatchTimerRunState.stopped);
expect(repository.stepResults, hasLength(1));
},
);
test('projects restRunning after finishing a set with rest', () async {
final now = DateTime.utc(2026, 7, 25, 12);
final session = _session(currentSetIndex: 1);
final repository = _FakeActiveSessionRepository()
..session = session
..restStates['rest'] = ActiveRestState(
metadata: _metadata('rest'),
activeWorkoutSessionId: session.metadata.id,
afterProgramIndex: 0,
afterExerciseIndex: 0,
afterSetIndex: 0,
plannedRestSeconds: 60,
adjustedRestSeconds: 60,
startedAt: now.subtract(const Duration(seconds: 10)),
);
final projector = _projector(repository, _clock(now));
final projection = await projector.project(revision: 4);
expect(projection.phase, WatchSessionPhase.restRunning);
expect(projection.primaryAction, WatchPrimaryAction.pauseSession);
expect(projection.secondaryActions, [WatchSecondaryAction.skipCurrentRest]);
expect(projection.dominantTimer?.kind, WatchTimerKind.rest);
expect(projection.dominantTimer?.targetMs, 60000);
expect(projection.dominantTimer?.accumulatedMs, 10000);
expect(projection.nextExerciseName, 'Squat');
});
test(
'projects restRunning next exercise when rest precedes another exercise',
() async {
final now = DateTime.utc(2026, 7, 25, 12);
final session = _session(
currentExerciseIndex: 1,
currentSetIndex: 0,
secondExerciseName: 'Fentes',
);
final repository = _FakeActiveSessionRepository()
..session = session
..restStates['rest'] = ActiveRestState(
metadata: _metadata('rest'),
activeWorkoutSessionId: session.metadata.id,
afterProgramIndex: 0,
afterExerciseIndex: 0,
afterSetIndex: 1,
plannedRestSeconds: 60,
adjustedRestSeconds: 60,
startedAt: now.subtract(const Duration(seconds: 10)),
);
final projector = _projector(repository, _clock(now));
final projection = await projector.project(revision: 4);
expect(projection.phase, WatchSessionPhase.restRunning);
expect(projection.exerciseName, 'Fentes');
expect(projection.nextExerciseName, 'Fentes');
},
);
test('projects restPaused', () async {
final now = DateTime.utc(2026, 7, 25, 12);
final session = _session(status: ActiveWorkoutStatus.paused, pausedAt: now);
final repository = _FakeActiveSessionRepository()
..session = session
..restStates['rest'] = ActiveRestState(
metadata: _metadata('rest'),
activeWorkoutSessionId: session.metadata.id,
afterProgramIndex: 0,
afterExerciseIndex: 0,
afterSetIndex: 0,
plannedRestSeconds: 60,
adjustedRestSeconds: 60,
startedAt: now.subtract(const Duration(seconds: 15)),
pausedAt: now,
);
final projector = _projector(repository, _clock(now));
final projection = await projector.project(revision: 5);
expect(projection.phase, WatchSessionPhase.restPaused);
expect(projection.primaryAction, WatchPrimaryAction.resumeSession);
expect(projection.dominantTimer?.runState, WatchTimerRunState.paused);
});
test(
'projects betweenSetsReady after rest ends before the next set',
() async {
final now = DateTime.utc(2026, 7, 25, 12);
final session = _session(currentSetIndex: 1);
final repository = _FakeActiveSessionRepository()
..session = session
..restStates['rest'] = ActiveRestState(
metadata: _metadata('rest'),
activeWorkoutSessionId: session.metadata.id,
afterProgramIndex: 0,
afterExerciseIndex: 0,
afterSetIndex: 0,
plannedRestSeconds: 60,
adjustedRestSeconds: 60,
startedAt: now.subtract(const Duration(seconds: 60)),
endedAt: now,
);
final projector = _projector(repository, _clock(now));
final projection = await projector.project(revision: 6);
expect(projection.phase, WatchSessionPhase.betweenSetsReady);
expect(projection.statusLabel, 'Prêt pour la série suivante');
expect(projection.primaryAction, WatchPrimaryAction.startCurrentExercise);
},
);
test(
'projects nextExerciseName between sets when exercise changes',
() async {
final session = _session(
currentExerciseIndex: 1,
currentSetIndex: 0,
secondExerciseName: 'Fentes',
);
final repository = _FakeActiveSessionRepository()..session = session;
final projector = _projector(repository, _clock());
final projection = await projector.project(revision: 7);
expect(projection.phase, WatchSessionPhase.betweenSetsReady);
expect(projection.exerciseName, 'Fentes');
expect(projection.nextExerciseName, 'Fentes');
},
);
test(
'keeps revision stable when refresh only changes volatile timing fields',
() async {
final now = DateTime.utc(2026, 7, 25, 12);
final clock = _clock(now);
final session = _session(steps: [_step(defaultTargetValue: 30)]);
final repository = _FakeActiveSessionRepository()
..session = session
..stepProgressStates['step-state'] = _stepState(
sessionId: session.metadata.id,
status: ActiveExerciseStepProgressStatus.runningTimer,
startedAt: now,
);
final publisher = _FakeWatchProjectionPublisher();
final useCases = WatchCompanionProjectionUseCases(
sessionRepository: repository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
publisher: publisher,
);
final emitted = <WatchSessionProjection>[];
final subscription = useCases.projections.listen(emitted.add);
final first = await useCases.emitCurrentProjection();
clock.value = now.add(const Duration(seconds: 2));
final second = await useCases.emitCurrentProjection();
await Future<void>.delayed(Duration.zero);
expect(first.revision, 1);
expect(second.revision, 1);
expect(second.projectedAtEpochMs, greaterThan(first.projectedAtEpochMs));
expect(
second.dominantTimer?.accumulatedMs,
greaterThan(first.dominantTimer?.accumulatedMs ?? 0),
);
expect(emitted.map((projection) => projection.revision), [1, 1]);
expect(publisher.published.map((projection) => projection.revision), [
1,
1,
]);
await subscription.cancel();
await useCases.dispose();
},
);
test('increments revision when session command state changes', () async {
final repository = _FakeActiveSessionRepository()
..session = _session(setsCount: 2);
final publisher = _FakeWatchProjectionPublisher();
final useCases = WatchCompanionProjectionUseCases(
sessionRepository: repository,
clock: _clock(),
ids: _FakeIds(),
originDeviceId: 'device-1',
publisher: publisher,
);
final first = await useCases.emitCurrentProjection();
repository.session = repository.session!.copyWith(currentSetIndex: 1);
final second = await useCases.emitCurrentProjection();
expect(first.revision, 1);
expect(second.revision, 2);
expect(second.seriesIndex, 2);
expect(publisher.published.map((projection) => projection.revision), [
1,
2,
]);
await useCases.dispose();
});
}
WatchSessionProjectionProjector _projector(
_FakeActiveSessionRepository repository,
_FakeClock clock,
) {
return WatchSessionProjectionProjector(
sessionRepository: repository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
);
}
_FakeClock _clock([DateTime? now]) {
return _FakeClock(now ?? DateTime.utc(2026, 7, 25, 12));
}
ActiveWorkoutSession _session({
ActiveWorkoutStatus status = ActiveWorkoutStatus.running,
DateTime? pausedAt,
int currentExerciseIndex = 0,
int currentSetIndex = 0,
int setsCount = 2,
bool timeEnabled = false,
bool repsEnabled = true,
bool scoreEnabled = false,
int? targetTimeSeconds,
double? targetScore,
ScoreInputMode scoreInputMode = ScoreInputMode.manual,
bool? autoStartNextTimedStepSnapshot = true,
List<ExerciseStep> steps = const [],
String? secondExerciseName,
}) {
final exerciseSnapshot = {
'id': 'exercise-snapshot-1',
'exerciseNameSnapshot': 'Squat',
'setsCount': setsCount,
'timeEnabled': timeEnabled,
'repsEnabled': repsEnabled,
'scoreEnabled': scoreEnabled,
'targetTimeSeconds': targetTimeSeconds,
'targetReps': repsEnabled ? setsCount : null,
'targetScore': targetScore,
'scoreInputModeSnapshot': scoreInputMode.name,
'exerciseStepsSnapshot': steps
.map((step) => step.toSnapshotJson())
.toList(),
'autoStartNextTimedStepSnapshot': ?autoStartNextTimedStepSnapshot,
};
final secondExerciseSnapshot = secondExerciseName == null
? null
: {
'id': 'exercise-snapshot-2',
'exerciseNameSnapshot': secondExerciseName,
'setsCount': 1,
'timeEnabled': false,
'repsEnabled': true,
'scoreEnabled': false,
'targetReps': 1,
'scoreInputModeSnapshot': ScoreInputMode.manual.name,
'exerciseStepsSnapshot': const [],
'autoStartNextTimedStepSnapshot': true,
};
return ActiveWorkoutSession(
metadata: _metadata('session-1'),
status: status,
startedAt: DateTime.utc(2026, 7, 25, 12),
pausedAt: pausedAt,
lastPersistedAt: DateTime.utc(2026, 7, 25, 12),
elapsedActiveMs: 0,
currentProgramIndex: 0,
currentExerciseIndex: currentExerciseIndex,
currentSetIndex: currentSetIndex,
resolvedTemplateSnapshotJson: jsonEncode({
'programs': [
{
'id': 'program-snapshot-1',
'programNameSnapshot': 'Programme',
'programSnapshotJson': jsonEncode({
'exercises': [exerciseSnapshot, ?secondExerciseSnapshot],
}),
},
],
}),
);
}
ExerciseStep _step({
String id = 'step-1',
int position = 0,
int defaultTargetValue = 1,
bool hasScore = false,
String? scoreLabel,
String? scoreUnit,
double? defaultTargetScore,
}) {
return ExerciseStep(
id: id,
position: position,
name: 'Step ${position + 1}',
type: ExerciseStepType.time,
defaultTargetValue: defaultTargetValue,
hasScore: hasScore,
scoreLabel: scoreLabel,
scoreUnit: scoreUnit,
defaultTargetScore: defaultTargetScore,
);
}
ActiveExerciseStepProgressState _stepState({
required String sessionId,
String stepId = 'step-1',
int stepIndex = 0,
ActiveExerciseStepProgressStatus status =
ActiveExerciseStepProgressStatus.stoppedTimer,
DateTime? startedAt,
int accumulatedMs = 0,
DateTime? lastTransitionAt,
}) {
return ActiveExerciseStepProgressState(
metadata: _metadata('step-state'),
activeWorkoutSessionId: sessionId,
programIndex: 0,
exerciseIndex: 0,
setIndex: 0,
currentPassageIndex: 0,
currentStepIndex: stepIndex,
currentStepSnapshotId: stepId,
status: status,
startedAt: startedAt,
accumulatedMs: accumulatedMs,
lastTransitionAt: lastTransitionAt ?? DateTime.utc(2026, 7, 25, 12),
);
}
ActiveScoreStopwatchState _scoreStopwatch({
required String sessionId,
required DateTime startedAt,
}) {
return ActiveScoreStopwatchState(
metadata: _metadata('score'),
activeWorkoutSessionId: sessionId,
programIndex: 0,
exerciseIndex: 0,
setIndex: 0,
status: ActiveScoreStopwatchStatus.running,
startedAt: startedAt,
accumulatedMs: 0,
);
}
ActiveSetTimerState _setTimer({
required String sessionId,
required DateTime startedAt,
}) {
return ActiveSetTimerState(
metadata: _metadata('set'),
activeWorkoutSessionId: sessionId,
programIndex: 0,
exerciseIndex: 0,
setIndex: 0,
status: ActiveSetTimerStatus.running,
startedAt: startedAt,
accumulatedMs: 0,
);
}
EntityMetadata _metadata(String id) {
return EntityMetadata(
id: id,
createdAt: DateTime.utc(2026, 7, 25, 12),
updatedAt: DateTime.utc(2026, 7, 25, 12),
originDeviceId: 'device-1',
);
}
final class _FakeWatchProjectionPublisher implements WatchProjectionPublisher {
final published = <WatchSessionProjection>[];
@override
Future<void> publish(WatchSessionProjection projection) async {
published.add(projection);
}
}
final class _FakeClock implements Clock {
_FakeClock(this.value);
DateTime value;
@override
DateTime now() => value;
}
final class _FakeIds implements IdGenerator {
var _next = 0;
@override
String newId() {
_next += 1;
return 'id-$_next';
}
}
final class _FakeActiveSessionRepository implements ActiveSessionRepository {
ActiveWorkoutSession? session;
final results = <ActiveSetResult>[];
final restStates = <String, ActiveRestState>{};
final setTimerStates = <String, ActiveSetTimerState>{};
final scoreStopwatchStates = <String, ActiveScoreStopwatchState>{};
final manualScoreStates = <String, ActiveManualScoreState>{};
final stepProgressStates = <String, ActiveExerciseStepProgressState>{};
final stepResults = <ActiveExerciseStepResult>[];
@override
Future<void> deleteScoreStopwatchState({
required String sessionId,
required int programIndex,
required int exerciseIndex,
required int setIndex,
required DateTime deletedAt,
}) async {
scoreStopwatchStates.clear();
}
@override
Future<void> deleteManualScoreState({
required String sessionId,
required int programIndex,
required int exerciseIndex,
required int setIndex,
required DateTime deletedAt,
}) async {
manualScoreStates.clear();
}
@override
Future<ActiveWorkoutSession?> findById(String id) async {
return session?.metadata.id == id ? session : null;
}
@override
Future<ActiveWorkoutSession?> findOpen() async => session;
@override
Future<ActiveExerciseStepProgressState?> findExerciseStepProgressState({
required String sessionId,
required int programIndex,
required int exerciseIndex,
required int setIndex,
}) async {
return stepProgressStates.values.where((state) {
return state.activeWorkoutSessionId == sessionId &&
state.programIndex == programIndex &&
state.exerciseIndex == exerciseIndex &&
state.setIndex == setIndex;
}).firstOrNull;
}
@override
Future<ActiveRestState?> findRestStateById(String id) async {
return restStates[id];
}
@override
Future<ActiveScoreStopwatchState?> findScoreStopwatchState({
required String sessionId,
required int programIndex,
required int exerciseIndex,
required int setIndex,
}) async {
return scoreStopwatchStates.values.where((state) {
return state.activeWorkoutSessionId == sessionId &&
state.programIndex == programIndex &&
state.exerciseIndex == exerciseIndex &&
state.setIndex == setIndex;
}).firstOrNull;
}
@override
Future<ActiveManualScoreState?> findManualScoreState({
required String sessionId,
required int programIndex,
required int exerciseIndex,
required int setIndex,
}) async {
return manualScoreStates.values.where((state) {
return state.activeWorkoutSessionId == sessionId &&
state.programIndex == programIndex &&
state.exerciseIndex == exerciseIndex &&
state.setIndex == setIndex;
}).firstOrNull;
}
@override
Future<ActiveSetTimerState?> findSetTimerState({
required String sessionId,
required int programIndex,
required int exerciseIndex,
required int setIndex,
}) async {
return setTimerStates.values.where((state) {
return state.activeWorkoutSessionId == sessionId &&
state.programIndex == programIndex &&
state.exerciseIndex == exerciseIndex &&
state.setIndex == setIndex;
}).firstOrNull;
}
@override
Future<List<ActiveExerciseStepProgressState>> listExerciseStepProgressStates(
String sessionId,
) async {
return stepProgressStates.values
.where((state) => state.activeWorkoutSessionId == sessionId)
.toList();
}
@override
Future<List<ActiveExerciseStepResult>> listExerciseStepResults(
String sessionId,
) async {
return stepResults
.where((result) => result.activeWorkoutSessionId == sessionId)
.toList();
}
@override
Future<List<ActiveRestState>> listRestStates(String sessionId) async {
return restStates.values
.where((state) => state.activeWorkoutSessionId == sessionId)
.toList();
}
@override
Future<List<ActiveScoreStopwatchState>> listScoreStopwatchStates(
String sessionId,
) async {
return scoreStopwatchStates.values
.where((state) => state.activeWorkoutSessionId == sessionId)
.toList();
}
@override
Future<List<ActiveManualScoreState>> listManualScoreStates(
String sessionId,
) async {
return manualScoreStates.values
.where((state) => state.activeWorkoutSessionId == sessionId)
.toList();
}
@override
Future<List<ActiveSetResult>> listSetResults(String sessionId) async {
return results
.where((result) => result.activeWorkoutSessionId == sessionId)
.toList();
}
@override
Future<List<ActiveSetTimerState>> listSetTimerStates(String sessionId) async {
return setTimerStates.values
.where((state) => state.activeWorkoutSessionId == sessionId)
.toList();
}
@override
Future<void> save(ActiveWorkoutSession session) async {
this.session = session;
}
@override
Future<void> saveExerciseStepProgressState(
ActiveExerciseStepProgressState state,
) async {
stepProgressStates[state.metadata.id] = state;
}
@override
Future<void> saveExerciseStepResult(ActiveExerciseStepResult result) async {
stepResults.add(result);
}
@override
Future<void> saveRestState(ActiveRestState restState) async {
restStates[restState.metadata.id] = restState;
}
@override
Future<void> saveScoreStopwatchState(ActiveScoreStopwatchState state) async {
scoreStopwatchStates[state.metadata.id] = state;
}
@override
Future<void> saveManualScoreState(ActiveManualScoreState state) async {
manualScoreStates[state.metadata.id] = state;
}
@override
Future<void> saveSetResult(ActiveSetResult result) async {
results.add(result);
}
@override
Future<void> saveSetTimerState(ActiveSetTimerState state) async {
setTimerStates[state.metadata.id] = state;
}
}