feat(exercice): exécution persistante des étapes et éditeur d'étapes (tickets #58, #59)

Ajoute la persistance de l'exécution par étapes avec résultats par
passage (ports, use cases, entités, modèle Drift avec migration) et
l'éditeur d'exercice avec séquence d'étapes sur
exercise_library_screen.dart. Développés dans le même worktree
partagé par DevBackend et DevFrontend ; commit combiné plutôt que
séparé car les fakes de repository de test corrigés (nouvelles
interfaces #58) touchent plusieurs écrans sans lien avec l'éditeur
lui-même (historique, accueil, exécution, séance-modèle). Corrige au
passage un ListTile sans ancêtre Material dans le nouvel éditeur
d'étapes (Material(type: MaterialType.transparency)). flutter pub get
OK, build_runner OK, analyze propre (mêmes infos préexistantes),
93/93 tests verts, build APK debug validé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 19:52:43 +02:00
parent 4731a257a1
commit ebba22db56
14 changed files with 11371 additions and 2 deletions

View File

@ -1311,6 +1311,8 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
final results = <ActiveSetResult>[];
final restStates = <String, ActiveRestState>{};
final scoreStopwatchStates = <String, ActiveScoreStopwatchState>{};
final stepProgressStates = <String, ActiveExerciseStepProgressState>{};
final stepResults = <ActiveExerciseStepResult>[];
@override
Future<ActiveWorkoutSession?> findById(String id) async {
@ -1342,6 +1344,25 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
.firstOrNull;
}
@override
Future<ActiveExerciseStepProgressState?> findExerciseStepProgressState({
required String sessionId,
required int programIndex,
required int exerciseIndex,
required int setIndex,
}) async {
return stepProgressStates.values
.where(
(state) =>
state.activeWorkoutSessionId == sessionId &&
state.programIndex == programIndex &&
state.exerciseIndex == exerciseIndex &&
state.setIndex == setIndex &&
state.metadata.deletedAt == null,
)
.firstOrNull;
}
@override
Future<List<ActiveRestState>> listRestStates(String sessionId) async {
return restStates.values
@ -1369,6 +1390,24 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
.toList();
}
@override
Future<List<ActiveExerciseStepProgressState>> listExerciseStepProgressStates(
String sessionId,
) async {
return stepProgressStates.values
.where((state) => state.activeWorkoutSessionId == sessionId)
.toList();
}
@override
Future<List<ActiveExerciseStepResult>> listExerciseStepResults(
String sessionId,
) async {
return stepResults
.where((result) => result.activeWorkoutSessionId == sessionId)
.toList();
}
@override
Future<void> save(ActiveWorkoutSession session) async {
this.session = session;
@ -1384,6 +1423,29 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
scoreStopwatchStates[state.metadata.id] = state;
}
@override
Future<void> saveExerciseStepProgressState(
ActiveExerciseStepProgressState state,
) async {
stepProgressStates[state.metadata.id] = state;
}
@override
Future<void> saveExerciseStepResult(
ActiveExerciseStepResult result,
) async {
stepResults.removeWhere(
(existing) =>
existing.activeWorkoutSessionId == result.activeWorkoutSessionId &&
existing.programIndex == result.programIndex &&
existing.exerciseIndex == result.exerciseIndex &&
existing.setIndex == result.setIndex &&
existing.passageIndex == result.passageIndex &&
existing.stepIndex == result.stepIndex,
);
stepResults.add(result);
}
@override
Future<void> deleteScoreStopwatchState({
required String sessionId,