Files
GameTime/lib/application/starter_content/starter_content.dart
Blomios 24e266c0e9 feat(bibliothèque): migration Drift v16, seed basket et seeding au démarrage (ticket #80)
Ajoute la migration Drift v16 (enum ExerciseCategory, champ isExample sur
exercises/programs/workout_templates, table local_seed_metadata), le
contenu seed basket déterministe (21 exercices, 1 programme, 1 séance-
modèle) dans lib/application/starter_content/, les ports
StarterSeedStateRepository/StarterContentRepository et le use case
SeedStarterContentUseCase branché dans AppBootstrap.create().

Validé GO par QA/Main : dart analyze propre, suite complète 167 tests
(4 échecs préexistants sans rapport avec ce lot), 4 nouveaux tests seed
tous verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 07:29:49 +02:00

205 lines
5.7 KiB
Dart

import '../../domain/domain.dart';
import '../ports.dart';
const int starterSeedVersion = 1;
final class StarterExerciseSeed {
const StarterExerciseSeed({
required this.id,
required this.name,
required this.description,
required this.category,
this.hasTimeMeasure = false,
this.hasRepsMeasure = false,
this.hasScoreMeasure = false,
this.scoreInputMode = ScoreInputMode.manual,
this.scoreLabel,
this.scoreUnit,
this.defaultTargetTimeSeconds,
this.defaultTargetReps,
this.defaultTargetScore,
this.defaultTargetScoreTimeMs,
this.steps = const [],
});
final String id;
final String name;
final String description;
final ExerciseCategory category;
final bool hasTimeMeasure;
final bool hasRepsMeasure;
final bool hasScoreMeasure;
final ScoreInputMode scoreInputMode;
final String? scoreLabel;
final String? scoreUnit;
final int? defaultTargetTimeSeconds;
final int? defaultTargetReps;
final double? defaultTargetScore;
final int? defaultTargetScoreTimeMs;
final List<StarterExerciseStepSeed> steps;
}
final class StarterExerciseStepSeed {
const StarterExerciseStepSeed({
required this.id,
required this.name,
required this.type,
required this.defaultTargetValue,
});
final String id;
final String name;
final ExerciseStepType type;
final int defaultTargetValue;
}
final class StarterProgramSeed {
const StarterProgramSeed({
required this.id,
required this.name,
required this.defaultRestSeconds,
required this.exercises,
});
final String id;
final String name;
final int defaultRestSeconds;
final List<StarterProgramExerciseSeed> exercises;
}
final class StarterProgramExerciseSeed {
const StarterProgramExerciseSeed({
required this.id,
required this.exerciseId,
required this.setsCount,
required this.enabledMeasures,
this.targetTimeSeconds,
this.targetReps,
this.targetScore,
this.targetScoreTimeMs,
this.restSecondsOverride,
});
final String id;
final String exerciseId;
final int setsCount;
final Set<WorkoutMeasure> enabledMeasures;
final int? targetTimeSeconds;
final int? targetReps;
final double? targetScore;
final int? targetScoreTimeMs;
final int? restSecondsOverride;
}
final class StarterWorkoutTemplateSeed {
const StarterWorkoutTemplateSeed({
required this.id,
required this.name,
required this.programSnapshotId,
});
final String id;
final String name;
final String programSnapshotId;
}
StarterContent buildStarterContent({
required List<StarterExerciseSeed> exerciseSeeds,
required StarterProgramSeed programSeed,
required StarterWorkoutTemplateSeed templateSeed,
required DateTime now,
required String originDeviceId,
}) {
final exercisesById = <String, Exercise>{};
for (final seed in exerciseSeeds) {
exercisesById[seed.id] = Exercise(
metadata: _metadata(seed.id, now, originDeviceId),
name: seed.name,
description: seed.description,
hasTimeMeasure: seed.hasTimeMeasure,
hasRepsMeasure: seed.hasRepsMeasure,
hasScoreMeasure: seed.hasScoreMeasure,
scoreInputMode: seed.scoreInputMode,
scoreLabel: seed.scoreLabel,
scoreUnit: seed.scoreUnit,
defaultTargetTimeSeconds: seed.defaultTargetTimeSeconds,
defaultTargetReps: seed.defaultTargetReps,
defaultTargetScore: seed.defaultTargetScore,
defaultTargetScoreTimeMs: seed.defaultTargetScoreTimeMs,
category: seed.category,
isExample: true,
steps: [
for (var index = 0; index < seed.steps.length; index += 1)
ExerciseStep(
id: seed.steps[index].id,
position: index,
name: seed.steps[index].name,
type: seed.steps[index].type,
defaultTargetValue: seed.steps[index].defaultTargetValue,
),
],
);
}
final programExercises = <ProgramExercise>[];
for (var index = 0; index < programSeed.exercises.length; index += 1) {
final seed = programSeed.exercises[index];
final exercise = exercisesById[seed.exerciseId];
if (exercise == null) {
throw DomainException('Unknown starter exercise id: ${seed.exerciseId}');
}
programExercises.add(
ProgramExercise.snapshotFromExercise(
metadata: _metadata(seed.id, now, originDeviceId),
programId: programSeed.id,
exercise: exercise,
position: index,
setsCount: seed.setsCount,
enabledMeasures: seed.enabledMeasures,
targetTimeSeconds: seed.targetTimeSeconds,
targetReps: seed.targetReps,
targetScore: seed.targetScore,
targetScoreTimeMs: seed.targetScoreTimeMs,
restSecondsOverride: seed.restSecondsOverride,
),
);
}
final program = Program(
metadata: _metadata(programSeed.id, now, originDeviceId),
name: programSeed.name,
defaultRestSeconds: programSeed.defaultRestSeconds,
isExample: true,
exercises: programExercises,
);
final templateProgram = WorkoutTemplateProgram.snapshotFromProgram(
metadata: _metadata(templateSeed.programSnapshotId, now, originDeviceId),
workoutTemplateId: templateSeed.id,
program: program,
position: 0,
);
final template = WorkoutTemplate(
metadata: _metadata(templateSeed.id, now, originDeviceId),
name: templateSeed.name,
isExample: true,
programs: [templateProgram],
);
return StarterContent(
exercises: exercisesById.values.toList(growable: false),
program: program,
workoutTemplate: template,
);
}
EntityMetadata _metadata(String id, DateTime now, String originDeviceId) {
return EntityMetadata(
id: id,
createdAt: now,
updatedAt: now,
originDeviceId: originDeviceId,
syncState: SyncState.localOnly,
localRevision: 0,
);
}