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>
This commit is contained in:
@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:gametime/application/application.dart';
|
||||
import 'package:gametime/domain/domain.dart';
|
||||
import 'package:gametime/presentation/presentation.dart';
|
||||
import 'package:watch_bridge_contract/watch_bridge_contract.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('le temps affiché est recalculé depuis les horodatages', (
|
||||
@ -45,6 +46,48 @@ void main() {
|
||||
expect(find.widgetWithText(FilledButton, 'Démarrer'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('une séance invalide est abandonnée avec un message clair', (
|
||||
tester,
|
||||
) async {
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12, 0, 15));
|
||||
final repository = _FakeActiveSessionRepository();
|
||||
final session = ActiveWorkoutSession(
|
||||
metadata: _metadata('session-1'),
|
||||
sourceWorkoutTemplateId: 'template-1',
|
||||
status: ActiveWorkoutStatus.running,
|
||||
startedAt: DateTime.utc(2026, 7, 17, 11, 59),
|
||||
lastPersistedAt: DateTime.utc(2026, 7, 17, 12),
|
||||
elapsedActiveMs: 30000,
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
resolvedTemplateSnapshotJson: _emptySessionSnapshot(),
|
||||
);
|
||||
repository.session = session;
|
||||
|
||||
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('Séance indisponible'), findsOneWidget);
|
||||
expect(
|
||||
find.text(
|
||||
'Cette séance ne peut plus être reprise. Elle a été abandonnée automatiquement.',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(repository.session?.status, ActiveWorkoutStatus.abandoned);
|
||||
});
|
||||
|
||||
testWidgets('les répétitions sont initialisées avec la cible', (
|
||||
tester,
|
||||
) async {
|
||||
@ -81,6 +124,214 @@ void main() {
|
||||
expect(find.text('8'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('affiche les statistiques montre live disponibles', (
|
||||
tester,
|
||||
) async {
|
||||
final now = DateTime.now().toUtc();
|
||||
final clock = _FakeClock(now);
|
||||
final repository = _FakeActiveSessionRepository();
|
||||
final sensorUseCases = ActiveWorkoutSensorUseCases(clock: clock)
|
||||
..recordHeartRateSample(
|
||||
WatchSensorSample(
|
||||
sessionId: 'session-1',
|
||||
recordedAtEpochMs: now
|
||||
.subtract(const Duration(hours: 1))
|
||||
.millisecondsSinceEpoch,
|
||||
heartRateBpm: 124,
|
||||
),
|
||||
)
|
||||
..recordHeartRateSample(
|
||||
WatchSensorSample(
|
||||
sessionId: 'session-1',
|
||||
recordedAtEpochMs: now.millisecondsSinceEpoch,
|
||||
heartRateBpm: 124,
|
||||
distanceMeters: 840,
|
||||
caloriesKcal: 186,
|
||||
),
|
||||
);
|
||||
addTearDown(sensorUseCases.dispose);
|
||||
final session = ActiveWorkoutSession(
|
||||
metadata: _metadata('session-1'),
|
||||
sourceWorkoutTemplateId: 'template-1',
|
||||
status: ActiveWorkoutStatus.running,
|
||||
startedAt: now,
|
||||
lastPersistedAt: now,
|
||||
elapsedActiveMs: 0,
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
resolvedTemplateSnapshotJson: _sessionSnapshot(),
|
||||
);
|
||||
repository.session = session;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: _activeUseCases(repository, clock),
|
||||
closeUseCase: _closeUseCase(repository, clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
sensorUseCases: sensorUseCases,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('FC 124 bpm'), findsOneWidget);
|
||||
expect(find.text('840 m'), findsOneWidget);
|
||||
expect(find.text('186 kcal'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('n’affiche aucun indicateur capteur sans donnée FC', (
|
||||
tester,
|
||||
) async {
|
||||
final now = DateTime.now().toUtc();
|
||||
final clock = _FakeClock(now);
|
||||
final repository = _FakeActiveSessionRepository();
|
||||
final sensorUseCases = ActiveWorkoutSensorUseCases(clock: clock);
|
||||
addTearDown(sensorUseCases.dispose);
|
||||
final session = ActiveWorkoutSession(
|
||||
metadata: _metadata('session-1'),
|
||||
sourceWorkoutTemplateId: 'template-1',
|
||||
status: ActiveWorkoutStatus.running,
|
||||
startedAt: now,
|
||||
lastPersistedAt: now,
|
||||
elapsedActiveMs: 0,
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
resolvedTemplateSnapshotJson: _sessionSnapshot(),
|
||||
);
|
||||
repository.session = session;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: _activeUseCases(repository, clock),
|
||||
closeUseCase: _closeUseCase(repository, clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
sensorUseCases: sensorUseCases,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.textContaining('bpm'), findsNothing);
|
||||
expect(find.textContaining('kcal'), findsNothing);
|
||||
expect(find.byIcon(Icons.favorite), findsNothing);
|
||||
expect(find.byIcon(Icons.local_fire_department), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('ne fabrique pas de calories sans mesure montre native', (
|
||||
tester,
|
||||
) async {
|
||||
final now = DateTime.now().toUtc();
|
||||
final clock = _FakeClock(now);
|
||||
final repository = _FakeActiveSessionRepository();
|
||||
final sensorUseCases = ActiveWorkoutSensorUseCases(clock: clock)
|
||||
..recordHeartRateSample(
|
||||
WatchSensorSample(
|
||||
sessionId: 'session-1',
|
||||
recordedAtEpochMs: now
|
||||
.subtract(const Duration(hours: 1))
|
||||
.millisecondsSinceEpoch,
|
||||
heartRateBpm: 124,
|
||||
),
|
||||
)
|
||||
..recordHeartRateSample(
|
||||
WatchSensorSample(
|
||||
sessionId: 'session-1',
|
||||
recordedAtEpochMs: now.millisecondsSinceEpoch,
|
||||
heartRateBpm: 124,
|
||||
),
|
||||
);
|
||||
addTearDown(sensorUseCases.dispose);
|
||||
final session = ActiveWorkoutSession(
|
||||
metadata: _metadata('session-1'),
|
||||
sourceWorkoutTemplateId: 'template-1',
|
||||
status: ActiveWorkoutStatus.running,
|
||||
startedAt: now,
|
||||
lastPersistedAt: now,
|
||||
elapsedActiveMs: 0,
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
resolvedTemplateSnapshotJson: _sessionSnapshot(),
|
||||
);
|
||||
repository.session = session;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: _activeUseCases(repository, clock),
|
||||
closeUseCase: _closeUseCase(repository, clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
sensorUseCases: sensorUseCases,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('FC 124 bpm'), findsOneWidget);
|
||||
expect(find.textContaining('kcal'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('atténue la fréquence cardiaque périmée', (tester) async {
|
||||
final now = DateTime.now().toUtc();
|
||||
final clock = _FakeClock(now);
|
||||
final repository = _FakeActiveSessionRepository();
|
||||
final sensorUseCases = ActiveWorkoutSensorUseCases(clock: clock)
|
||||
..recordHeartRateSample(
|
||||
WatchSensorSample(
|
||||
sessionId: 'session-1',
|
||||
recordedAtEpochMs: now
|
||||
.subtract(const Duration(seconds: 30))
|
||||
.millisecondsSinceEpoch,
|
||||
heartRateBpm: 118,
|
||||
),
|
||||
);
|
||||
addTearDown(sensorUseCases.dispose);
|
||||
final session = ActiveWorkoutSession(
|
||||
metadata: _metadata('session-1'),
|
||||
sourceWorkoutTemplateId: 'template-1',
|
||||
status: ActiveWorkoutStatus.running,
|
||||
startedAt: now,
|
||||
lastPersistedAt: now,
|
||||
elapsedActiveMs: 0,
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
resolvedTemplateSnapshotJson: _sessionSnapshot(),
|
||||
);
|
||||
repository.session = session;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: _activeUseCases(repository, clock),
|
||||
closeUseCase: _closeUseCase(repository, clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
sensorUseCases: sensorUseCases,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('FC 118 bpm'), findsOneWidget);
|
||||
expect(
|
||||
find.ancestor(
|
||||
of: find.text('FC 118 bpm'),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) => widget is Opacity && widget.opacity == 0.45,
|
||||
),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('les références de temps sont formatées sans valeurs brutes', (
|
||||
tester,
|
||||
) async {
|
||||
@ -152,7 +403,9 @@ void main() {
|
||||
tester,
|
||||
) async {
|
||||
final scoreController = TextEditingController();
|
||||
final scoreFocusNode = FocusNode();
|
||||
addTearDown(scoreController.dispose);
|
||||
addTearDown(scoreFocusNode.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
@ -170,6 +423,8 @@ void main() {
|
||||
),
|
||||
reps: 0,
|
||||
scoreController: scoreController,
|
||||
scoreFocusNode: scoreFocusNode,
|
||||
onScoreSubmitted: (_) {},
|
||||
onRepsChanged: (_) {},
|
||||
),
|
||||
),
|
||||
@ -808,6 +1063,63 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'la saisie du score téléphone alimente le score vivant de la montre',
|
||||
(tester) async {
|
||||
await tester.binding.setSurfaceSize(const Size(400, 1400));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||
final repository = _FakeActiveSessionRepository();
|
||||
final activeUseCases = _activeUseCases(repository, clock);
|
||||
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(
|
||||
restSeconds: 0,
|
||||
scoreUnit: 'points',
|
||||
),
|
||||
);
|
||||
repository.session = session;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: activeUseCases,
|
||||
closeUseCase: _closeUseCase(repository, clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextField, 'Score (points)'),
|
||||
'7.5',
|
||||
);
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pump();
|
||||
|
||||
expect(repository.manualScoreStates.single.value, 7.5);
|
||||
|
||||
final incremented = await activeUseCases.incrementManualScore(
|
||||
sessionId: 'session-1',
|
||||
programIndex: 0,
|
||||
exerciseIndex: 0,
|
||||
setIndex: 0,
|
||||
);
|
||||
expect(incremented.state.value, 8.5);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('la flèche de retour met la séance active en pause', (
|
||||
tester,
|
||||
) async {
|
||||
@ -1186,11 +1498,75 @@ void main() {
|
||||
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);
|
||||
expect(find.text('ÉTAPE 1 / 2'), findsNothing);
|
||||
expect(find.text('Dribble main droite'), findsOneWidget);
|
||||
expect(find.text('00:10'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('un exercice dense avec étapes et scores reste scrollable', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.binding.setSurfaceSize(const Size(400, 700));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
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: 3,
|
||||
targetReps: 20,
|
||||
scoreEnabled: true,
|
||||
scoreUnit: 'paniers',
|
||||
exerciseSteps: [
|
||||
_step(
|
||||
'step-1',
|
||||
0,
|
||||
'Tirs après dribble',
|
||||
ExerciseStepType.time,
|
||||
10,
|
||||
hasScore: true,
|
||||
scoreLabel: 'Paniers',
|
||||
scoreUnit: 'paniers',
|
||||
defaultTargetScore: 8,
|
||||
),
|
||||
_step('step-2', 1, 'Replacement défensif', ExerciseStepType.reps, 6),
|
||||
],
|
||||
),
|
||||
);
|
||||
repository.session = session;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: _activeUseCases(repository, clock),
|
||||
stepUseCases: _stepUseCases(repository, clock),
|
||||
stepAudioCuePlayer: _FakeStepAudioCuePlayer(),
|
||||
closeUseCase: _closeUseCase(repository, clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.takeException(), isNull);
|
||||
expect(find.text('ÉTAPE 1 / 2'), findsNothing);
|
||||
expect(find.text('Tirs après dribble'), findsOneWidget);
|
||||
expect(find.text('00:10'), findsOneWidget);
|
||||
expect(find.text('Passer l’étape'), findsOneWidget);
|
||||
expect(find.byType(SingleChildScrollView), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('l’écran actif avec séquence longue reste sans scroll', (
|
||||
tester,
|
||||
) async {
|
||||
@ -1465,7 +1841,7 @@ void main() {
|
||||
|
||||
expect(repository.stepResults.single.stepIndex, 0);
|
||||
expect(repository.stepResults.single.status, SetResultStatus.completed);
|
||||
expect(find.text('ÉTAPE 2 / 2'), findsOneWidget);
|
||||
expect(find.text('ÉTAPE 2 / 2'), findsNothing);
|
||||
expect(find.text('Droite'), findsOneWidget);
|
||||
expect(find.text('00:02'), findsOneWidget);
|
||||
expect(
|
||||
@ -1526,7 +1902,7 @@ void main() {
|
||||
await tester.pump();
|
||||
|
||||
expect(repository.stepResults.single.stepIndex, 0);
|
||||
expect(find.text('ÉTAPE 2 / 2'), findsOneWidget);
|
||||
expect(find.text('ÉTAPE 2 / 2'), findsNothing);
|
||||
expect(find.text('Droite'), findsOneWidget);
|
||||
expect(find.text('00:02'), findsOneWidget);
|
||||
expect(find.text('Chrono prêt'), findsOneWidget);
|
||||
@ -1847,6 +2223,20 @@ String _sessionSnapshot({
|
||||
});
|
||||
}
|
||||
|
||||
String _emptySessionSnapshot() {
|
||||
return jsonEncode({
|
||||
'name': 'Séance vide',
|
||||
'programs': [
|
||||
{
|
||||
'id': 'template-program-1',
|
||||
'programNameSnapshot': 'Programme vide',
|
||||
'programSnapshotJson': jsonEncode({'exercises': const []}),
|
||||
},
|
||||
],
|
||||
'overrides': const [],
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, Object?> _step(
|
||||
String id,
|
||||
int position,
|
||||
@ -1960,6 +2350,7 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
final results = <ActiveSetResult>[];
|
||||
final restStates = <ActiveRestState>[];
|
||||
final scoreStopwatchStates = <ActiveScoreStopwatchState>[];
|
||||
final manualScoreStates = <ActiveManualScoreState>[];
|
||||
final setTimerStates = <ActiveSetTimerState>[];
|
||||
final stepProgressStates = <ActiveExerciseStepProgressState>[];
|
||||
final stepResults = <ActiveExerciseStepResult>[];
|
||||
@ -2001,6 +2392,25 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ActiveManualScoreState?> findManualScoreState({
|
||||
required String sessionId,
|
||||
required int programIndex,
|
||||
required int exerciseIndex,
|
||||
required int setIndex,
|
||||
}) async {
|
||||
for (final state in manualScoreStates) {
|
||||
if (state.activeWorkoutSessionId == sessionId &&
|
||||
state.programIndex == programIndex &&
|
||||
state.exerciseIndex == exerciseIndex &&
|
||||
state.setIndex == setIndex &&
|
||||
state.metadata.deletedAt == null) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ActiveSetTimerState?> findSetTimerState({
|
||||
required String sessionId,
|
||||
@ -2055,6 +2465,15 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ActiveManualScoreState>> listManualScoreStates(
|
||||
String sessionId,
|
||||
) async {
|
||||
return manualScoreStates
|
||||
.where((state) => state.activeWorkoutSessionId == sessionId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ActiveSetResult>> listSetResults(String sessionId) async {
|
||||
return results;
|
||||
@ -2114,6 +2533,18 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveManualScoreState(ActiveManualScoreState state) async {
|
||||
final index = manualScoreStates.indexWhere(
|
||||
(saved) => saved.metadata.id == state.metadata.id,
|
||||
);
|
||||
if (index == -1) {
|
||||
manualScoreStates.add(state);
|
||||
} else {
|
||||
manualScoreStates[index] = state;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveSetTimerState(ActiveSetTimerState state) async {
|
||||
final index = setTimerStates.indexWhere(
|
||||
@ -2183,6 +2614,23 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteManualScoreState({
|
||||
required String sessionId,
|
||||
required int programIndex,
|
||||
required int exerciseIndex,
|
||||
required int setIndex,
|
||||
required DateTime deletedAt,
|
||||
}) async {
|
||||
manualScoreStates.removeWhere(
|
||||
(state) =>
|
||||
state.activeWorkoutSessionId == sessionId &&
|
||||
state.programIndex == programIndex &&
|
||||
state.exerciseIndex == exerciseIndex &&
|
||||
state.setIndex == setIndex,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveSetResult(ActiveSetResult result) async {
|
||||
final index = results.indexWhere(
|
||||
@ -2237,6 +2685,14 @@ final class _FakeWorkoutHistoryRepository implements WorkoutHistoryRepository {
|
||||
@override
|
||||
Future<void> save(WorkoutHistory history) async {}
|
||||
|
||||
@override
|
||||
Future<void> patchHeartRateSummary({
|
||||
required String historyId,
|
||||
required double averageHeartRateBpm,
|
||||
required int maxHeartRateBpm,
|
||||
required DateTime patchedAt,
|
||||
}) async {}
|
||||
|
||||
@override
|
||||
Future<void> saveSetResult(WorkoutHistorySetResult result) async {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user