feat(watch): phone session projection to watch (#91-B)
This commit is contained in:
673
test/application/watch_companion_projection_test.dart
Normal file
673
test/application/watch_companion_projection_test.dart
Normal file
@ -0,0 +1,673 @@
|
||||
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, 0);
|
||||
expect(
|
||||
projection.dominantTimer?.startedAtEpochMs,
|
||||
now.subtract(const Duration(seconds: 5)).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 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(
|
||||
'emits projections through stream and publisher with incremented revision',
|
||||
() async {
|
||||
final repository = _FakeActiveSessionRepository()
|
||||
..session = _session(steps: [_step()]);
|
||||
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();
|
||||
final second = await useCases.emitCurrentProjection();
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(first.revision, 1);
|
||||
expect(second.revision, 2);
|
||||
expect(emitted.map((projection) => projection.revision), [1, 2]);
|
||||
expect(publisher.published.map((projection) => projection.revision), [
|
||||
1,
|
||||
2,
|
||||
]);
|
||||
|
||||
await subscription.cancel();
|
||||
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,
|
||||
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,
|
||||
'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,
|
||||
}) {
|
||||
return ExerciseStep(
|
||||
id: id,
|
||||
position: position,
|
||||
name: 'Step ${position + 1}',
|
||||
type: ExerciseStepType.time,
|
||||
defaultTargetValue: defaultTargetValue,
|
||||
);
|
||||
}
|
||||
|
||||
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 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<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<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<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> saveSetResult(ActiveSetResult result) async {
|
||||
results.add(result);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveSetTimerState(ActiveSetTimerState state) async {
|
||||
setTimerStates[state.metadata.id] = state;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user