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

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import '../application/application.dart';
import '../domain/domain.dart';
import 'history_screen.dart';
enum WorkoutExecutionMode { active, rest, paused, finished }
@ -13,12 +14,16 @@ final class WorkoutExecutionScreen extends StatefulWidget {
required this.initialSession,
required this.activeUseCases,
required this.closeUseCase,
required this.historyUseCases,
required this.workoutTemplateUseCases,
super.key,
});
final ActiveWorkoutSession initialSession;
final ActiveWorkoutSessionUseCases activeUseCases;
final CloseWorkoutSessionUseCase closeUseCase;
final WorkoutHistoryUseCases historyUseCases;
final WorkoutTemplateUseCases workoutTemplateUseCases;
@override
State<WorkoutExecutionScreen> createState() => _WorkoutExecutionScreenState();
@ -30,6 +35,7 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
late WorkoutExecutionMode _mode;
Timer? _ticker;
Timer? _restTicker;
WorkoutHistory? _completedHistory;
var _reps = 0;
final _scoreController = TextEditingController();
var _remainingRestSeconds = 0;
@ -232,9 +238,12 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
const SizedBox(height: 12),
Text('Temps total : ${_formatDuration(elapsed)}'),
const SizedBox(height: 24),
OutlinedButton(onPressed: () {}, child: const Text('Voir le détail')),
OutlinedButton(
onPressed: () {},
onPressed: _completedHistory == null ? null : _openHistoryDetail,
child: const Text('Voir le détail'),
),
OutlinedButton(
onPressed: _completedHistory == null ? null : _restartCompleted,
child: const Text('Relancer cette séance'),
),
FilledButton(
@ -367,7 +376,7 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
Future<void> _complete() async {
_session = await widget.activeUseCases.complete(_session.metadata.id);
await widget.closeUseCase.close(
_completedHistory = await widget.closeUseCase.close(
sessionId: _session.metadata.id,
nameSnapshot: _plan.name,
completed: true,
@ -375,6 +384,56 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
if (!mounted) return;
setState(() => _mode = WorkoutExecutionMode.finished);
}
Future<void> _openHistoryDetail() async {
final history = _completedHistory;
if (history == null) return;
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => HistoryDetailScreen(
history: history,
historyUseCases: widget.historyUseCases,
workoutTemplateUseCases: widget.workoutTemplateUseCases,
activeUseCases: widget.activeUseCases,
closeUseCase: widget.closeUseCase,
),
),
);
}
Future<void> _restartCompleted() async {
final history = _completedHistory;
if (history == null) return;
ActiveWorkoutSession session;
final sourceId = history.sourceWorkoutTemplateId;
if (sourceId != null &&
await widget.workoutTemplateUseCases.findById(sourceId) != null) {
session = await widget.activeUseCases.startFromTemplate(sourceId);
} else {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"La séance originale n'existe plus. Une copie va être utilisée.",
),
),
);
}
session = await widget.activeUseCases.startFromHistory(history);
}
if (!mounted) return;
await Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => WorkoutExecutionScreen(
initialSession: session,
activeUseCases: widget.activeUseCases,
closeUseCase: widget.closeUseCase,
historyUseCases: widget.historyUseCases,
workoutTemplateUseCases: widget.workoutTemplateUseCases,
),
),
);
}
}
final class SetMeasureInput extends StatelessWidget {