feat(ui): écran Historique des séances

Ajoute l'écran d'historique des séances (presentation/history_screen.dart),
avec les ajustements application/domaine/repositories nécessaires et la
navigation depuis l'accueil. Complète le dernier écran du périmètre UI.
flutter analyze propre, 18/18 tests verts, build APK debug validé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 19:17:55 +02:00
parent d8cb9b9385
commit 34b3638ce3
11 changed files with 1026 additions and 3 deletions

View File

@ -428,6 +428,52 @@ final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository {
final db.AppDatabase database;
@override
Future<domain.WorkoutHistory?> findById(String id) async {
final row =
await (database.select(
database.workoutHistories,
)..where((table) => table.id.equals(id) & table.deletedAt.isNull()))
.getSingleOrNull();
if (row == null) {
return null;
}
final results =
await (database.select(database.workoutHistorySetResults)
..where(
(table) =>
table.workoutHistoryId.equals(id) &
table.deletedAt.isNull(),
)
..orderBy([
(table) => OrderingTerm.asc(table.programIndex),
(table) => OrderingTerm.asc(table.exerciseIndex),
(table) => OrderingTerm.asc(table.setIndex),
]))
.get();
return _workoutHistoryFromRow(
row,
results.map(_workoutHistorySetResultFromRow).toList(),
);
}
@override
Future<List<domain.WorkoutHistory>> listActive() async {
final rows =
await (database.select(database.workoutHistories)
..where((table) => table.deletedAt.isNull())
..orderBy([(table) => OrderingTerm.desc(table.startedAt)]))
.get();
final histories = <domain.WorkoutHistory>[];
for (final row in rows) {
final history = await findById(row.id);
if (history != null) {
histories.add(history);
}
}
return histories;
}
@override
Future<void> save(domain.WorkoutHistory history) async {
await database
@ -444,6 +490,28 @@ final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository {
.into(database.workoutHistorySetResults)
.insertOnConflictUpdate(_workoutHistorySetResultCompanion(result));
}
@override
Future<void> delete(String id, DateTime deletedAt) async {
await database.transaction(() async {
await (database.update(
database.workoutHistories,
)..where((table) => table.id.equals(id))).write(
db.WorkoutHistoriesCompanion(
deletedAt: Value(deletedAt),
updatedAt: Value(deletedAt),
),
);
await (database.update(
database.workoutHistorySetResults,
)..where((table) => table.workoutHistoryId.equals(id))).write(
db.WorkoutHistorySetResultsCompanion(
deletedAt: Value(deletedAt),
updatedAt: Value(deletedAt),
),
);
});
}
}
domain.EntityMetadata _metadataFromRow(dynamic row) {
@ -986,6 +1054,53 @@ db.WorkoutHistorySetResultsCompanion _workoutHistorySetResultCompanion(
);
}
domain.WorkoutHistory _workoutHistoryFromRow(
db.WorkoutHistory row,
List<domain.WorkoutHistorySetResult> results,
) {
return domain.WorkoutHistory(
metadata: _metadataFromRow(row),
sourceWorkoutTemplateId: row.sourceWorkoutTemplateId,
sourceActiveWorkoutSessionId: row.sourceActiveWorkoutSessionId,
nameSnapshot: row.nameSnapshot,
startedAt: row.startedAt,
endedAt: row.endedAt,
totalActiveMs: row.totalActiveMs,
completed: row.completed,
historySnapshotJson: row.historySnapshotJson,
results: results,
);
}
domain.WorkoutHistorySetResult _workoutHistorySetResultFromRow(
db.WorkoutHistorySetResult row,
) {
return domain.WorkoutHistorySetResult(
metadata: _metadataFromRow(row),
workoutHistoryId: row.workoutHistoryId,
programSnapshotId: row.programSnapshotId,
exerciseSnapshotId: row.exerciseSnapshotId,
programIndex: row.programIndex,
exerciseIndex: row.exerciseIndex,
setIndex: row.setIndex,
programNameSnapshot: row.programNameSnapshot,
exerciseNameSnapshot: row.exerciseNameSnapshot,
timeEnabledSnapshot: row.timeEnabledSnapshot,
repsEnabledSnapshot: row.repsEnabledSnapshot,
scoreEnabledSnapshot: row.scoreEnabledSnapshot,
targetTimeSecondsSnapshot: row.targetTimeSecondsSnapshot,
targetRepsSnapshot: row.targetRepsSnapshot,
targetScoreSnapshot: row.targetScoreSnapshot,
actualTimeMs: row.actualTimeMs,
actualReps: row.actualReps,
actualScore: row.actualScore,
scoreLabelSnapshot: row.scoreLabelSnapshot,
scoreUnitSnapshot: row.scoreUnitSnapshot,
startedAt: row.startedAt,
completedAt: row.completedAt,
);
}
String _syncStateToDb(domain.SyncState state) => switch (state) {
domain.SyncState.localOnly => 'localOnly',
domain.SyncState.dirty => 'dirty',