feat(ui): écran Composition de séance-modèle

Ajoute l'écran de composition de séance-modèle
(presentation/workout_template_screen.dart), avec les ajustements
application/domaine/repositories nécessaires et la navigation depuis
l'accueil. flutter analyze propre, 12/12 tests verts, build APK debug
validé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 18:59:59 +02:00
parent be02bbfeea
commit 89d89a3005
9 changed files with 1029 additions and 7 deletions

View File

@ -398,6 +398,14 @@ final class WorkoutTemplateUseCases {
final IdGenerator ids;
final String originDeviceId;
Future<WorkoutTemplate?> findById(String id) {
return templateRepository.findById(id);
}
Future<List<WorkoutTemplate>> listActive() {
return templateRepository.listActive();
}
Future<WorkoutTemplate> create({required String name}) async {
final now = clock.now();
final template = WorkoutTemplate(
@ -408,6 +416,78 @@ final class WorkoutTemplateUseCases {
return template;
}
Future<WorkoutTemplate> saveConfigured({
String? id,
required String name,
required List<WorkoutTemplateProgramConfig> programs,
required List<WorkoutTemplateExerciseOverrideConfig> overrides,
}) async {
final now = clock.now();
final existing = id == null ? null : await templateRepository.findById(id);
if (id != null && existing == null) {
throw const DomainException('Workout template not found.');
}
final templateId = id ?? ids.newId();
final metadata =
existing?.metadata.touch(now) ??
EntityMetadata(
id: templateId,
createdAt: now,
updatedAt: now,
originDeviceId: originDeviceId,
);
final templatePrograms = <WorkoutTemplateProgram>[];
final programIdsByClientKey = <String, String>{};
for (var index = 0; index < programs.length; index++) {
final input = programs[index];
final programMetadata =
input.existingMetadata?.touch(now) ??
_newMetadata(ids, originDeviceId, now);
programIdsByClientKey[input.clientKey] = programMetadata.id;
templatePrograms.add(
WorkoutTemplateProgram(
metadata: programMetadata,
workoutTemplateId: templateId,
sourceProgramId: input.sourceProgramId,
position: index,
programNameSnapshot: input.programNameSnapshot,
defaultRestSecondsSnapshot: input.defaultRestSecondsSnapshot,
programSnapshotJson: input.programSnapshotJson,
),
);
}
final templateOverrides = <WorkoutTemplateExerciseOverride>[];
for (final input in overrides) {
final workoutTemplateProgramId =
programIdsByClientKey[input.workoutTemplateProgramClientKey];
if (workoutTemplateProgramId == null) {
throw const DomainException('Workout template program not found.');
}
templateOverrides.add(
WorkoutTemplateExerciseOverride(
metadata:
input.existingMetadata?.touch(now) ??
_newMetadata(ids, originDeviceId, now),
workoutTemplateProgramId: workoutTemplateProgramId,
snapshotProgramExerciseId: input.snapshotProgramExerciseId,
setsCountOverride: input.setsCountOverride,
targetTimeSecondsOverride: input.targetTimeSecondsOverride,
targetRepsOverride: input.targetRepsOverride,
targetScoreOverride: input.targetScoreOverride,
),
);
}
final template = WorkoutTemplate(
metadata: metadata,
name: name,
lastStartedAt: existing?.lastStartedAt,
programs: templatePrograms,
overrides: templateOverrides,
);
await templateRepository.replaceComposition(template, now);
return template;
}
Future<WorkoutTemplateProgram> addProgramSnapshot({
required String workoutTemplateId,
required String programId,
@ -457,6 +537,44 @@ final class WorkoutTemplateUseCases {
}
}
final class WorkoutTemplateProgramConfig {
const WorkoutTemplateProgramConfig({
required this.clientKey,
this.existingMetadata,
this.sourceProgramId,
required this.programNameSnapshot,
required this.defaultRestSecondsSnapshot,
required this.programSnapshotJson,
});
final String clientKey;
final EntityMetadata? existingMetadata;
final String? sourceProgramId;
final String programNameSnapshot;
final int defaultRestSecondsSnapshot;
final String programSnapshotJson;
}
final class WorkoutTemplateExerciseOverrideConfig {
const WorkoutTemplateExerciseOverrideConfig({
this.existingMetadata,
required this.workoutTemplateProgramClientKey,
required this.snapshotProgramExerciseId,
this.setsCountOverride,
this.targetTimeSecondsOverride,
this.targetRepsOverride,
this.targetScoreOverride,
});
final EntityMetadata? existingMetadata;
final String workoutTemplateProgramClientKey;
final String snapshotProgramExerciseId;
final int? setsCountOverride;
final int? targetTimeSecondsOverride;
final int? targetRepsOverride;
final double? targetScoreOverride;
}
final class ActiveWorkoutSessionUseCases {
const ActiveWorkoutSessionUseCases({
required this.sessionRepository,