feat(watch): route watch commands to existing session use cases (#91-C)
This commit is contained in:
@ -32,6 +32,7 @@ final class AppBootstrap implements AppDependencies {
|
||||
required this.activeWorkoutSessionUseCases,
|
||||
required this.activeExerciseStepUseCases,
|
||||
required this.watchCompanionProjectionUseCases,
|
||||
required this.watchCompanionCommandHandler,
|
||||
required this.closeWorkoutSessionUseCase,
|
||||
required this.workoutHistoryUseCases,
|
||||
required this.progressionStatsUseCase,
|
||||
@ -59,6 +60,7 @@ final class AppBootstrap implements AppDependencies {
|
||||
@override
|
||||
final ActiveExerciseStepUseCases activeExerciseStepUseCases;
|
||||
final WatchCompanionProjectionUseCases watchCompanionProjectionUseCases;
|
||||
final WatchCompanionCommandHandler watchCompanionCommandHandler;
|
||||
@override
|
||||
final CloseWorkoutSessionUseCase closeWorkoutSessionUseCase;
|
||||
@override
|
||||
@ -105,6 +107,25 @@ final class AppBootstrap implements AppDependencies {
|
||||
final ids = LocalIdGenerator();
|
||||
const clock = SystemClock();
|
||||
const originDeviceId = 'local-device';
|
||||
final activeWorkoutSessionUseCases = ActiveWorkoutSessionUseCases(
|
||||
sessionRepository: activeSessionRepository,
|
||||
templateRepository: templateRepository,
|
||||
clock: clock,
|
||||
ids: ids,
|
||||
originDeviceId: originDeviceId,
|
||||
);
|
||||
final activeExerciseStepUseCases = ActiveExerciseStepUseCases(
|
||||
sessionRepository: activeSessionRepository,
|
||||
clock: clock,
|
||||
ids: ids,
|
||||
originDeviceId: originDeviceId,
|
||||
);
|
||||
final watchCompanionProjectionUseCases = WatchCompanionProjectionUseCases(
|
||||
sessionRepository: activeSessionRepository,
|
||||
clock: clock,
|
||||
ids: ids,
|
||||
originDeviceId: originDeviceId,
|
||||
);
|
||||
await SeedStarterContentUseCase(
|
||||
seedStateRepository: starterSeedRepository,
|
||||
contentRepository: starterSeedRepository,
|
||||
@ -160,24 +181,14 @@ final class AppBootstrap implements AppDependencies {
|
||||
ids: ids,
|
||||
originDeviceId: originDeviceId,
|
||||
),
|
||||
activeWorkoutSessionUseCases: ActiveWorkoutSessionUseCases(
|
||||
activeWorkoutSessionUseCases: activeWorkoutSessionUseCases,
|
||||
activeExerciseStepUseCases: activeExerciseStepUseCases,
|
||||
watchCompanionProjectionUseCases: watchCompanionProjectionUseCases,
|
||||
watchCompanionCommandHandler: WatchCompanionCommandHandler(
|
||||
sessionRepository: activeSessionRepository,
|
||||
templateRepository: templateRepository,
|
||||
clock: clock,
|
||||
ids: ids,
|
||||
originDeviceId: originDeviceId,
|
||||
),
|
||||
activeExerciseStepUseCases: ActiveExerciseStepUseCases(
|
||||
sessionRepository: activeSessionRepository,
|
||||
clock: clock,
|
||||
ids: ids,
|
||||
originDeviceId: originDeviceId,
|
||||
),
|
||||
watchCompanionProjectionUseCases: WatchCompanionProjectionUseCases(
|
||||
sessionRepository: activeSessionRepository,
|
||||
clock: clock,
|
||||
ids: ids,
|
||||
originDeviceId: originDeviceId,
|
||||
activeSessionUseCases: activeWorkoutSessionUseCases,
|
||||
stepUseCases: activeExerciseStepUseCases,
|
||||
projectionSource: watchCompanionProjectionUseCases,
|
||||
),
|
||||
closeWorkoutSessionUseCase: CloseWorkoutSessionUseCase(
|
||||
sessionRepository: activeSessionRepository,
|
||||
|
||||
@ -3134,6 +3134,358 @@ final class WatchCompanionProjectionUseCases implements WatchProjectionSource {
|
||||
}
|
||||
}
|
||||
|
||||
final class WatchCompanionCommandHandler implements WatchCommandIngress {
|
||||
WatchCompanionCommandHandler({
|
||||
required ActiveSessionRepository sessionRepository,
|
||||
required ActiveWorkoutSessionUseCases activeSessionUseCases,
|
||||
required ActiveExerciseStepUseCases stepUseCases,
|
||||
required WatchProjectionSource projectionSource,
|
||||
}) : _sessionRepository = sessionRepository,
|
||||
_activeSessionUseCases = activeSessionUseCases,
|
||||
_stepUseCases = stepUseCases,
|
||||
_projectionSource = projectionSource;
|
||||
|
||||
final ActiveSessionRepository _sessionRepository;
|
||||
final ActiveWorkoutSessionUseCases _activeSessionUseCases;
|
||||
final ActiveExerciseStepUseCases _stepUseCases;
|
||||
final WatchProjectionSource _projectionSource;
|
||||
final _handledCommands = <_WatchCommandKey, WatchCommandAck>{};
|
||||
Future<void> _tail = Future<void>.value();
|
||||
|
||||
@override
|
||||
Future<WatchCommandAck> dispatch(WatchCommandEnvelope command) {
|
||||
final run = _tail.then(
|
||||
(_) => _dispatch(command),
|
||||
onError: (_) => _dispatch(command),
|
||||
);
|
||||
_tail = run.then((_) {}, onError: (_) {});
|
||||
return run;
|
||||
}
|
||||
|
||||
Future<WatchCommandAck> _dispatch(WatchCommandEnvelope command) async {
|
||||
final key = _WatchCommandKey(command);
|
||||
final previousAck = _handledCommands[key];
|
||||
if (previousAck == WatchCommandAck.accepted ||
|
||||
previousAck == WatchCommandAck.acceptedNoOp) {
|
||||
return WatchCommandAck.acceptedNoOp;
|
||||
}
|
||||
|
||||
try {
|
||||
final projection = await _projectionSource.currentProjection();
|
||||
if (projection.phase == WatchSessionPhase.noActiveSession ||
|
||||
projection.deviceSessionId.isEmpty) {
|
||||
return WatchCommandAck.rejectedNoActiveSession;
|
||||
}
|
||||
if (command.sessionId != projection.deviceSessionId) {
|
||||
return WatchCommandAck.rejectedSessionMismatch;
|
||||
}
|
||||
if (command.expectedRevision != projection.revision) {
|
||||
return WatchCommandAck.rejectedStaleRevision;
|
||||
}
|
||||
if (!_isApplicable(command.type, projection)) {
|
||||
return WatchCommandAck.rejectedNotApplicable;
|
||||
}
|
||||
|
||||
final session = await _sessionRepository.findOpen();
|
||||
if (session == null ||
|
||||
session.status == ActiveWorkoutStatus.completed ||
|
||||
session.status == ActiveWorkoutStatus.abandoned ||
|
||||
session.status == ActiveWorkoutStatus.savedExit) {
|
||||
return WatchCommandAck.rejectedNoActiveSession;
|
||||
}
|
||||
if (session.metadata.id != command.sessionId) {
|
||||
return WatchCommandAck.rejectedSessionMismatch;
|
||||
}
|
||||
|
||||
final ack = await _route(command.type, session);
|
||||
if (ack == WatchCommandAck.accepted ||
|
||||
ack == WatchCommandAck.acceptedNoOp) {
|
||||
_handledCommands[key] = ack;
|
||||
}
|
||||
if (ack == WatchCommandAck.accepted) {
|
||||
await _emitProjectionAfterCommand();
|
||||
}
|
||||
return ack;
|
||||
} on DomainException {
|
||||
return WatchCommandAck.rejectedNotApplicable;
|
||||
} on StateError {
|
||||
return WatchCommandAck.rejectedNotApplicable;
|
||||
} on Exception {
|
||||
return WatchCommandAck.rejectedPhoneBusy;
|
||||
}
|
||||
}
|
||||
|
||||
bool _isApplicable(WatchCommandType type, WatchSessionProjection projection) {
|
||||
return switch (type) {
|
||||
WatchCommandType.startCurrentExercise =>
|
||||
projection.primaryAction == WatchPrimaryAction.startCurrentExercise,
|
||||
WatchCommandType.pauseSession =>
|
||||
projection.primaryAction == WatchPrimaryAction.pauseSession,
|
||||
WatchCommandType.resumeSession =>
|
||||
projection.primaryAction == WatchPrimaryAction.resumeSession,
|
||||
WatchCommandType.startPreparedTimedStep =>
|
||||
projection.primaryAction == WatchPrimaryAction.startPreparedTimedStep,
|
||||
WatchCommandType.skipCurrentStep => projection.secondaryActions.contains(
|
||||
WatchSecondaryAction.skipCurrentStep,
|
||||
),
|
||||
WatchCommandType.skipCurrentPassage =>
|
||||
projection.secondaryActions.contains(
|
||||
WatchSecondaryAction.skipCurrentPassage,
|
||||
),
|
||||
WatchCommandType.finishCurrentSet => projection.secondaryActions.contains(
|
||||
WatchSecondaryAction.finishCurrentSet,
|
||||
),
|
||||
WatchCommandType.skipCurrentSet => projection.secondaryActions.contains(
|
||||
WatchSecondaryAction.skipCurrentSet,
|
||||
),
|
||||
WatchCommandType.skipCurrentRest =>
|
||||
projection.primaryAction == WatchPrimaryAction.skipCurrentRest ||
|
||||
projection.secondaryActions.contains(
|
||||
WatchSecondaryAction.skipCurrentRest,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
Future<WatchCommandAck> _route(
|
||||
WatchCommandType type,
|
||||
ActiveWorkoutSession session,
|
||||
) {
|
||||
return switch (type) {
|
||||
WatchCommandType.startCurrentExercise => _startCurrentExercise(session),
|
||||
WatchCommandType.pauseSession => _pause(session),
|
||||
WatchCommandType.resumeSession => _resume(session),
|
||||
WatchCommandType.startPreparedTimedStep => _startPreparedTimedStep(
|
||||
session,
|
||||
),
|
||||
WatchCommandType.skipCurrentStep => _skipCurrentStep(session),
|
||||
WatchCommandType.skipCurrentPassage => _skipCurrentPassage(session),
|
||||
WatchCommandType.finishCurrentSet => _finishCurrentSet(
|
||||
session,
|
||||
skipped: false,
|
||||
),
|
||||
WatchCommandType.skipCurrentSet => _finishCurrentSet(
|
||||
session,
|
||||
skipped: true,
|
||||
),
|
||||
WatchCommandType.skipCurrentRest => _skipCurrentRest(session),
|
||||
};
|
||||
}
|
||||
|
||||
Future<WatchCommandAck> _startCurrentExercise(
|
||||
ActiveWorkoutSession session,
|
||||
) async {
|
||||
final result = await _activeSessionUseCases.startCurrentExerciseTimers(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: session.currentProgramIndex,
|
||||
exerciseIndex: session.currentExerciseIndex,
|
||||
setIndex: session.currentSetIndex,
|
||||
);
|
||||
final changed =
|
||||
result.setTimer != null ||
|
||||
result.scoreStopwatch != null ||
|
||||
result.stepProgress != null;
|
||||
return changed ? WatchCommandAck.accepted : WatchCommandAck.acceptedNoOp;
|
||||
}
|
||||
|
||||
Future<WatchCommandAck> _pause(ActiveWorkoutSession session) async {
|
||||
await _activeSessionUseCases.pause(session.metadata.id);
|
||||
return WatchCommandAck.accepted;
|
||||
}
|
||||
|
||||
Future<WatchCommandAck> _resume(ActiveWorkoutSession session) async {
|
||||
await _activeSessionUseCases.resume(session.metadata.id);
|
||||
return WatchCommandAck.accepted;
|
||||
}
|
||||
|
||||
Future<WatchCommandAck> _startPreparedTimedStep(
|
||||
ActiveWorkoutSession session,
|
||||
) async {
|
||||
await _stepUseCases.startTimer(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: session.currentProgramIndex,
|
||||
exerciseIndex: session.currentExerciseIndex,
|
||||
setIndex: session.currentSetIndex,
|
||||
);
|
||||
return WatchCommandAck.accepted;
|
||||
}
|
||||
|
||||
Future<WatchCommandAck> _skipCurrentStep(ActiveWorkoutSession session) async {
|
||||
await _stepUseCases.skipCurrentStep(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: session.currentProgramIndex,
|
||||
exerciseIndex: session.currentExerciseIndex,
|
||||
setIndex: session.currentSetIndex,
|
||||
);
|
||||
return WatchCommandAck.accepted;
|
||||
}
|
||||
|
||||
Future<WatchCommandAck> _skipCurrentPassage(
|
||||
ActiveWorkoutSession session,
|
||||
) async {
|
||||
await _stepUseCases.skipCurrentPassage(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: session.currentProgramIndex,
|
||||
exerciseIndex: session.currentExerciseIndex,
|
||||
setIndex: session.currentSetIndex,
|
||||
);
|
||||
return WatchCommandAck.accepted;
|
||||
}
|
||||
|
||||
Future<WatchCommandAck> _finishCurrentSet(
|
||||
ActiveWorkoutSession session, {
|
||||
required bool skipped,
|
||||
}) async {
|
||||
final snapshot = _findExerciseSnapshot(
|
||||
resolvedTemplateSnapshotJson: session.resolvedTemplateSnapshotJson,
|
||||
programIndex: session.currentProgramIndex,
|
||||
exerciseIndex: session.currentExerciseIndex,
|
||||
);
|
||||
if (snapshot == null) {
|
||||
return WatchCommandAck.rejectedNotApplicable;
|
||||
}
|
||||
final setTimer = skipped
|
||||
? await _activeSessionUseCases.skipSetExecutionTimers(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: session.currentProgramIndex,
|
||||
exerciseIndex: session.currentExerciseIndex,
|
||||
setIndex: session.currentSetIndex,
|
||||
)
|
||||
: await _activeSessionUseCases.stopSetExecutionTimers(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: session.currentProgramIndex,
|
||||
exerciseIndex: session.currentExerciseIndex,
|
||||
setIndex: session.currentSetIndex,
|
||||
);
|
||||
final actualScoreTimeMs = skipped
|
||||
? null
|
||||
: await _scoreStopwatchMsIfNeeded(session, snapshot);
|
||||
await _activeSessionUseCases.recordCurrentSetResult(
|
||||
sessionId: session.metadata.id,
|
||||
programSnapshotId: snapshot.programSnapshotId,
|
||||
exerciseSnapshotId: snapshot.exerciseSnapshotId,
|
||||
programIndex: session.currentProgramIndex,
|
||||
exerciseIndex: session.currentExerciseIndex,
|
||||
setIndex: session.currentSetIndex,
|
||||
actualTimeMs: skipped || !snapshot.timeEnabled
|
||||
? null
|
||||
: setTimer?.accumulatedMs,
|
||||
actualReps: skipped || !snapshot.repsEnabled ? null : snapshot.targetReps,
|
||||
actualScoreTimeMs: actualScoreTimeMs,
|
||||
scoreInputModeSnapshot: snapshot.scoreInputModeSnapshot,
|
||||
scoreLabelSnapshot: snapshot.scoreLabelSnapshot,
|
||||
scoreUnitSnapshot: snapshot.scoreUnitSnapshot,
|
||||
);
|
||||
await _advanceAfterSet(session, snapshot);
|
||||
return WatchCommandAck.accepted;
|
||||
}
|
||||
|
||||
Future<int?> _scoreStopwatchMsIfNeeded(
|
||||
ActiveWorkoutSession session,
|
||||
_ResolvedExerciseSnapshot snapshot,
|
||||
) async {
|
||||
if (!snapshot.scoreEnabled ||
|
||||
snapshot.scoreInputModeSnapshot != ScoreInputMode.stopwatch) {
|
||||
return null;
|
||||
}
|
||||
final state = await _sessionRepository.findScoreStopwatchState(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: session.currentProgramIndex,
|
||||
exerciseIndex: session.currentExerciseIndex,
|
||||
setIndex: session.currentSetIndex,
|
||||
);
|
||||
return state?.accumulatedMs;
|
||||
}
|
||||
|
||||
Future<void> _advanceAfterSet(
|
||||
ActiveWorkoutSession session,
|
||||
_ResolvedExerciseSnapshot snapshot,
|
||||
) async {
|
||||
final next = _nextPosition(session.resolvedTemplateSnapshotJson, session);
|
||||
if (next == null) {
|
||||
await _activeSessionUseCases.complete(session.metadata.id);
|
||||
return;
|
||||
}
|
||||
if (snapshot.restSeconds > 0) {
|
||||
await _activeSessionUseCases.startRestAfterSet(
|
||||
sessionId: session.metadata.id,
|
||||
afterProgramIndex: session.currentProgramIndex,
|
||||
afterExerciseIndex: session.currentExerciseIndex,
|
||||
afterSetIndex: session.currentSetIndex,
|
||||
plannedRestSeconds: snapshot.restSeconds,
|
||||
);
|
||||
return;
|
||||
}
|
||||
await _activeSessionUseCases.updateProgress(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: next.programIndex,
|
||||
exerciseIndex: next.exerciseIndex,
|
||||
setIndex: next.setIndex,
|
||||
);
|
||||
}
|
||||
|
||||
Future<WatchCommandAck> _skipCurrentRest(ActiveWorkoutSession session) async {
|
||||
final rest = await _activeSessionUseCases.findActiveRest(
|
||||
sessionId: session.metadata.id,
|
||||
);
|
||||
if (rest == null) {
|
||||
return WatchCommandAck.acceptedNoOp;
|
||||
}
|
||||
await _activeSessionUseCases.skipRest(restStateId: rest.metadata.id);
|
||||
final next = _nextPositionAfter(
|
||||
session.resolvedTemplateSnapshotJson,
|
||||
programIndex: rest.afterProgramIndex,
|
||||
exerciseIndex: rest.afterExerciseIndex,
|
||||
setIndex: rest.afterSetIndex,
|
||||
);
|
||||
if (next == null) {
|
||||
await _activeSessionUseCases.complete(session.metadata.id);
|
||||
} else {
|
||||
await _activeSessionUseCases.updateProgress(
|
||||
sessionId: session.metadata.id,
|
||||
programIndex: next.programIndex,
|
||||
exerciseIndex: next.exerciseIndex,
|
||||
setIndex: next.setIndex,
|
||||
);
|
||||
}
|
||||
return WatchCommandAck.accepted;
|
||||
}
|
||||
|
||||
Future<void> _emitProjectionAfterCommand() async {
|
||||
try {
|
||||
await _projectionSource.emitCurrentProjection();
|
||||
} on Exception {
|
||||
// The command has already been applied; a publish failure must not turn
|
||||
// the watch retry path into a second mutation.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class _WatchCommandKey {
|
||||
_WatchCommandKey(WatchCommandEnvelope command)
|
||||
: sessionId = command.sessionId,
|
||||
expectedRevision = command.expectedRevision,
|
||||
commandId = command.commandId,
|
||||
type = command.type;
|
||||
|
||||
final String sessionId;
|
||||
final int expectedRevision;
|
||||
final String commandId;
|
||||
final WatchCommandType type;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
other is _WatchCommandKey &&
|
||||
sessionId == other.sessionId &&
|
||||
expectedRevision == other.expectedRevision &&
|
||||
commandId == other.commandId &&
|
||||
type == other.type;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(sessionId, expectedRevision, commandId, type);
|
||||
}
|
||||
|
||||
final class WatchSessionProjectionProjector {
|
||||
const WatchSessionProjectionProjector({
|
||||
required this.sessionRepository,
|
||||
@ -4977,6 +5329,37 @@ _SetPositionSnapshot? _findSetSnapshot({
|
||||
return null;
|
||||
}
|
||||
|
||||
_SetPositionSnapshot? _nextPosition(
|
||||
String resolvedTemplateSnapshotJson,
|
||||
ActiveWorkoutSession session,
|
||||
) {
|
||||
return _nextPositionAfter(
|
||||
resolvedTemplateSnapshotJson,
|
||||
programIndex: session.currentProgramIndex,
|
||||
exerciseIndex: session.currentExerciseIndex,
|
||||
setIndex: session.currentSetIndex,
|
||||
);
|
||||
}
|
||||
|
||||
_SetPositionSnapshot? _nextPositionAfter(
|
||||
String resolvedTemplateSnapshotJson, {
|
||||
required int programIndex,
|
||||
required int exerciseIndex,
|
||||
required int setIndex,
|
||||
}) {
|
||||
final snapshots = _listSetSnapshots(resolvedTemplateSnapshotJson);
|
||||
final currentIndex = snapshots.indexWhere(
|
||||
(snapshot) =>
|
||||
snapshot.programIndex == programIndex &&
|
||||
snapshot.exerciseIndex == exerciseIndex &&
|
||||
snapshot.setIndex == setIndex,
|
||||
);
|
||||
if (currentIndex == -1 || currentIndex + 1 >= snapshots.length) {
|
||||
return null;
|
||||
}
|
||||
return snapshots[currentIndex + 1];
|
||||
}
|
||||
|
||||
_ResolvedExerciseSnapshot? _findExerciseSnapshot({
|
||||
required String resolvedTemplateSnapshotJson,
|
||||
required int programIndex,
|
||||
@ -5031,6 +5414,7 @@ _ResolvedExerciseSnapshot? _findExerciseSnapshot({
|
||||
scoreLabelSnapshot: exercise['scoreLabelSnapshot'] as String?,
|
||||
scoreUnitSnapshot: exercise['scoreUnitSnapshot'] as String?,
|
||||
setsCount: exercise['setsCount'] as int? ?? 0,
|
||||
restSeconds: exercise['restSecondsOverride'] as int? ?? 0,
|
||||
steps: _exerciseStepsFromSnapshot(exercise['exerciseStepsSnapshot']),
|
||||
autoStartNextTimedStepEffective: autoStartNextTimedStepEffective,
|
||||
);
|
||||
@ -5098,6 +5482,7 @@ List<_SetPositionSnapshot> _listSetSnapshots(
|
||||
scoreInputModeSnapshot: _scoreInputModeFromSnapshot(
|
||||
exercise['scoreInputModeSnapshot'],
|
||||
),
|
||||
restSeconds: exercise['restSecondsOverride'] as int? ?? 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -5116,6 +5501,7 @@ final class _SetPositionSnapshot {
|
||||
this.scoreLabelSnapshot,
|
||||
this.scoreUnitSnapshot,
|
||||
required this.scoreInputModeSnapshot,
|
||||
required this.restSeconds,
|
||||
});
|
||||
|
||||
final String programSnapshotId;
|
||||
@ -5126,6 +5512,7 @@ final class _SetPositionSnapshot {
|
||||
final String? scoreLabelSnapshot;
|
||||
final String? scoreUnitSnapshot;
|
||||
final ScoreInputMode scoreInputModeSnapshot;
|
||||
final int restSeconds;
|
||||
}
|
||||
|
||||
final class _StepSequenceContext {
|
||||
@ -5243,6 +5630,7 @@ Map<String, _ResolvedExerciseSnapshot> _exerciseSnapshotsById(
|
||||
scoreLabelSnapshot: exercise['scoreLabelSnapshot'] as String?,
|
||||
scoreUnitSnapshot: exercise['scoreUnitSnapshot'] as String?,
|
||||
setsCount: exercise['setsCount'] as int? ?? 0,
|
||||
restSeconds: exercise['restSecondsOverride'] as int? ?? 0,
|
||||
steps: _exerciseStepsFromSnapshot(exercise['exerciseStepsSnapshot']),
|
||||
autoStartNextTimedStepEffective:
|
||||
(exercise['autoStartNextTimedStepOverride'] as bool?) ??
|
||||
@ -5272,6 +5660,7 @@ final class _ResolvedExerciseSnapshot {
|
||||
this.scoreLabelSnapshot,
|
||||
this.scoreUnitSnapshot,
|
||||
required this.setsCount,
|
||||
required this.restSeconds,
|
||||
this.steps = const [],
|
||||
this.autoStartNextTimedStepEffective = true,
|
||||
});
|
||||
@ -5292,6 +5681,7 @@ final class _ResolvedExerciseSnapshot {
|
||||
final String? scoreLabelSnapshot;
|
||||
final String? scoreUnitSnapshot;
|
||||
final int setsCount;
|
||||
final int restSeconds;
|
||||
final List<ExerciseStep> steps;
|
||||
final bool autoStartNextTimedStepEffective;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user