wip(watch): sauvegarde travail en cours lot critique (#157 #163 #164 #174 #175 #178)

This commit is contained in:
2026-07-29 12:34:07 +02:00
parent f523a5a8b5
commit 74d6000606
26 changed files with 516 additions and 62 deletions

View File

@ -35,6 +35,9 @@ final class AudioplayersExerciseStepAudioCuePlayer
Future<void> _configureAudioContext() async {
try {
await _player.setAudioContext(_stepCueAudioContext);
await _player.setPlayerMode(PlayerMode.lowLatency);
await _player.setReleaseMode(ReleaseMode.stop);
await _player.setVolume(1);
} on Object catch (error) {
debugPrint('Audio cue context ignored: $error');
}

View File

@ -18,6 +18,7 @@ final class HistoryListScreen extends StatefulWidget {
this.sensorUseCases,
this.performanceReferenceUseCase,
this.onOpenProgression,
this.watchAlertPublisher,
super.key,
});
@ -30,6 +31,7 @@ final class HistoryListScreen extends StatefulWidget {
final CloseWorkoutSessionUseCase closeUseCase;
final MediaUseCases? mediaUseCases;
final VoidCallback? onOpenProgression;
final WatchAlertPublisher? watchAlertPublisher;
@override
State<HistoryListScreen> createState() => _HistoryListScreenState();
@ -116,6 +118,7 @@ final class _HistoryListScreenState extends State<HistoryListScreen> {
performanceReferenceUseCase: widget.performanceReferenceUseCase,
closeUseCase: widget.closeUseCase,
mediaUseCases: widget.mediaUseCases,
watchAlertPublisher: widget.watchAlertPublisher,
),
),
);
@ -138,6 +141,7 @@ final class HistoryDetailScreen extends StatelessWidget {
this.stepUseCases,
this.sensorUseCases,
this.performanceReferenceUseCase,
this.watchAlertPublisher,
super.key,
});
@ -150,6 +154,7 @@ final class HistoryDetailScreen extends StatelessWidget {
final ExercisePerformanceReferenceUseCase? performanceReferenceUseCase;
final CloseWorkoutSessionUseCase closeUseCase;
final MediaUseCases? mediaUseCases;
final WatchAlertPublisher? watchAlertPublisher;
@override
Widget build(BuildContext context) {
@ -265,6 +270,7 @@ final class HistoryDetailScreen extends StatelessWidget {
workoutTemplateUseCases: workoutTemplateUseCases,
performanceReferenceUseCase: performanceReferenceUseCase,
mediaUseCases: mediaUseCases,
watchAlertPublisher: watchAlertPublisher,
),
),
);

View File

@ -170,6 +170,7 @@ final class _HomeScreenState extends State<HomeScreen> with RouteAware {
shareUseCases: widget.bootstrap.shareUseCases,
authUseCases: widget.bootstrap.authUseCases,
syncUseCases: widget.bootstrap.syncUseCases,
watchAlertPublisher: widget.bootstrap.watchAlertPublisher,
),
),
),
@ -191,6 +192,7 @@ final class _HomeScreenState extends State<HomeScreen> with RouteAware {
widget.bootstrap.exercisePerformanceReferenceUseCase,
closeUseCase: widget.bootstrap.closeWorkoutSessionUseCase,
mediaUseCases: widget.bootstrap.mediaUseCases,
watchAlertPublisher: widget.bootstrap.watchAlertPublisher,
onOpenProgression: () => _openProgression(context),
),
),
@ -256,6 +258,7 @@ final class _HomeScreenState extends State<HomeScreen> with RouteAware {
widget.bootstrap.exercisePerformanceReferenceUseCase,
closeUseCase: widget.bootstrap.closeWorkoutSessionUseCase,
mediaUseCases: widget.bootstrap.mediaUseCases,
watchAlertPublisher: widget.bootstrap.watchAlertPublisher,
),
),
);
@ -276,6 +279,7 @@ final class _HomeScreenState extends State<HomeScreen> with RouteAware {
performanceReferenceUseCase:
widget.bootstrap.exercisePerformanceReferenceUseCase,
mediaUseCases: widget.bootstrap.mediaUseCases,
watchAlertPublisher: widget.bootstrap.watchAlertPublisher,
),
),
);

View File

