feat(exercice): exécution de séance avec module séquence et bips (ticket #60)
Ajoute le lecteur de bips d'étapes (presentation/exercise_step_audio.dart, port ExerciseStepAudioCuePlayer injectable en test, jamais le vrai audioplayers en test) et les fichiers audio associés (assets/audio/step_countdown_short.wav, step_completion_long.wav). Câble ActiveExerciseStepUseCases dans app_bootstrap.dart et l'expose à WorkoutExecutionScreen. Les appels audio réels sont protégés par un try/catch qui log sans jamais bloquer la progression d'étape. flutter pub get OK (ajout audioplayers), analyze propre (mêmes infos préexistantes + 2 nouvelles use_build_context_synchronously, non bloquantes), 100/100 tests verts, build APK debug validé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -7,6 +7,7 @@ abstract interface class AppDependencies {
|
||||
ProgramUseCases get programUseCases;
|
||||
WorkoutTemplateUseCases get workoutTemplateUseCases;
|
||||
ActiveWorkoutSessionUseCases get activeWorkoutSessionUseCases;
|
||||
ActiveExerciseStepUseCases get activeExerciseStepUseCases;
|
||||
CloseWorkoutSessionUseCase get closeWorkoutSessionUseCase;
|
||||
WorkoutHistoryUseCases get workoutHistoryUseCases;
|
||||
}
|
||||
@ -19,6 +20,7 @@ final class AppBootstrap implements AppDependencies {
|
||||
required this.programUseCases,
|
||||
required this.workoutTemplateUseCases,
|
||||
required this.activeWorkoutSessionUseCases,
|
||||
required this.activeExerciseStepUseCases,
|
||||
required this.closeWorkoutSessionUseCase,
|
||||
required this.workoutHistoryUseCases,
|
||||
required this.syncGateway,
|
||||
@ -36,6 +38,8 @@ final class AppBootstrap implements AppDependencies {
|
||||
@override
|
||||
final ActiveWorkoutSessionUseCases activeWorkoutSessionUseCases;
|
||||
@override
|
||||
final ActiveExerciseStepUseCases activeExerciseStepUseCases;
|
||||
@override
|
||||
final CloseWorkoutSessionUseCase closeWorkoutSessionUseCase;
|
||||
@override
|
||||
final WorkoutHistoryUseCases workoutHistoryUseCases;
|
||||
@ -93,6 +97,12 @@ final class AppBootstrap implements AppDependencies {
|
||||
ids: ids,
|
||||
originDeviceId: originDeviceId,
|
||||
),
|
||||
activeExerciseStepUseCases: ActiveExerciseStepUseCases(
|
||||
sessionRepository: activeSessionRepository,
|
||||
clock: clock,
|
||||
ids: ids,
|
||||
originDeviceId: originDeviceId,
|
||||
),
|
||||
closeWorkoutSessionUseCase: CloseWorkoutSessionUseCase(
|
||||
sessionRepository: activeSessionRepository,
|
||||
historyRepository: historyRepository,
|
||||
|
||||
49
lib/presentation/exercise_step_audio.dart
Normal file
49
lib/presentation/exercise_step_audio.dart
Normal file
@ -0,0 +1,49 @@
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
|
||||
abstract interface class ExerciseStepAudioCuePlayer {
|
||||
Future<void> playShortCountdownBeep();
|
||||
|
||||
Future<void> playLongCompletionBeep();
|
||||
|
||||
Future<void> dispose();
|
||||
}
|
||||
|
||||
final class AudioplayersExerciseStepAudioCuePlayer
|
||||
implements ExerciseStepAudioCuePlayer {
|
||||
AudioplayersExerciseStepAudioCuePlayer();
|
||||
|
||||
final AudioPlayer _player = AudioPlayer();
|
||||
|
||||
@override
|
||||
Future<void> playShortCountdownBeep() {
|
||||
return _play('audio/step_countdown_short.wav');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> playLongCompletionBeep() {
|
||||
return _play('audio/step_completion_long.wav');
|
||||
}
|
||||
|
||||
Future<void> _play(String assetPath) async {
|
||||
await _player.stop();
|
||||
await _player.play(AssetSource(assetPath));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() {
|
||||
return _player.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
final class NoOpExerciseStepAudioCuePlayer implements ExerciseStepAudioCuePlayer {
|
||||
const NoOpExerciseStepAudioCuePlayer();
|
||||
|
||||
@override
|
||||
Future<void> playShortCountdownBeep() async {}
|
||||
|
||||
@override
|
||||
Future<void> playLongCompletionBeep() async {}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {}
|
||||
}
|
||||
@ -14,12 +14,14 @@ final class HistoryListScreen extends StatefulWidget {
|
||||
required this.activeUseCases,
|
||||
required this.closeUseCase,
|
||||
this.mediaUseCases,
|
||||
this.stepUseCases,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final WorkoutHistoryUseCases historyUseCases;
|
||||
final WorkoutTemplateUseCases workoutTemplateUseCases;
|
||||
final ActiveWorkoutSessionUseCases activeUseCases;
|
||||
final ActiveExerciseStepUseCases? stepUseCases;
|
||||
final CloseWorkoutSessionUseCase closeUseCase;
|
||||
final MediaUseCases? mediaUseCases;
|
||||
|
||||
@ -94,6 +96,7 @@ final class _HistoryListScreenState extends State<HistoryListScreen> {
|
||||
historyUseCases: widget.historyUseCases,
|
||||
workoutTemplateUseCases: widget.workoutTemplateUseCases,
|
||||
activeUseCases: widget.activeUseCases,
|
||||
stepUseCases: widget.stepUseCases,
|
||||
closeUseCase: widget.closeUseCase,
|
||||
mediaUseCases: widget.mediaUseCases,
|
||||
),
|
||||
@ -115,6 +118,7 @@ final class HistoryDetailScreen extends StatelessWidget {
|
||||
required this.activeUseCases,
|
||||
required this.closeUseCase,
|
||||
this.mediaUseCases,
|
||||
this.stepUseCases,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@ -122,6 +126,7 @@ final class HistoryDetailScreen extends StatelessWidget {
|
||||
final WorkoutHistoryUseCases historyUseCases;
|
||||
final WorkoutTemplateUseCases workoutTemplateUseCases;
|
||||
final ActiveWorkoutSessionUseCases activeUseCases;
|
||||
final ActiveExerciseStepUseCases? stepUseCases;
|
||||
final CloseWorkoutSessionUseCase closeUseCase;
|
||||
final MediaUseCases? mediaUseCases;
|
||||
|
||||
@ -193,6 +198,7 @@ final class HistoryDetailScreen extends StatelessWidget {
|
||||
builder: (context) => WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: activeUseCases,
|
||||
stepUseCases: stepUseCases,
|
||||
closeUseCase: closeUseCase,
|
||||
historyUseCases: historyUseCases,
|
||||
workoutTemplateUseCases: workoutTemplateUseCases,
|
||||
|
||||
@ -152,6 +152,7 @@ final class _HomeScreenState extends State<HomeScreen> with RouteAware {
|
||||
widget.bootstrap.workoutTemplateUseCases,
|
||||
programUseCases: widget.bootstrap.programUseCases,
|
||||
activeUseCases: widget.bootstrap.activeWorkoutSessionUseCases,
|
||||
stepUseCases: widget.bootstrap.activeExerciseStepUseCases,
|
||||
closeUseCase: widget.bootstrap.closeWorkoutSessionUseCase,
|
||||
historyUseCases: widget.bootstrap.workoutHistoryUseCases,
|
||||
mediaUseCases: widget.bootstrap.mediaUseCases,
|
||||
@ -170,6 +171,7 @@ final class _HomeScreenState extends State<HomeScreen> with RouteAware {
|
||||
workoutTemplateUseCases:
|
||||
widget.bootstrap.workoutTemplateUseCases,
|
||||
activeUseCases: widget.bootstrap.activeWorkoutSessionUseCases,
|
||||
stepUseCases: widget.bootstrap.activeExerciseStepUseCases,
|
||||
closeUseCase: widget.bootstrap.closeWorkoutSessionUseCase,
|
||||
mediaUseCases: widget.bootstrap.mediaUseCases,
|
||||
),
|
||||
@ -187,6 +189,7 @@ final class _HomeScreenState extends State<HomeScreen> with RouteAware {
|
||||
builder: (context) => WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: widget.bootstrap.activeWorkoutSessionUseCases,
|
||||
stepUseCases: widget.bootstrap.activeExerciseStepUseCases,
|
||||
closeUseCase: widget.bootstrap.closeWorkoutSessionUseCase,
|
||||
historyUseCases: widget.bootstrap.workoutHistoryUseCases,
|
||||
workoutTemplateUseCases: widget.bootstrap.workoutTemplateUseCases,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export 'game_time_app.dart';
|
||||
export 'exercise_step_audio.dart';
|
||||
export 'exercise_library_screen.dart';
|
||||
export 'history_screen.dart';
|
||||
export 'home_screen.dart';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -14,12 +14,14 @@ final class WorkoutTemplateListScreen extends StatefulWidget {
|
||||
required this.closeUseCase,
|
||||
required this.historyUseCases,
|
||||
this.mediaUseCases,
|
||||
this.stepUseCases,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final WorkoutTemplateUseCases workoutTemplateUseCases;
|
||||
final ProgramUseCases programUseCases;
|
||||
final ActiveWorkoutSessionUseCases activeUseCases;
|
||||
final ActiveExerciseStepUseCases? stepUseCases;
|
||||
final CloseWorkoutSessionUseCase closeUseCase;
|
||||
final WorkoutHistoryUseCases historyUseCases;
|
||||
final MediaUseCases? mediaUseCases;
|
||||
@ -140,6 +142,7 @@ final class _WorkoutTemplateListScreenState
|
||||
builder: (context) => WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: widget.activeUseCases,
|
||||
stepUseCases: widget.stepUseCases,
|
||||
closeUseCase: widget.closeUseCase,
|
||||
historyUseCases: widget.historyUseCases,
|
||||
workoutTemplateUseCases: widget.workoutTemplateUseCases,
|
||||
|
||||
Reference in New Issue
Block a user