feat(ui): écran Exécution de séance

Ajoute l'écran d'exécution de séance (presentation/workout_execution_screen.dart),
avec les ajustements application/domaine/repositories nécessaires et la
navigation depuis l'accueil et l'écran séance-modèle. flutter analyze
propre, 15/15 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:08:23 +02:00
parent 6bc09fe1ca
commit bfd727a6a2
9 changed files with 1159 additions and 8 deletions

View File

@ -8,6 +8,8 @@ final class AppBootstrap {
required this.mediaUseCases,
required this.programUseCases,
required this.workoutTemplateUseCases,
required this.activeWorkoutSessionUseCases,
required this.closeWorkoutSessionUseCase,
});
final AppDatabase database;
@ -15,6 +17,8 @@ final class AppBootstrap {
final MediaUseCases mediaUseCases;
final ProgramUseCases programUseCases;
final WorkoutTemplateUseCases workoutTemplateUseCases;
final ActiveWorkoutSessionUseCases activeWorkoutSessionUseCases;
final CloseWorkoutSessionUseCase closeWorkoutSessionUseCase;
static Future<AppBootstrap> create() async {
final database = AppDatabase.open();
@ -23,6 +27,8 @@ final class AppBootstrap {
final mediaRepository = DriftMediaAssetRepository(database);
final programRepository = DriftProgramRepository(database);
final templateRepository = DriftWorkoutTemplateRepository(database);
final activeSessionRepository = DriftActiveSessionRepository(database);
final historyRepository = DriftWorkoutHistoryRepository(database);
final ids = LocalIdGenerator();
const clock = SystemClock();
const originDeviceId = 'local-device';
@ -57,6 +63,20 @@ final class AppBootstrap {
ids: ids,
originDeviceId: originDeviceId,
),
activeWorkoutSessionUseCases: ActiveWorkoutSessionUseCases(
sessionRepository: activeSessionRepository,
templateRepository: templateRepository,
clock: clock,
ids: ids,
originDeviceId: originDeviceId,
),
closeWorkoutSessionUseCase: CloseWorkoutSessionUseCase(
sessionRepository: activeSessionRepository,
historyRepository: historyRepository,
clock: clock,
ids: ids,
originDeviceId: originDeviceId,
),
);
}

View File

@ -76,6 +76,7 @@ abstract interface class WorkoutTemplateRepository {
abstract interface class ActiveSessionRepository {
Future<ActiveWorkoutSession?> findById(String id);
Future<ActiveWorkoutSession?> findOpen();
Future<void> save(ActiveWorkoutSession session);
Future<void> saveSetResult(ActiveSetResult result);
Future<void> saveRestState(ActiveRestState restState);

View File

@ -590,6 +590,8 @@ final class ActiveWorkoutSessionUseCases {
final IdGenerator ids;
final String originDeviceId;
Future<ActiveWorkoutSession?> findOpen() => sessionRepository.findOpen();
Future<ActiveWorkoutSession> startFromTemplate(String templateId) async {
final template = await templateRepository.findById(templateId);
if (template == null) {
@ -659,6 +661,97 @@ final class ActiveWorkoutSessionUseCases {
return result;
}
Future<ActiveSetResult> recordCurrentSetResult({
required String sessionId,
required String programSnapshotId,
required String exerciseSnapshotId,
required int programIndex,
required int exerciseIndex,
required int setIndex,
int? actualTimeMs,
int? actualReps,
double? actualScore,
String? scoreLabelSnapshot,
String? scoreUnitSnapshot,
}) async {
final now = clock.now();
final result = ActiveSetResult(
metadata: _newMetadata(ids, originDeviceId, now),
activeWorkoutSessionId: sessionId,
programSnapshotId: programSnapshotId,
exerciseSnapshotId: exerciseSnapshotId,
programIndex: programIndex,
exerciseIndex: exerciseIndex,
setIndex: setIndex,
completedAt: now,
actualTimeMs: actualTimeMs,
actualReps: actualReps,
actualScore: actualScore,
scoreLabelSnapshot: scoreLabelSnapshot,
scoreUnitSnapshot: scoreUnitSnapshot,
);
await sessionRepository.saveSetResult(result);
return result;
}
Future<ActiveWorkoutSession> updateProgress({
required String sessionId,
required int programIndex,
required int exerciseIndex,
required int setIndex,
}) async {
final session = await _requiredSession(sessionId);
final updated = session.copyWith(
currentProgramIndex: programIndex,
currentExerciseIndex: exerciseIndex,
currentSetIndex: setIndex,
metadata: session.metadata.touch(clock.now()),
);
await sessionRepository.save(updated);
return updated;
}
Future<ActiveWorkoutSession> quitAndSave(String sessionId) async {
final session = await _requiredSession(sessionId);
final now = clock.now();
final saved = session.copyWith(
status: ActiveWorkoutStatus.savedExit,
lastPersistedAt: now,
elapsedActiveMs: session.elapsedActiveMillisecondsAt(now),
metadata: session.metadata.touch(now),
);
await sessionRepository.save(saved);
return saved;
}
Future<ActiveWorkoutSession> abandon(String sessionId) async {
final session = await _requiredSession(sessionId);
final now = clock.now();
final abandoned = session.copyWith(
status: ActiveWorkoutStatus.abandoned,
endedAt: now,
lastPersistedAt: now,
elapsedActiveMs: session.elapsedActiveMillisecondsAt(now),
metadata: session.metadata.touch(now),
);
await sessionRepository.save(abandoned);
return abandoned;
}
Future<ActiveWorkoutSession> complete(String sessionId) async {
final session = await _requiredSession(sessionId);
final now = clock.now();
final completed = session.copyWith(
status: ActiveWorkoutStatus.completed,
endedAt: now,
lastPersistedAt: now,
elapsedActiveMs: session.elapsedActiveMillisecondsAt(now),
metadata: session.metadata.touch(now),
);
await sessionRepository.save(completed);
return completed;
}
Future<ActiveRestState> startRestAfterSet({
required String sessionId,
required int afterProgramIndex,