@ -16,6 +16,7 @@ final class ProgressionScreen extends StatefulWidget {
this.mediaUseCases,
this.stepUseCases,
this.performanceReferenceUseCase,
this.watchAlertPublisher,
super.key,
});
@ -27,6 +28,7 @@ final class ProgressionScreen extends StatefulWidget {
final ExercisePerformanceReferenceUseCase? performanceReferenceUseCase;
final CloseWorkoutSessionUseCase closeUseCase;
final MediaUseCases? mediaUseCases;
final WatchAlertPublisher? watchAlertPublisher;
@override
State<ProgressionScreen> createState() => _ProgressionScreenState();
@ -247,6 +249,7 @@ final class _ProgressionScreenState extends State<ProgressionScreen> {
performanceReferenceUseCase: widget.performanceReferenceUseCase,
closeUseCase: widget.closeUseCase,
mediaUseCases: widget.mediaUseCases,
watchAlertPublisher: widget.watchAlertPublisher,
),
),
);
@ -278,6 +281,7 @@ final class _ProgressionScreenState extends State<ProgressionScreen> {
performanceReferenceUseCase: widget.performanceReferenceUseCase,
closeUseCase: widget.closeUseCase,
mediaUseCases: widget.mediaUseCases,
watchAlertPublisher: widget.watchAlertPublisher,
),
),
);

View File

@ -4,6 +4,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:watch_bridge_contract/watch_bridge_contract.dart';
import '../application/application.dart';
import '../domain/domain.dart';
@ -30,6 +31,7 @@ final class WorkoutExecutionScreen extends StatefulWidget {
this.stepUseCases,
this.sensorUseCases,
this.stepAudioCuePlayer,
this.watchAlertPublisher,
super.key,
});
@ -45,6 +47,7 @@ final class WorkoutExecutionScreen extends StatefulWidget {
final VideoMediaBuilder? videoMediaBuilder;
final ActiveWorkoutSensorUseCases? sensorUseCases;
final ExerciseStepAudioCuePlayer? stepAudioCuePlayer;
final WatchAlertPublisher? watchAlertPublisher;
@override
State<WorkoutExecutionScreen> createState() => _WorkoutExecutionScreenState();
@ -1077,6 +1080,7 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
}
Future<void> _playStepAudioCue({required bool short}) async {
unawaited(_publishStepAlert(short: short));
try {
if (short) {
await _stepAudioCuePlayer.playShortCountdownBeep();
@ -1088,6 +1092,52 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
}
}
Future<void> _publishStepAlert({required bool short}) async {
final publisher = widget.watchAlertPublisher;
if (publisher == null) {
return;
}
final nowMs = DateTime.now().toUtc().millisecondsSinceEpoch;
final position = _position;
final stepState = _stepProgress?.state;
final passageIndex = stepState?.currentPassageIndex;
final stepIndex = stepState?.currentStepIndex;
final ttlMs = short ? 700 : 2000;
final alert = WatchAlertEnvelope(
alertId: [
_session.metadata.id,
position.programIndex,
position.exerciseIndex,
position.setIndex,
passageIndex,
stepIndex,
short ? 'tick' : 'finish',
nowMs,
].join('-'),
sessionId: _session.metadata.id,
revision: 0,
kind: short
? WatchAlertKind.countdownTick
: WatchAlertKind.countdownFinished,
timerKind: WatchTimerKind.step,
programIndex: position.programIndex,
exerciseIndex: position.exerciseIndex,
setIndex: position.setIndex,
passageIndex: passageIndex,
stepIndex: stepIndex,
scheduledForEpochMs: nowMs,
expiresAtEpochMs: nowMs + ttlMs,
pattern: short
? WatchAlertPattern.countdownTick
: WatchAlertPattern.timerFinished,
);
try {
await publisher.publishAlert(alert);
} on Object catch (error) {
debugPrint('Watch alert ignored: $error');
}
}
Future<void> _startCurrentStepTimer() async {
if (_stepProgress?.currentStep?.type != ExerciseStepType.time) return;
try {
@ -1818,6 +1868,7 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
mediaAssetLoader: widget.mediaAssetLoader,
videoMediaBuilder: widget.videoMediaBuilder,
sensorUseCases: widget.sensorUseCases,
watchAlertPublisher: widget.watchAlertPublisher,
),
),
);

View File

@ -24,6 +24,7 @@ final class WorkoutTemplateListScreen extends StatefulWidget {
this.shareUseCases,
this.authUseCases,
this.syncUseCases,
this.watchAlertPublisher,
super.key,
});
@ -39,6 +40,7 @@ final class WorkoutTemplateListScreen extends StatefulWidget {
final ShareUseCases? shareUseCases;
final AuthUseCases? authUseCases;
final SyncUseCases? syncUseCases;
final WatchAlertPublisher? watchAlertPublisher;
@override
State<WorkoutTemplateListScreen> createState() =>
@ -311,6 +313,7 @@ final class _WorkoutTemplateListScreenState
performanceReferenceUseCase: widget.performanceReferenceUseCase,
mediaUseCases: widget.mediaUseCases,
sensorUseCases: widget.sensorUseCases,
watchAlertPublisher: widget.watchAlertPublisher,
),
),
);