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:
2026-07-18 14:16:31 +02:00
parent 7385c96517
commit ff6b79b66e
10 changed files with 654 additions and 1 deletions

View File

@ -37,7 +37,7 @@ final class AppDatabase extends _$AppDatabase {
}
@override
int get schemaVersion => 5;
int get schemaVersion => 6;
@override
MigrationStrategy get migration {
@ -67,6 +67,9 @@ final class AppDatabase extends _$AppDatabase {
if (from < 5) {
await _migrateToSchema5(migrator);
}
if (from < 6) {
await _migrateToSchema6();
}
await _createIndexes();
},
beforeOpen: (details) async {
@ -250,4 +253,11 @@ extension on AppDatabase {
'WHERE image_media_id IS NOT NULL',
);
}
Future<void> _migrateToSchema6() async {
await customStatement(
'ALTER TABLE program_exercises ADD COLUMN '
'exercise_image_media_ids_snapshot_json TEXT',
);
}
}

View File

@ -11148,6 +11148,17 @@ class $ProgramExercisesTable extends ProgramExercises
'REFERENCES media_assets (id)',
),
);
static const VerificationMeta _exerciseImageMediaIdsSnapshotJsonMeta =
const VerificationMeta('exerciseImageMediaIdsSnapshotJson');
@override
late final GeneratedColumn<String> exerciseImageMediaIdsSnapshotJson =
GeneratedColumn<String>(
'exercise_image_media_ids_snapshot_json',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _exerciseVideoMediaIdSnapshotMeta =
const VerificationMeta('exerciseVideoMediaIdSnapshot');
@override
@ -11380,6 +11391,7 @@ class $ProgramExercisesTable extends ProgramExercises
exerciseNameSnapshot,
exerciseDescriptionSnapshot,
exerciseImageMediaIdSnapshot,
exerciseImageMediaIdsSnapshotJson,
exerciseVideoMediaIdSnapshot,
exerciseArchivedSnapshot,
availableTimeSnapshot,
@ -11557,6 +11569,15 @@ class $ProgramExercisesTable extends ProgramExercises
),
);
}
if (data.containsKey('exercise_image_media_ids_snapshot_json')) {
context.handle(
_exerciseImageMediaIdsSnapshotJsonMeta,
exerciseImageMediaIdsSnapshotJson.isAcceptableOrUnknown(
data['exercise_image_media_ids_snapshot_json']!,
_exerciseImageMediaIdsSnapshotJsonMeta,
),
);
}
if (data.containsKey('exercise_video_media_id_snapshot')) {
context.handle(
_exerciseVideoMediaIdSnapshotMeta,
@ -11795,6 +11816,10 @@ class $ProgramExercisesTable extends ProgramExercises
DriftSqlType.string,
data['${effectivePrefix}exercise_image_media_id_snapshot'],
),
exerciseImageMediaIdsSnapshotJson: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}exercise_image_media_ids_snapshot_json'],
),
exerciseVideoMediaIdSnapshot: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}exercise_video_media_id_snapshot'],
@ -11890,6 +11915,7 @@ class ProgramExercise extends DataClass implements Insertable<ProgramExercise> {
final String exerciseNameSnapshot;
final String? exerciseDescriptionSnapshot;
final String? exerciseImageMediaIdSnapshot;
final String? exerciseImageMediaIdsSnapshotJson;
final String? exerciseVideoMediaIdSnapshot;
final bool exerciseArchivedSnapshot;
final bool availableTimeSnapshot;
@ -11925,6 +11951,7 @@ class ProgramExercise extends DataClass implements Insertable<ProgramExercise> {
required this.exerciseNameSnapshot,
this.exerciseDescriptionSnapshot,
this.exerciseImageMediaIdSnapshot,
this.exerciseImageMediaIdsSnapshotJson,
this.exerciseVideoMediaIdSnapshot,
required this.exerciseArchivedSnapshot,
required this.availableTimeSnapshot,
@ -11981,6 +12008,11 @@ class ProgramExercise extends DataClass implements Insertable<ProgramExercise> {
exerciseImageMediaIdSnapshot,
);
}
if (!nullToAbsent || exerciseImageMediaIdsSnapshotJson != null) {
map['exercise_image_media_ids_snapshot_json'] = Variable<String>(
exerciseImageMediaIdsSnapshotJson,
);
}
if (!nullToAbsent || exerciseVideoMediaIdSnapshot != null) {
map['exercise_video_media_id_snapshot'] = Variable<String>(
exerciseVideoMediaIdSnapshot,
@ -12056,6 +12088,10 @@ class ProgramExercise extends DataClass implements Insertable<ProgramExercise> {
exerciseImageMediaIdSnapshot == null && nullToAbsent
? const Value.absent()
: Value(exerciseImageMediaIdSnapshot),
exerciseImageMediaIdsSnapshotJson:
exerciseImageMediaIdsSnapshotJson == null && nullToAbsent
? const Value.absent()
: Value(exerciseImageMediaIdsSnapshotJson),
exerciseVideoMediaIdSnapshot:
exerciseVideoMediaIdSnapshot == null && nullToAbsent
? const Value.absent()
@ -12124,6 +12160,9 @@ class ProgramExercise extends DataClass implements Insertable<ProgramExercise> {
exerciseImageMediaIdSnapshot: serializer.fromJson<String?>(
json['exerciseImageMediaIdSnapshot'],
),
exerciseImageMediaIdsSnapshotJson: serializer.fromJson<String?>(
json['exerciseImageMediaIdsSnapshotJson'],
),
exerciseVideoMediaIdSnapshot: serializer.fromJson<String?>(
json['exerciseVideoMediaIdSnapshot'],
),
@ -12186,6 +12225,9 @@ class ProgramExercise extends DataClass implements Insertable<ProgramExercise> {
'exerciseImageMediaIdSnapshot': serializer.toJson<String?>(
exerciseImageMediaIdSnapshot,
),
'exerciseImageMediaIdsSnapshotJson': serializer.toJson<String?>(
exerciseImageMediaIdsSnapshotJson,
),
'exerciseVideoMediaIdSnapshot': serializer.toJson<String?>(
exerciseVideoMediaIdSnapshot,
),
@ -12230,6 +12272,7 @@ class ProgramExercise extends DataClass implements Insertable<ProgramExercise> {
String? exerciseNameSnapshot,
Value<String?> exerciseDescriptionSnapshot = const Value.absent(),
Value<String?> exerciseImageMediaIdSnapshot = const Value.absent(),
Value<String?> exerciseImageMediaIdsSnapshotJson = const Value.absent(),
Value<String?> exerciseVideoMediaIdSnapshot = const Value.absent(),
bool? exerciseArchivedSnapshot,
bool? availableTimeSnapshot,
@ -12275,6 +12318,9 @@ class ProgramExercise extends DataClass implements Insertable<ProgramExercise> {
exerciseImageMediaIdSnapshot: exerciseImageMediaIdSnapshot.present
? exerciseImageMediaIdSnapshot.value
: this.exerciseImageMediaIdSnapshot,
exerciseImageMediaIdsSnapshotJson: exerciseImageMediaIdsSnapshotJson.present
? exerciseImageMediaIdsSnapshotJson.value
: this.exerciseImageMediaIdsSnapshotJson,
exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot.present
? exerciseVideoMediaIdSnapshot.value
: this.exerciseVideoMediaIdSnapshot,
@ -12347,6 +12393,10 @@ class ProgramExercise extends DataClass implements Insertable<ProgramExercise> {
exerciseImageMediaIdSnapshot: data.exerciseImageMediaIdSnapshot.present
? data.exerciseImageMediaIdSnapshot.value
: this.exerciseImageMediaIdSnapshot,
exerciseImageMediaIdsSnapshotJson:
data.exerciseImageMediaIdsSnapshotJson.present
? data.exerciseImageMediaIdsSnapshotJson.value
: this.exerciseImageMediaIdsSnapshotJson,
exerciseVideoMediaIdSnapshot: data.exerciseVideoMediaIdSnapshot.present
? data.exerciseVideoMediaIdSnapshot.value
: this.exerciseVideoMediaIdSnapshot,
@ -12421,6 +12471,9 @@ class ProgramExercise extends DataClass implements Insertable<ProgramExercise> {
..write(
'exerciseImageMediaIdSnapshot: $exerciseImageMediaIdSnapshot, ',
)
..write(
'exerciseImageMediaIdsSnapshotJson: $exerciseImageMediaIdsSnapshotJson, ',
)
..write(
'exerciseVideoMediaIdSnapshot: $exerciseVideoMediaIdSnapshot, ',
)
@ -12463,6 +12516,7 @@ class ProgramExercise extends DataClass implements Insertable<ProgramExercise> {
exerciseNameSnapshot,
exerciseDescriptionSnapshot,
exerciseImageMediaIdSnapshot,
exerciseImageMediaIdsSnapshotJson,
exerciseVideoMediaIdSnapshot,
exerciseArchivedSnapshot,
availableTimeSnapshot,
@ -12504,6 +12558,8 @@ class ProgramExercise extends DataClass implements Insertable<ProgramExercise> {
this.exerciseDescriptionSnapshot &&
other.exerciseImageMediaIdSnapshot ==
this.exerciseImageMediaIdSnapshot &&
other.exerciseImageMediaIdsSnapshotJson ==
this.exerciseImageMediaIdsSnapshotJson &&
other.exerciseVideoMediaIdSnapshot ==
this.exerciseVideoMediaIdSnapshot &&
other.exerciseArchivedSnapshot == this.exerciseArchivedSnapshot &&
@ -12542,6 +12598,7 @@ class ProgramExercisesCompanion extends UpdateCompanion<ProgramExercise> {
final Value<String> exerciseNameSnapshot;
final Value<String?> exerciseDescriptionSnapshot;
final Value<String?> exerciseImageMediaIdSnapshot;
final Value<String?> exerciseImageMediaIdsSnapshotJson;
final Value<String?> exerciseVideoMediaIdSnapshot;
final Value<bool> exerciseArchivedSnapshot;
final Value<bool> availableTimeSnapshot;
@ -12578,6 +12635,7 @@ class ProgramExercisesCompanion extends UpdateCompanion<ProgramExercise> {
this.exerciseNameSnapshot = const Value.absent(),
this.exerciseDescriptionSnapshot = const Value.absent(),
this.exerciseImageMediaIdSnapshot = const Value.absent(),
this.exerciseImageMediaIdsSnapshotJson = const Value.absent(),
this.exerciseVideoMediaIdSnapshot = const Value.absent(),
this.exerciseArchivedSnapshot = const Value.absent(),
this.availableTimeSnapshot = const Value.absent(),
@ -12615,6 +12673,7 @@ class ProgramExercisesCompanion extends UpdateCompanion<ProgramExercise> {
required String exerciseNameSnapshot,
this.exerciseDescriptionSnapshot = const Value.absent(),
this.exerciseImageMediaIdSnapshot = const Value.absent(),
this.exerciseImageMediaIdsSnapshotJson = const Value.absent(),
this.exerciseVideoMediaIdSnapshot = const Value.absent(),
this.exerciseArchivedSnapshot = const Value.absent(),
required bool availableTimeSnapshot,
@ -12667,6 +12726,7 @@ class ProgramExercisesCompanion extends UpdateCompanion<ProgramExercise> {
Expression<String>? exerciseNameSnapshot,
Expression<String>? exerciseDescriptionSnapshot,
Expression<String>? exerciseImageMediaIdSnapshot,
Expression<String>? exerciseImageMediaIdsSnapshotJson,
Expression<String>? exerciseVideoMediaIdSnapshot,
Expression<bool>? exerciseArchivedSnapshot,
Expression<bool>? availableTimeSnapshot,
@ -12708,6 +12768,9 @@ class ProgramExercisesCompanion extends UpdateCompanion<ProgramExercise> {
'exercise_description_snapshot': exerciseDescriptionSnapshot,
if (exerciseImageMediaIdSnapshot != null)
'exercise_image_media_id_snapshot': exerciseImageMediaIdSnapshot,
if (exerciseImageMediaIdsSnapshotJson != null)
'exercise_image_media_ids_snapshot_json':
exerciseImageMediaIdsSnapshotJson,
if (exerciseVideoMediaIdSnapshot != null)
'exercise_video_media_id_snapshot': exerciseVideoMediaIdSnapshot,
if (exerciseArchivedSnapshot != null)
@ -12755,6 +12818,7 @@ class ProgramExercisesCompanion extends UpdateCompanion<ProgramExercise> {
Value<String>? exerciseNameSnapshot,
Value<String?>? exerciseDescriptionSnapshot,
Value<String?>? exerciseImageMediaIdSnapshot,
Value<String?>? exerciseImageMediaIdsSnapshotJson,
Value<String?>? exerciseVideoMediaIdSnapshot,
Value<bool>? exerciseArchivedSnapshot,
Value<bool>? availableTimeSnapshot,
@ -12794,6 +12858,9 @@ class ProgramExercisesCompanion extends UpdateCompanion<ProgramExercise> {
exerciseDescriptionSnapshot ?? this.exerciseDescriptionSnapshot,
exerciseImageMediaIdSnapshot:
exerciseImageMediaIdSnapshot ?? this.exerciseImageMediaIdSnapshot,
exerciseImageMediaIdsSnapshotJson:
exerciseImageMediaIdsSnapshotJson ??
this.exerciseImageMediaIdsSnapshotJson,
exerciseVideoMediaIdSnapshot:
exerciseVideoMediaIdSnapshot ?? this.exerciseVideoMediaIdSnapshot,
exerciseArchivedSnapshot:
@ -12883,6 +12950,11 @@ class ProgramExercisesCompanion extends UpdateCompanion<ProgramExercise> {
exerciseImageMediaIdSnapshot.value,
);
}
if (exerciseImageMediaIdsSnapshotJson.present) {
map['exercise_image_media_ids_snapshot_json'] = Variable<String>(
exerciseImageMediaIdsSnapshotJson.value,
);
}
if (exerciseVideoMediaIdSnapshot.present) {
map['exercise_video_media_id_snapshot'] = Variable<String>(
exerciseVideoMediaIdSnapshot.value,
@ -12974,6 +13046,9 @@ class ProgramExercisesCompanion extends UpdateCompanion<ProgramExercise> {
..write(
'exerciseImageMediaIdSnapshot: $exerciseImageMediaIdSnapshot, ',
)
..write(
'exerciseImageMediaIdsSnapshotJson: $exerciseImageMediaIdsSnapshotJson, ',
)
..write(
'exerciseVideoMediaIdSnapshot: $exerciseVideoMediaIdSnapshot, ',
)
@ -25793,6 +25868,7 @@ typedef $$ProgramExercisesTableCreateCompanionBuilder =
required String exerciseNameSnapshot,
Value<String?> exerciseDescriptionSnapshot,
Value<String?> exerciseImageMediaIdSnapshot,
Value<String?> exerciseImageMediaIdsSnapshotJson,
Value<String?> exerciseVideoMediaIdSnapshot,
Value<bool> exerciseArchivedSnapshot,
required bool availableTimeSnapshot,
@ -25831,6 +25907,7 @@ typedef $$ProgramExercisesTableUpdateCompanionBuilder =
Value<String> exerciseNameSnapshot,
Value<String?> exerciseDescriptionSnapshot,
Value<String?> exerciseImageMediaIdSnapshot,
Value<String?> exerciseImageMediaIdsSnapshotJson,
Value<String?> exerciseVideoMediaIdSnapshot,
Value<bool> exerciseArchivedSnapshot,
Value<bool> availableTimeSnapshot,
@ -26019,6 +26096,12 @@ class $$ProgramExercisesTableFilterComposer
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get exerciseImageMediaIdsSnapshotJson =>
$composableBuilder(
column: $table.exerciseImageMediaIdsSnapshotJson,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<bool> get exerciseArchivedSnapshot => $composableBuilder(
column: $table.exerciseArchivedSnapshot,
builder: (column) => ColumnFilters(column),
@ -26271,6 +26354,12 @@ class $$ProgramExercisesTableOrderingComposer
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get exerciseImageMediaIdsSnapshotJson =>
$composableBuilder(
column: $table.exerciseImageMediaIdsSnapshotJson,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<bool> get exerciseArchivedSnapshot => $composableBuilder(
column: $table.exerciseArchivedSnapshot,
builder: (column) => ColumnOrderings(column),
@ -26511,6 +26600,12 @@ class $$ProgramExercisesTableAnnotationComposer
builder: (column) => column,
);
GeneratedColumn<String> get exerciseImageMediaIdsSnapshotJson =>
$composableBuilder(
column: $table.exerciseImageMediaIdsSnapshotJson,
builder: (column) => column,
);
GeneratedColumn<bool> get exerciseArchivedSnapshot => $composableBuilder(
column: $table.exerciseArchivedSnapshot,
builder: (column) => column,
@ -26736,6 +26831,8 @@ class $$ProgramExercisesTableTableManager
const Value.absent(),
Value<String?> exerciseImageMediaIdSnapshot =
const Value.absent(),
Value<String?> exerciseImageMediaIdsSnapshotJson =
const Value.absent(),
Value<String?> exerciseVideoMediaIdSnapshot =
const Value.absent(),
Value<bool> exerciseArchivedSnapshot = const Value.absent(),
@ -26773,6 +26870,8 @@ class $$ProgramExercisesTableTableManager
exerciseNameSnapshot: exerciseNameSnapshot,
exerciseDescriptionSnapshot: exerciseDescriptionSnapshot,
exerciseImageMediaIdSnapshot: exerciseImageMediaIdSnapshot,
exerciseImageMediaIdsSnapshotJson:
exerciseImageMediaIdsSnapshotJson,
exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot,
exerciseArchivedSnapshot: exerciseArchivedSnapshot,
availableTimeSnapshot: availableTimeSnapshot,
@ -26813,6 +26912,8 @@ class $$ProgramExercisesTableTableManager
const Value.absent(),
Value<String?> exerciseImageMediaIdSnapshot =
const Value.absent(),
Value<String?> exerciseImageMediaIdsSnapshotJson =
const Value.absent(),
Value<String?> exerciseVideoMediaIdSnapshot =
const Value.absent(),
Value<bool> exerciseArchivedSnapshot = const Value.absent(),
@ -26850,6 +26951,8 @@ class $$ProgramExercisesTableTableManager
exerciseNameSnapshot: exerciseNameSnapshot,
exerciseDescriptionSnapshot: exerciseDescriptionSnapshot,
exerciseImageMediaIdSnapshot: exerciseImageMediaIdSnapshot,
exerciseImageMediaIdsSnapshotJson:
exerciseImageMediaIdsSnapshotJson,
exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot,
exerciseArchivedSnapshot: exerciseArchivedSnapshot,
availableTimeSnapshot: availableTimeSnapshot,

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:drift/drift.dart';
import '../../application/application.dart';
@ -1208,6 +1210,9 @@ db.ProgramExercisesCompanion _programExerciseCompanion(
exerciseNameSnapshot: Value(exercise.exerciseNameSnapshot),
exerciseDescriptionSnapshot: Value(exercise.exerciseDescriptionSnapshot),
exerciseImageMediaIdSnapshot: Value(exercise.exerciseImageMediaIdSnapshot),
exerciseImageMediaIdsSnapshotJson: Value(
_encodeImageMediaIdsSnapshot(exercise.exerciseImageMediaIdsSnapshot),
),
exerciseVideoMediaIdSnapshot: Value(exercise.exerciseVideoMediaIdSnapshot),
exerciseArchivedSnapshot: Value(exercise.exerciseArchivedSnapshot),
availableTimeSnapshot: Value(exercise.availableTimeSnapshot),
@ -1239,6 +1244,10 @@ domain.ProgramExercise _programExerciseFromRow(db.ProgramExercise row) {
exerciseNameSnapshot: row.exerciseNameSnapshot,
exerciseDescriptionSnapshot: row.exerciseDescriptionSnapshot,
exerciseImageMediaIdSnapshot: row.exerciseImageMediaIdSnapshot,
exerciseImageMediaIdsSnapshot: _decodeImageMediaIdsSnapshot(
row.exerciseImageMediaIdsSnapshotJson,
row.exerciseImageMediaIdSnapshot,
),
exerciseVideoMediaIdSnapshot: row.exerciseVideoMediaIdSnapshot,
exerciseArchivedSnapshot: row.exerciseArchivedSnapshot,
availableTimeSnapshot: row.availableTimeSnapshot,
@ -1766,6 +1775,29 @@ domain.ActiveScoreStopwatchStatus _scoreStopwatchStatusFromDb(String value) =>
),
};
String? _encodeImageMediaIdsSnapshot(List<String> imageMediaIds) {
return imageMediaIds.isEmpty ? null : jsonEncode(imageMediaIds);
}
List<String> _decodeImageMediaIdsSnapshot(
String? encoded,
String? fallbackImageMediaId,
) {
if (encoded == null || encoded.trim().isEmpty) {
return [if (fallbackImageMediaId != null) fallbackImageMediaId];
}
final decoded = jsonDecode(encoded);
if (decoded is! List) {
throw const domain.DomainException(
'Invalid exercise image media ids snapshot.',
);
}
return decoded
.whereType<String>()
.where((id) => id.trim().isNotEmpty)
.toList(growable: false);
}
domain.ActiveWorkoutStatus _activeStatusFromDb(String value) => switch (value) {
'running' => domain.ActiveWorkoutStatus.running,
'paused' => domain.ActiveWorkoutStatus.paused,

View File

@ -128,6 +128,7 @@ class ProgramExercises extends SyncableTable {
@ReferenceName('programExerciseImageSnapshotReferences')
TextColumn get exerciseImageMediaIdSnapshot =>
text().nullable().references(MediaAssets, #id)();
TextColumn get exerciseImageMediaIdsSnapshotJson => text().nullable()();
@ReferenceName('programExerciseVideoSnapshotReferences')
TextColumn get exerciseVideoMediaIdSnapshot =>