feat(exercice): modèle domain + Drift pour exercices à étapes (ticket #56)

Étend le modèle Drift (tables.dart, app_database.dart/.g.dart,
migration schemaVersion 7→8) et la couche application (entités, use
cases, repositories) pour supporter des exercices composés de
plusieurs étapes. flutter pub get OK, build_runner OK, flutter
analyze propre (mêmes 8 infos préexistantes), 87/87 tests verts,
build APK debug validé. Premier ticket du chantier "Exercice à
plusieurs étapes" (#54).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 19:32:24 +02:00
parent 73d01371d4
commit b1182db32e
8 changed files with 2958 additions and 6 deletions

View File

@ -107,6 +107,50 @@ class ExerciseImages extends SyncableTable {
];
}
class ExerciseSteps extends SyncableTable {
@override
String get tableName => 'exercise_steps';
TextColumn get exerciseId => text().references(Exercises, #id)();
IntColumn get position => integer()();
TextColumn get name => text().withLength(min: 1)();
TextColumn get type => text()();
IntColumn get defaultTargetValue => integer()();
BoolColumn get hasScore => boolean()();
TextColumn get scoreInputMode => text().nullable()();
TextColumn get scoreLabel => text().nullable()();
TextColumn get scoreUnit => text().nullable()();
RealColumn get defaultTargetScore => real().nullable()();
IntColumn get defaultTargetScoreTimeMs => integer().nullable()();
@override
List<String> get customConstraints => [
"CHECK (type IN ('time', 'reps'))",
'CHECK (position >= 0 AND position < 8)',
'CHECK (default_target_value > 0)',
"CHECK (score_input_mode IS NULL OR score_input_mode IN ('manual', "
"'stopwatch'))",
'CHECK (has_score OR (score_input_mode IS NULL '
'AND score_label IS NULL AND score_unit IS NULL '
'AND default_target_score IS NULL '
'AND default_target_score_time_ms IS NULL))',
'CHECK (NOT has_score OR score_input_mode IS NOT NULL)',
"CHECK (NOT has_score OR score_input_mode != 'manual' OR "
'(score_label IS NOT NULL AND length(trim(score_label)) > 0 '
'AND score_unit IS NOT NULL AND length(trim(score_unit)) > 0))',
'CHECK (default_target_score IS NULL OR default_target_score >= 0)',
'CHECK (default_target_score_time_ms IS NULL OR '
'default_target_score_time_ms > 0)',
"CHECK (score_input_mode != 'manual' OR "
'default_target_score_time_ms IS NULL)',
"CHECK (score_input_mode != 'stopwatch' OR "
'(score_label IS NULL AND score_unit IS NULL '
'AND default_target_score IS NULL))',
'CHECK (default_target_score IS NULL OR '
'default_target_score_time_ms IS NULL)',
];
}
class Programs extends SyncableTable {
@override
String get tableName => 'programs';
@ -132,6 +176,7 @@ class ProgramExercises extends SyncableTable {
TextColumn get exerciseImageMediaIdSnapshot =>
text().nullable().references(MediaAssets, #id)();
TextColumn get exerciseImageMediaIdsSnapshotJson => text().nullable()();
TextColumn get exerciseStepsSnapshotJson => text().nullable()();
@ReferenceName('programExerciseVideoSnapshotReferences')
TextColumn get exerciseVideoMediaIdSnapshot =>