feat(exercice): affichage des médias pendant l'exécution (ticket #36)
Expose la galerie d'images d'exercice (backend du ticket #35) pendant l'exécution de séance sur workout_execution_screen.dart et program_screen.dart, avec les ajustements domain/use cases/repositories nécessaires. flutter analyze propre, 67/67 tests verts, build APK debug validé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -225,6 +225,66 @@ void main() {
|
||||
expect(updated.imageMediaId, 'media-1');
|
||||
});
|
||||
|
||||
test('program exercise snapshot keeps the full image gallery', () async {
|
||||
final exerciseRepository = _FakeExerciseRepository()
|
||||
..exercise = Exercise(
|
||||
metadata: _metadata('exercise-1'),
|
||||
name: 'Squat',
|
||||
imageMediaIds: const ['media-1', 'media-2', 'media-3'],
|
||||
videoMediaId: 'video-1',
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: false,
|
||||
defaultTargetReps: 10,
|
||||
);
|
||||
final programRepository = _FakeProgramRepository()
|
||||
..programs.add(
|
||||
Program(
|
||||
metadata: _metadata('program-1'),
|
||||
name: 'Jambes',
|
||||
defaultRestSeconds: 60,
|
||||
),
|
||||
);
|
||||
final useCase = ProgramUseCases(
|
||||
programRepository: programRepository,
|
||||
exerciseRepository: exerciseRepository,
|
||||
templateRepository: _FakeWorkoutTemplateRepository(),
|
||||
clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
|
||||
ids: _FakeIds(),
|
||||
originDeviceId: 'device-1',
|
||||
);
|
||||
|
||||
final snapshot = await useCase.addExercise(
|
||||
programId: 'program-1',
|
||||
exerciseId: 'exercise-1',
|
||||
position: 0,
|
||||
setsCount: 3,
|
||||
enabledMeasures: const {WorkoutMeasure.reps},
|
||||
);
|
||||
|
||||
expect(snapshot.exerciseImageMediaIdSnapshot, 'media-1');
|
||||
expect(snapshot.exerciseImageMediaIdsSnapshot, [
|
||||
'media-1',
|
||||
'media-2',
|
||||
'media-3',
|
||||
]);
|
||||
expect(snapshot.exerciseVideoMediaIdSnapshot, 'video-1');
|
||||
expect(snapshot.toSnapshotJson()['imageMediaIdsSnapshot'], [
|
||||
'media-1',
|
||||
'media-2',
|
||||
'media-3',
|
||||
]);
|
||||
expect(
|
||||
programRepository
|
||||
.programs
|
||||
.single
|
||||
.exercises
|
||||
.single
|
||||
.exerciseImageMediaIdsSnapshot,
|
||||
snapshot.exerciseImageMediaIdsSnapshot,
|
||||
);
|
||||
});
|
||||
|
||||
test('delete exercise removes it from referencing programs', () async {
|
||||
final exerciseRepository = _FakeExerciseRepository()
|
||||
..exercise = Exercise(
|
||||
|
||||
@ -116,6 +116,131 @@ void main() {
|
||||
expect(find.text('Pas de repos'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('le bouton médias est absent sans média', (tester) async {
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||
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(),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: _activeUseCases(
|
||||
_FakeActiveSessionRepository(),
|
||||
clock,
|
||||
),
|
||||
closeUseCase: _closeUseCase(_FakeActiveSessionRepository(), clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Voir médias'), findsNothing);
|
||||
expect(find.byTooltip('Voir les médias de l’exercice'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('le bouton médias ouvre la sheet si médias présents', (
|
||||
tester,
|
||||
) async {
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||
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(
|
||||
imageMediaIds: const ['image-1'],
|
||||
videoMediaId: 'video-1',
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: _activeUseCases(
|
||||
_FakeActiveSessionRepository(),
|
||||
clock,
|
||||
),
|
||||
closeUseCase: _closeUseCase(_FakeActiveSessionRepository(), clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byTooltip('Voir les médias de l’exercice'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Voir médias'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Médias de l’exercice'), findsOneWidget);
|
||||
expect(find.text('Images'), findsOneWidget);
|
||||
expect(find.text('Vidéo'), findsOneWidget);
|
||||
expect(find.text('1/1'), findsOneWidget);
|
||||
expect(find.text('Image 1'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('la galerie médias navigue entre les images', (tester) async {
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||
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(
|
||||
imageMediaIds: const ['image-1', 'image-2', 'image-3'],
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: _activeUseCases(
|
||||
_FakeActiveSessionRepository(),
|
||||
clock,
|
||||
),
|
||||
closeUseCase: _closeUseCase(_FakeActiveSessionRepository(), clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Voir médias'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('1/3'), findsOneWidget);
|
||||
|
||||
await tester.drag(find.byType(PageView), const Offset(-400, 0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('2/3'), findsOneWidget);
|
||||
expect(find.text('Image 2'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('un repos actif est repris avec le temps restant recalculé', (
|
||||
tester,
|
||||
) async {
|
||||
@ -559,6 +684,8 @@ String _sessionSnapshot({
|
||||
double targetScore = 80,
|
||||
String scoreUnit = 'kg',
|
||||
ScoreInputMode scoreInputMode = ScoreInputMode.manual,
|
||||
List<String> imageMediaIds = const [],
|
||||
String? videoMediaId,
|
||||
}) {
|
||||
return jsonEncode({
|
||||
'name': 'Séance jambes',
|
||||
@ -582,6 +709,10 @@ String _sessionSnapshot({
|
||||
'scoreLabelSnapshot': 'Score',
|
||||
'scoreUnitSnapshot': scoreUnit,
|
||||
'restSecondsOverride': restSeconds,
|
||||
'exerciseImageMediaIdsSnapshot': imageMediaIds,
|
||||
'imageMediaIdsSnapshot': imageMediaIds,
|
||||
'exerciseVideoMediaIdSnapshot': videoMediaId,
|
||||
'videoMediaIdSnapshot': videoMediaId,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user