feat(execution): refonte de l'UI de l'écran de séance (ticket #93)
Refonte de workout_execution_screen.dart cadrée UX+Architect ; résout au passage la dette de tests du ticket #90 (3 échecs préexistants désormais verts). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -141,7 +141,10 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
|||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
title: Text(_plan.name),
|
title: _ExecutionAppBarTitle(
|
||||||
|
workoutName: _plan.name,
|
||||||
|
programName: _plan.programAt(_position).name,
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Voir le plan',
|
tooltip: 'Voir le plan',
|
||||||
@ -183,7 +186,6 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
|||||||
exercise: _exercise,
|
exercise: _exercise,
|
||||||
currentSet: _position.setIndex + 1,
|
currentSet: _position.setIndex + 1,
|
||||||
totalSets: _exercise.setsCount,
|
totalSets: _exercise.setsCount,
|
||||||
progressLabel: _plan.progressLabel(_position),
|
|
||||||
setTimerLabel: _setTimerLabel,
|
setTimerLabel: _setTimerLabel,
|
||||||
setTimerComplete: _setTimer?.status == ActiveSetTimerStatus.stopped,
|
setTimerComplete: _setTimer?.status == ActiveSetTimerStatus.stopped,
|
||||||
showStartButton: _shouldShowStartExerciseButton,
|
showStartButton: _shouldShowStartExerciseButton,
|
||||||
@ -191,13 +193,6 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
|||||||
onOpenMedia: _exercise.hasMedia
|
onOpenMedia: _exercise.hasMedia
|
||||||
? () => _openExerciseMedia(_exercise)
|
? () => _openExerciseMedia(_exercise)
|
||||||
: null,
|
: null,
|
||||||
elapsedLabel: _formatDuration(
|
|
||||||
Duration(
|
|
||||||
milliseconds: widget.activeUseCases.elapsedActiveMilliseconds(
|
|
||||||
_session,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_performanceReference != null) ...[
|
if (_performanceReference != null) ...[
|
||||||
@ -237,16 +232,16 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
|||||||
onResetStepScore: _resetStepScore,
|
onResetStepScore: _resetStepScore,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
if (_exercise.manualScoreEnabled) ...[
|
||||||
SizedBox(
|
const SizedBox(height: 8),
|
||||||
height: _exercise.manualScoreEnabled ? 66 : 48,
|
SizedBox(
|
||||||
child: _StepSetResultSummary(
|
height: 66,
|
||||||
view: _stepProgress,
|
child: _StepSetResultSummary(
|
||||||
exercise: _exercise,
|
exercise: _exercise,
|
||||||
reps: _reps,
|
scoreController: _scoreController,
|
||||||
scoreController: _scoreController,
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
if (_exercise.stopwatchScoreEnabled) ...[
|
if (_exercise.stopwatchScoreEnabled) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@ -1230,7 +1225,7 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
Navigator.of(context).pop(_MissingSetTimerAction.start),
|
Navigator.of(context).pop(_MissingSetTimerAction.start),
|
||||||
child: const Text('Démarrer l’exercice'),
|
child: const Text('Démarrer'),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () => Navigator.of(
|
onPressed: () => Navigator.of(
|
||||||
@ -1264,7 +1259,7 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
Navigator.of(context).pop(_MissingStopwatchAction.start),
|
Navigator.of(context).pop(_MissingStopwatchAction.start),
|
||||||
child: const Text('Démarrer l’exercice'),
|
child: const Text('Démarrer'),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () => Navigator.of(
|
onPressed: () => Navigator.of(
|
||||||
@ -1757,7 +1752,9 @@ final class SetMeasureInput extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
exercise.targetTimeSeconds == null
|
exercise.targetTimeSeconds == null
|
||||||
? 'Chronométrer'
|
? 'Chronométrer'
|
||||||
: '${exercise.targetTimeSeconds} s',
|
: _formatDuration(
|
||||||
|
Duration(seconds: exercise.targetTimeSeconds!),
|
||||||
|
),
|
||||||
style: AppTextStyles.scoreNumber(context),
|
style: AppTextStyles.scoreNumber(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -1800,13 +1797,46 @@ final class SetMeasureInput extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class _ExecutionAppBarTitle extends StatelessWidget {
|
||||||
|
const _ExecutionAppBarTitle({
|
||||||
|
required this.workoutName,
|
||||||
|
required this.programName,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String workoutName;
|
||||||
|
final String programName;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final tokens = courtBlazerTokensOf(context);
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
workoutName,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).appBarTheme.titleTextStyle,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
programName,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.bodySmall?.copyWith(color: tokens.mutedText),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class _ExecutionContextHeader extends StatelessWidget {
|
final class _ExecutionContextHeader extends StatelessWidget {
|
||||||
const _ExecutionContextHeader({
|
const _ExecutionContextHeader({
|
||||||
required this.exercise,
|
required this.exercise,
|
||||||
required this.currentSet,
|
required this.currentSet,
|
||||||
required this.totalSets,
|
required this.totalSets,
|
||||||
required this.progressLabel,
|
|
||||||
required this.elapsedLabel,
|
|
||||||
required this.setTimerLabel,
|
required this.setTimerLabel,
|
||||||
required this.setTimerComplete,
|
required this.setTimerComplete,
|
||||||
required this.showStartButton,
|
required this.showStartButton,
|
||||||
@ -1817,8 +1847,6 @@ final class _ExecutionContextHeader extends StatelessWidget {
|
|||||||
final ExecutionExercise exercise;
|
final ExecutionExercise exercise;
|
||||||
final int currentSet;
|
final int currentSet;
|
||||||
final int totalSets;
|
final int totalSets;
|
||||||
final String progressLabel;
|
|
||||||
final String elapsedLabel;
|
|
||||||
final String? setTimerLabel;
|
final String? setTimerLabel;
|
||||||
final bool setTimerComplete;
|
final bool setTimerComplete;
|
||||||
final bool showStartButton;
|
final bool showStartButton;
|
||||||
@ -1834,40 +1862,39 @@ final class _ExecutionContextHeader extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 76,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'SÉRIE',
|
||||||
|
style: theme.textTheme.labelMedium?.copyWith(
|
||||||
|
color: tokens.mutedText,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
'$currentSet / $totalSets',
|
||||||
|
maxLines: 1,
|
||||||
|
style: AppTextStyles.scoreNumber(
|
||||||
|
context,
|
||||||
|
).copyWith(color: theme.colorScheme.primary),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 4,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Série $currentSet/$totalSets',
|
|
||||||
style: theme.textTheme.labelLarge?.copyWith(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
progressLabel,
|
|
||||||
style: theme.textTheme.labelMedium?.copyWith(
|
|
||||||
color: tokens.mutedText,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
elapsedLabel,
|
|
||||||
style: theme.textTheme.labelMedium?.copyWith(
|
|
||||||
color: tokens.mutedText,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
Text(
|
||||||
exercise.name,
|
exercise.name,
|
||||||
maxLines: 2,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: theme.textTheme.titleLarge,
|
style: theme.textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
@ -1896,7 +1923,7 @@ final class _ExecutionContextHeader extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.play_arrow),
|
icon: const Icon(Icons.play_arrow),
|
||||||
label: const Text('Démarrer l’exercice'),
|
label: const Text('Démarrer'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (onOpenMedia != null) ...[
|
if (onOpenMedia != null) ...[
|
||||||
@ -1951,71 +1978,38 @@ final class _SetActionBar extends StatelessWidget {
|
|||||||
|
|
||||||
final class _StepSetResultSummary extends StatelessWidget {
|
final class _StepSetResultSummary extends StatelessWidget {
|
||||||
const _StepSetResultSummary({
|
const _StepSetResultSummary({
|
||||||
required this.view,
|
|
||||||
required this.exercise,
|
required this.exercise,
|
||||||
required this.reps,
|
|
||||||
required this.scoreController,
|
required this.scoreController,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ActiveExerciseStepProgressView? view;
|
|
||||||
final ExecutionExercise exercise;
|
final ExecutionExercise exercise;
|
||||||
final int reps;
|
|
||||||
final TextEditingController scoreController;
|
final TextEditingController scoreController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final completedPassages = _completedPassagesFor(view);
|
|
||||||
final expectedPassages = view?.expectedPassages ?? reps;
|
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final tokens = courtBlazerTokensOf(context);
|
final tokens = courtBlazerTokensOf(context);
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surface,
|
color: theme.colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
border: Border.all(color: tokens.border),
|
border: Border.all(color: tokens.border),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: TextField(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
controller: scoreController,
|
||||||
children: [
|
decoration: InputDecoration(
|
||||||
Expanded(
|
isDense: true,
|
||||||
child: Text(
|
labelText: exercise.scoreUnit == null
|
||||||
expectedPassages > 0
|
? 'Score'
|
||||||
? 'Passages réalisés : $completedPassages / $expectedPassages'
|
: 'Score (${exercise.scoreUnit})',
|
||||||
: 'Passages réalisés : $completedPassages',
|
),
|
||||||
style: theme.textTheme.titleSmall,
|
keyboardType: TextInputType.number,
|
||||||
),
|
|
||||||
),
|
|
||||||
if (exercise.manualScoreEnabled) ...[
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
SizedBox(
|
|
||||||
width: 128,
|
|
||||||
child: TextField(
|
|
||||||
controller: scoreController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
isDense: true,
|
|
||||||
labelText: exercise.scoreUnit == null
|
|
||||||
? 'Score'
|
|
||||||
: 'Score (${exercise.scoreUnit})',
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int _completedPassagesFor(ActiveExerciseStepProgressView? view) {
|
|
||||||
if (view == null) return 0;
|
|
||||||
if (view.state.status == ActiveExerciseStepProgressStatus.sequenceComplete) {
|
|
||||||
return view.expectedPassages;
|
|
||||||
}
|
|
||||||
return view.state.currentPassageIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum _MissingSetTimerAction { start, finishWithout }
|
enum _MissingSetTimerAction { start, finishWithout }
|
||||||
|
|
||||||
enum _MissingStopwatchAction { start, finishWithout }
|
enum _MissingStopwatchAction { start, finishWithout }
|
||||||
@ -2221,29 +2215,27 @@ final class _CurrentStepPane extends StatelessWidget {
|
|||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Flexible(
|
Flexible(
|
||||||
fit: FlexFit.tight,
|
fit: FlexFit.tight,
|
||||||
child: LayoutBuilder(
|
child: step.type == ExerciseStepType.time
|
||||||
builder: (context, constraints) {
|
? _TimedStepBody(
|
||||||
final body = step.type == ExerciseStepType.time
|
step: step,
|
||||||
? _TimedStepBody(
|
remainingLabel: remainingLabel,
|
||||||
step: step,
|
running:
|
||||||
remainingLabel: remainingLabel,
|
view.state.status ==
|
||||||
running:
|
ActiveExerciseStepProgressStatus.runningTimer,
|
||||||
view.state.status ==
|
readyToStart: _isNextTimedStepReady(view),
|
||||||
ActiveExerciseStepProgressStatus.runningTimer,
|
onStartTimer: onStartTimer,
|
||||||
readyToStart: _isNextTimedStepReady(view),
|
)
|
||||||
onStartTimer: onStartTimer,
|
: Align(
|
||||||
)
|
|
||||||
: _RepsStepBody(step: step, onCompleteStep: onCompleteStep);
|
|
||||||
return Align(
|
|
||||||
alignment: Alignment.topCenter,
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
child: SizedBox(width: constraints.maxWidth, child: body),
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: _RepsStepBody(
|
||||||
|
step: step,
|
||||||
|
onCompleteStep: onCompleteStep,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (step.hasScore) ...[
|
if (step.hasScore) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@ -2321,34 +2313,37 @@ final class _TimedStepBody extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Text('Objectif : ${step.defaultTargetValue} s'),
|
Text('Objectif : ${_formatShortSeconds(step.defaultTargetValue)}'),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 4),
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
remainingLabel,
|
remainingLabel,
|
||||||
style: AppTextStyles.timer(
|
style: Theme.of(context).textTheme.displayMedium?.copyWith(
|
||||||
context,
|
fontFamily: 'Anton',
|
||||||
).copyWith(color: Theme.of(context).colorScheme.primary),
|
fontFeatures: AppTextStyles.timer(context).fontFeatures,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!running) ...[
|
if (!running) ...[
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 8),
|
||||||
if (readyToStart) ...[
|
if (readyToStart) ...[
|
||||||
Text(
|
Text(
|
||||||
'Chrono suivant prêt',
|
'Chrono prêt',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 4),
|
||||||
],
|
],
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: onStartTimer,
|
onPressed: onStartTimer,
|
||||||
icon: const Icon(Icons.play_arrow),
|
style: FilledButton.styleFrom(
|
||||||
label: Text(
|
minimumSize: const Size.fromHeight(48),
|
||||||
readyToStart ? 'Démarrer le chrono' : 'Démarrer la séquence',
|
|
||||||
),
|
),
|
||||||
|
icon: const Icon(Icons.play_arrow),
|
||||||
|
label: const Text('Lancer'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -2444,7 +2439,7 @@ final class _StepScoreInput extends StatelessWidget {
|
|||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: running ? onStop : onStart,
|
onPressed: running ? onStop : onStart,
|
||||||
icon: Icon(running ? Icons.stop : Icons.play_arrow),
|
icon: Icon(running ? Icons.stop : Icons.play_arrow),
|
||||||
label: Text(running ? 'Arrêter' : 'Démarrer'),
|
label: Text(running ? 'Arrêter' : 'Lancer'),
|
||||||
),
|
),
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: onReset,
|
onPressed: onReset,
|
||||||
@ -2620,7 +2615,7 @@ final class _ScoreStopwatchInput extends StatelessWidget {
|
|||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: onStart,
|
onPressed: onStart,
|
||||||
icon: const Icon(Icons.play_arrow),
|
icon: const Icon(Icons.play_arrow),
|
||||||
label: const Text('Démarrer'),
|
label: const Text('Lancer'),
|
||||||
)
|
)
|
||||||
else if (running)
|
else if (running)
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
@ -4089,7 +4084,7 @@ String? _formatSetPerformance(
|
|||||||
if (performance == null) return null;
|
if (performance == null) return null;
|
||||||
final values = [
|
final values = [
|
||||||
if (measures.timeEnabled && performance.actualTimeMs != null)
|
if (measures.timeEnabled && performance.actualTimeMs != null)
|
||||||
_formatReferenceTime(performance.actualTimeMs!, stopwatch: false),
|
_formatReferenceMeasureTime(performance.actualTimeMs!),
|
||||||
if (measures.repsEnabled && performance.actualReps != null)
|
if (measures.repsEnabled && performance.actualReps != null)
|
||||||
_formatReferenceReps(
|
_formatReferenceReps(
|
||||||
performance.actualReps!,
|
performance.actualReps!,
|
||||||
@ -4117,7 +4112,7 @@ String? _formatMetricPerformance(
|
|||||||
PerformanceMetric.time =>
|
PerformanceMetric.time =>
|
||||||
performance.actualTimeMs == null
|
performance.actualTimeMs == null
|
||||||
? null
|
? null
|
||||||
: _formatReferenceTime(performance.actualTimeMs!, stopwatch: false),
|
: _formatReferenceMeasureTime(performance.actualTimeMs!),
|
||||||
PerformanceMetric.reps =>
|
PerformanceMetric.reps =>
|
||||||
performance.actualReps == null
|
performance.actualReps == null
|
||||||
? null
|
? null
|
||||||
@ -4129,10 +4124,7 @@ String? _formatMetricPerformance(
|
|||||||
performance.scoreInputModeSnapshot == ScoreInputMode.stopwatch
|
performance.scoreInputModeSnapshot == ScoreInputMode.stopwatch
|
||||||
? performance.actualScoreTimeMs == null
|
? performance.actualScoreTimeMs == null
|
||||||
? null
|
? null
|
||||||
: _formatReferenceTime(
|
: _formatReferenceStopwatchScore(performance.actualScoreTimeMs!)
|
||||||
performance.actualScoreTimeMs!,
|
|
||||||
stopwatch: true,
|
|
||||||
)
|
|
||||||
: performance.actualScore == null
|
: performance.actualScore == null
|
||||||
? null
|
? null
|
||||||
: _formatReferenceManualScore(performance.actualScore!, scoreUnit),
|
: _formatReferenceManualScore(performance.actualScore!, scoreUnit),
|
||||||
@ -4162,7 +4154,7 @@ String? _formatReferenceScore({
|
|||||||
if (scoreInputMode == ScoreInputMode.stopwatch) {
|
if (scoreInputMode == ScoreInputMode.stopwatch) {
|
||||||
return actualScoreTimeMs == null
|
return actualScoreTimeMs == null
|
||||||
? null
|
? null
|
||||||
: _formatReferenceTime(actualScoreTimeMs, stopwatch: true);
|
: _formatReferenceStopwatchScore(actualScoreTimeMs);
|
||||||
}
|
}
|
||||||
return actualScore == null
|
return actualScore == null
|
||||||
? null
|
? null
|
||||||
@ -4180,22 +4172,27 @@ String _formatReferenceManualScore(double score, String? scoreUnit) {
|
|||||||
return '$value $unit';
|
return '$value $unit';
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatReferenceTime(int milliseconds, {required bool stopwatch}) {
|
String _formatReferenceMeasureTime(int milliseconds) {
|
||||||
final totalTenths = (milliseconds / 100).round();
|
final totalSeconds = (milliseconds / 1000).round().clamp(0, 999999).toInt();
|
||||||
final minutes = totalTenths ~/ 600;
|
if (totalSeconds < 60) {
|
||||||
final seconds = (totalTenths ~/ 10) % 60;
|
return '$totalSeconds s';
|
||||||
final tenths = totalTenths % 10;
|
|
||||||
if (minutes == 0) {
|
|
||||||
if (!stopwatch && tenths == 0) {
|
|
||||||
return '$seconds s';
|
|
||||||
}
|
|
||||||
return '${(seconds + tenths / 10).toStringAsFixed(1)} s';
|
|
||||||
}
|
}
|
||||||
final paddedSeconds = seconds.toString().padLeft(2, '0');
|
final minutes = totalSeconds ~/ 60;
|
||||||
if (stopwatch) {
|
final seconds = (totalSeconds % 60).toString().padLeft(2, '0');
|
||||||
return '$minutes:$paddedSeconds.$tenths';
|
return '$minutes:$seconds';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatReferenceStopwatchScore(int milliseconds) {
|
||||||
|
return _formatScoreStopwatch(Duration(milliseconds: milliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatShortSeconds(int seconds) {
|
||||||
|
if (seconds < 60) {
|
||||||
|
return '$seconds s';
|
||||||
}
|
}
|
||||||
return '$minutes:$paddedSeconds';
|
final minutes = seconds ~/ 60;
|
||||||
|
final remainingSeconds = (seconds % 60).toString().padLeft(2, '0');
|
||||||
|
return '$minutes:$remainingSeconds';
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> _imageMediaIdsFromSnapshot(Map<String, dynamic> exercise) {
|
List<String> _imageMediaIdsFromSnapshot(Map<String, dynamic> exercise) {
|
||||||
|
|||||||
@ -42,7 +42,7 @@ void main() {
|
|||||||
|
|
||||||
expect(find.text('Squat'), findsOneWidget);
|
expect(find.text('Squat'), findsOneWidget);
|
||||||
expect(find.text('Temps de série : 00:45'), findsOneWidget);
|
expect(find.text('Temps de série : 00:45'), findsOneWidget);
|
||||||
expect(find.text('Démarrer l’exercice'), findsOneWidget);
|
expect(find.widgetWithText(FilledButton, 'Démarrer'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('les répétitions sont initialisées avec la cible', (
|
testWidgets('les répétitions sont initialisées avec la cible', (
|
||||||
@ -81,6 +81,73 @@ void main() {
|
|||||||
expect(find.text('8'), findsOneWidget);
|
expect(find.text('8'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('les références de temps sont formatées sans valeurs brutes', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||||
|
final repository = _FakeActiveSessionRepository();
|
||||||
|
final performanceRepository = _FakeExercisePerformanceReferenceRepository(
|
||||||
|
last: WorkoutHistorySetPerformance(
|
||||||
|
workoutHistoryId: 'history-1',
|
||||||
|
startedAt: DateTime.utc(2026, 7, 12),
|
||||||
|
setIndex: 0,
|
||||||
|
exerciseNameSnapshot: 'Squat',
|
||||||
|
scoreInputModeSnapshot: ScoreInputMode.stopwatch,
|
||||||
|
actualTimeMs: 72000,
|
||||||
|
actualScoreTimeMs: 45100,
|
||||||
|
),
|
||||||
|
record: WorkoutHistoryMetricPerformance(
|
||||||
|
workoutHistoryId: 'history-2',
|
||||||
|
startedAt: DateTime.utc(2026, 7, 15),
|
||||||
|
setIndex: 0,
|
||||||
|
exerciseNameSnapshot: 'Squat',
|
||||||
|
metric: PerformanceMetric.score,
|
||||||
|
scoreInputModeSnapshot: ScoreInputMode.stopwatch,
|
||||||
|
actualScoreTimeMs: 42800,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final session = ActiveWorkoutSession(
|
||||||
|
metadata: _metadata('session-1'),
|
||||||
|
sourceWorkoutTemplateId: 'template-1',
|
||||||
|
status: ActiveWorkoutStatus.running,
|
||||||
|
startedAt: DateTime.utc(2026, 7, 17, 12),
|
||||||
|
lastPersistedAt: DateTime.utc(2026, 7, 17, 12),
|
||||||
|
elapsedActiveMs: 0,
|
||||||
|
currentProgramIndex: 0,
|
||||||
|
currentExerciseIndex: 0,
|
||||||
|
currentSetIndex: 0,
|
||||||
|
resolvedTemplateSnapshotJson: _sessionSnapshot(
|
||||||
|
sourceExerciseId: 'exercise-source-1',
|
||||||
|
scoreInputMode: ScoreInputMode.stopwatch,
|
||||||
|
targetTimeSeconds: 300,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
repository.session = session;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: WorkoutExecutionScreen(
|
||||||
|
initialSession: session,
|
||||||
|
activeUseCases: _activeUseCases(repository, clock),
|
||||||
|
closeUseCase: _closeUseCase(repository, clock),
|
||||||
|
historyUseCases: _historyUseCases(clock),
|
||||||
|
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||||
|
performanceReferenceUseCase: ExercisePerformanceReferenceUseCase(
|
||||||
|
repository: performanceRepository,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.text('Temps de série : 05:00'), findsOneWidget);
|
||||||
|
expect(find.text('1:12 · 00:45.1'), findsOneWidget);
|
||||||
|
expect(find.text('00:42.8'), findsOneWidget);
|
||||||
|
expect(find.textContaining('72000'), findsNothing);
|
||||||
|
expect(find.textContaining('42800'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('les mesures cumulées respectent la hiérarchie visuelle', (
|
testWidgets('les mesures cumulées respectent la hiérarchie visuelle', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
@ -379,7 +446,7 @@ void main() {
|
|||||||
expect(find.text('Chrono score'), findsOneWidget);
|
expect(find.text('Chrono score'), findsOneWidget);
|
||||||
expect(find.text('00:00.0'), findsOneWidget);
|
expect(find.text('00:00.0'), findsOneWidget);
|
||||||
|
|
||||||
await tester.tap(find.widgetWithText(FilledButton, 'Démarrer'));
|
await tester.tap(find.widgetWithText(FilledButton, 'Lancer'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 0, 100);
|
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 0, 100);
|
||||||
await tester.pump(const Duration(milliseconds: 100));
|
await tester.pump(const Duration(milliseconds: 100));
|
||||||
@ -447,13 +514,10 @@ void main() {
|
|||||||
|
|
||||||
expect(find.text('Chrono non lancé'), findsOneWidget);
|
expect(find.text('Chrono non lancé'), findsOneWidget);
|
||||||
expect(
|
expect(
|
||||||
find.text("Tu n’as pas démarré le chrono de cette série."),
|
find.text('Tu n’as pas démarré le chrono de cette série.'),
|
||||||
findsOneWidget,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
find.widgetWithText(TextButton, 'Démarrer l’exercice'),
|
|
||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
);
|
);
|
||||||
|
expect(find.widgetWithText(TextButton, 'Démarrer'), findsOneWidget);
|
||||||
expect(find.text('Terminer sans chrono'), findsOneWidget);
|
expect(find.text('Terminer sans chrono'), findsOneWidget);
|
||||||
expect(repository.results, isEmpty);
|
expect(repository.results, isEmpty);
|
||||||
});
|
});
|
||||||
@ -495,7 +559,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.tap(find.text('Démarrer l’exercice'));
|
await tester.tap(find.widgetWithText(FilledButton, 'Démarrer').first);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 2, 500);
|
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 2, 500);
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
@ -662,7 +726,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await tester.tap(find.text('Démarrer l’exercice'));
|
await tester.tap(find.widgetWithText(FilledButton, 'Démarrer').first);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 2);
|
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 2);
|
||||||
await tester.pump(const Duration(seconds: 2));
|
await tester.pump(const Duration(seconds: 2));
|
||||||
@ -713,12 +777,12 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(find.text('Programme 1/1 · Exercice 1/1'), findsOneWidget);
|
expect(find.text('Programme jambes'), findsOneWidget);
|
||||||
expect(find.text('Série 2/3'), findsOneWidget);
|
expect(find.text('2 / 3'), findsOneWidget);
|
||||||
expect(find.text('Squat'), findsOneWidget);
|
expect(find.text('Squat'), findsOneWidget);
|
||||||
expect(find.text('Temps de série : 00:30'), findsOneWidget);
|
expect(find.text('Temps de série : 00:30'), findsOneWidget);
|
||||||
|
|
||||||
await tester.tap(find.text('Démarrer l’exercice'));
|
await tester.tap(find.widgetWithText(FilledButton, 'Démarrer').first);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
for (var i = 0; i < 2; i++) {
|
for (var i = 0; i < 2; i++) {
|
||||||
@ -738,8 +802,8 @@ void main() {
|
|||||||
expect(repository.results.single.actualReps, 3);
|
expect(repository.results.single.actualReps, 3);
|
||||||
expect(repository.results.single.actualScore, 5);
|
expect(repository.results.single.actualScore, 5);
|
||||||
expect(repository.session?.currentSetIndex, 2);
|
expect(repository.session?.currentSetIndex, 2);
|
||||||
expect(find.text('Programme 1/1 · Exercice 1/1'), findsOneWidget);
|
expect(find.text('Programme jambes'), findsOneWidget);
|
||||||
expect(find.text('Série 3/3'), findsOneWidget);
|
expect(find.text('3 / 3'), findsOneWidget);
|
||||||
expect(find.text('5'), findsOneWidget);
|
expect(find.text('5'), findsOneWidget);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -1025,8 +1089,8 @@ void main() {
|
|||||||
await tester.tap(find.byTooltip('Fermer'));
|
await tester.tap(find.byTooltip('Fermer'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.text('Programme 1/1 · Exercice 1/1'), findsOneWidget);
|
expect(find.text('Programme jambes'), findsOneWidget);
|
||||||
expect(find.text('Série 3/4'), findsOneWidget);
|
expect(find.text('3 / 4'), findsOneWidget);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1071,8 +1135,8 @@ void main() {
|
|||||||
|
|
||||||
expect(find.text('Plan de séance'), findsNothing);
|
expect(find.text('Plan de séance'), findsNothing);
|
||||||
expect(find.text('Modifier la série'), findsNothing);
|
expect(find.text('Modifier la série'), findsNothing);
|
||||||
expect(find.text('Programme 1/1 · Exercice 1/1'), findsOneWidget);
|
expect(find.text('Programme jambes'), findsOneWidget);
|
||||||
expect(find.text('Série 2/3'), findsOneWidget);
|
expect(find.text('2 / 3'), findsOneWidget);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1127,6 +1191,72 @@ void main() {
|
|||||||
expect(find.text('00:10'), findsOneWidget);
|
expect(find.text('00:10'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('l’écran actif avec séquence longue reste sans scroll', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await tester.binding.setSurfaceSize(const Size(400, 900));
|
||||||
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||||
|
|
||||||
|
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||||
|
final repository = _FakeActiveSessionRepository();
|
||||||
|
final session = ActiveWorkoutSession(
|
||||||
|
metadata: _metadata('session-1'),
|
||||||
|
sourceWorkoutTemplateId: 'template-1',
|
||||||
|
status: ActiveWorkoutStatus.running,
|
||||||
|
startedAt: DateTime.utc(2026, 7, 17, 12),
|
||||||
|
lastPersistedAt: DateTime.utc(2026, 7, 17, 12),
|
||||||
|
elapsedActiveMs: 0,
|
||||||
|
currentProgramIndex: 0,
|
||||||
|
currentExerciseIndex: 0,
|
||||||
|
currentSetIndex: 0,
|
||||||
|
resolvedTemplateSnapshotJson: _sessionSnapshot(
|
||||||
|
setsCount: 4,
|
||||||
|
workoutName: 'Séance tirs du soir très complète',
|
||||||
|
programName:
|
||||||
|
'Programme finition longue avec nom volontairement interminable',
|
||||||
|
exerciseName: 'Dribble combo finition contact côté faible très long',
|
||||||
|
targetReps: 10,
|
||||||
|
scoreUnit: 'paniers',
|
||||||
|
exerciseSteps: [
|
||||||
|
_step(
|
||||||
|
'step-1',
|
||||||
|
0,
|
||||||
|
'Dribble main droite enchaîné avec finition longue',
|
||||||
|
ExerciseStepType.time,
|
||||||
|
90,
|
||||||
|
),
|
||||||
|
_step('step-2', 1, 'Appuis explosifs', ExerciseStepType.reps, 8),
|
||||||
|
_step('step-3', 2, 'Tir après contact', ExerciseStepType.reps, 6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
repository.session = session;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: WorkoutExecutionScreen(
|
||||||
|
initialSession: session,
|
||||||
|
activeUseCases: _activeUseCases(repository, clock),
|
||||||
|
stepUseCases: _stepUseCases(repository, clock),
|
||||||
|
stepAudioCuePlayer: _FakeStepAudioCuePlayer(),
|
||||||
|
closeUseCase: _closeUseCase(repository, clock),
|
||||||
|
historyUseCases: _historyUseCases(clock),
|
||||||
|
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
expect(find.byType(ListView), findsNothing);
|
||||||
|
expect(find.text('SÉRIE'), findsOneWidget);
|
||||||
|
expect(find.text('Passage 1 / 10'), findsOneWidget);
|
||||||
|
expect(find.text('Objectif : 1:30'), findsOneWidget);
|
||||||
|
expect(find.widgetWithText(FilledButton, 'Démarrer'), findsOneWidget);
|
||||||
|
expect(find.widgetWithText(FilledButton, 'Lancer'), findsOneWidget);
|
||||||
|
expect(find.textContaining('Passages réalisés'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('une étape répétitions peut être validée', (tester) async {
|
testWidgets('une étape répétitions peut être validée', (tester) async {
|
||||||
await tester.binding.setSurfaceSize(const Size(400, 1600));
|
await tester.binding.setSurfaceSize(const Size(400, 1600));
|
||||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||||
@ -1215,7 +1345,7 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.text('00:05'), findsOneWidget);
|
expect(find.text('00:05'), findsOneWidget);
|
||||||
await tester.tap(find.widgetWithText(FilledButton, 'Démarrer la séquence'));
|
await tester.tap(find.widgetWithText(FilledButton, 'Lancer'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 2);
|
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 2);
|
||||||
await tester.pump(const Duration(milliseconds: 100));
|
await tester.pump(const Duration(milliseconds: 100));
|
||||||
@ -1274,13 +1404,10 @@ void main() {
|
|||||||
|
|
||||||
expect(find.text('Chrono non lancé'), findsOneWidget);
|
expect(find.text('Chrono non lancé'), findsOneWidget);
|
||||||
expect(
|
expect(
|
||||||
find.text("Tu n’as pas démarré le chrono de cette série."),
|
find.text('Tu n’as pas démarré le chrono de cette série.'),
|
||||||
findsOneWidget,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
find.widgetWithText(TextButton, 'Démarrer l’exercice'),
|
|
||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
);
|
);
|
||||||
|
expect(find.widgetWithText(TextButton, 'Démarrer'), findsOneWidget);
|
||||||
expect(find.text('Terminer sans chrono'), findsOneWidget);
|
expect(find.text('Terminer sans chrono'), findsOneWidget);
|
||||||
expect(repository.results, isEmpty);
|
expect(repository.results, isEmpty);
|
||||||
},
|
},
|
||||||
@ -1330,7 +1457,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
await tester.tap(find.widgetWithText(FilledButton, 'Démarrer la séquence'));
|
await tester.tap(find.widgetWithText(FilledButton, 'Lancer'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 1, 200);
|
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 1, 200);
|
||||||
await tester.pump(const Duration(milliseconds: 200));
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
@ -1392,9 +1519,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
await tester.tap(
|
await tester.tap(find.widgetWithText(FilledButton, 'Lancer'));
|
||||||
find.widgetWithText(FilledButton, 'Démarrer la séquence'),
|
|
||||||
);
|
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 1, 200);
|
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 1, 200);
|
||||||
await tester.pump(const Duration(milliseconds: 200));
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
@ -1404,15 +1529,15 @@ void main() {
|
|||||||
expect(find.text('ÉTAPE 2 / 2'), findsOneWidget);
|
expect(find.text('ÉTAPE 2 / 2'), findsOneWidget);
|
||||||
expect(find.text('Droite'), findsOneWidget);
|
expect(find.text('Droite'), findsOneWidget);
|
||||||
expect(find.text('00:02'), findsOneWidget);
|
expect(find.text('00:02'), findsOneWidget);
|
||||||
expect(find.text('Chrono suivant prêt'), findsOneWidget);
|
expect(find.text('Chrono prêt'), findsOneWidget);
|
||||||
expect(find.text('Démarrer le chrono'), findsOneWidget);
|
expect(find.widgetWithText(FilledButton, 'Lancer'), findsOneWidget);
|
||||||
expect(
|
expect(
|
||||||
repository.stepProgressStates.single.status,
|
repository.stepProgressStates.single.status,
|
||||||
ActiveExerciseStepProgressStatus.stoppedTimer,
|
ActiveExerciseStepProgressStatus.stoppedTimer,
|
||||||
);
|
);
|
||||||
expect(repository.stepProgressStates.single.startedAt, isNull);
|
expect(repository.stepProgressStates.single.startedAt, isNull);
|
||||||
|
|
||||||
await tester.tap(find.widgetWithText(FilledButton, 'Démarrer le chrono'));
|
await tester.tap(find.widgetWithText(FilledButton, 'Lancer'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@ -1466,7 +1591,7 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.text('Passage 2 / 2'), findsOneWidget);
|
expect(find.text('Passage 2 / 2'), findsOneWidget);
|
||||||
expect(find.text('Passages réalisés : 1 / 2'), findsOneWidget);
|
expect(find.textContaining('Passages réalisés'), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('terminer la série reste utilisable avant la fin de séquence', (
|
testWidgets('terminer la série reste utilisable avant la fin de séquence', (
|
||||||
@ -1678,18 +1803,23 @@ String _sessionSnapshot({
|
|||||||
List<Map<String, Object?>> exerciseSteps = const [],
|
List<Map<String, Object?>> exerciseSteps = const [],
|
||||||
bool autoStartNextTimedStep = true,
|
bool autoStartNextTimedStep = true,
|
||||||
String? videoMediaId,
|
String? videoMediaId,
|
||||||
|
String? sourceExerciseId,
|
||||||
|
String workoutName = 'Séance jambes',
|
||||||
|
String programName = 'Programme jambes',
|
||||||
|
String exerciseName = 'Squat',
|
||||||
}) {
|
}) {
|
||||||
return jsonEncode({
|
return jsonEncode({
|
||||||
'name': 'Séance jambes',
|
'name': workoutName,
|
||||||
'programs': [
|
'programs': [
|
||||||
{
|
{
|
||||||
'id': 'template-program-1',
|
'id': 'template-program-1',
|
||||||
'programNameSnapshot': 'Programme jambes',
|
'programNameSnapshot': programName,
|
||||||
'programSnapshotJson': jsonEncode({
|
'programSnapshotJson': jsonEncode({
|
||||||
'exercises': [
|
'exercises': [
|
||||||
{
|
{
|
||||||
'id': 'exercise-1',
|
'id': 'exercise-1',
|
||||||
'exerciseNameSnapshot': 'Squat',
|
'sourceExerciseId': sourceExerciseId,
|
||||||
|
'exerciseNameSnapshot': exerciseName,
|
||||||
'setsCount': setsCount,
|
'setsCount': setsCount,
|
||||||
'timeEnabled': timeEnabled,
|
'timeEnabled': timeEnabled,
|
||||||
'repsEnabled': repsEnabled,
|
'repsEnabled': repsEnabled,
|
||||||
@ -1794,6 +1924,37 @@ final class _FakeStepAudioCuePlayer implements ExerciseStepAudioCuePlayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class _FakeExercisePerformanceReferenceRepository
|
||||||
|
implements ExercisePerformanceReferenceRepository {
|
||||||
|
const _FakeExercisePerformanceReferenceRepository({this.last, this.record});
|
||||||
|
|
||||||
|
final WorkoutHistorySetPerformance? last;
|
||||||
|
final WorkoutHistoryMetricPerformance? record;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> hasAnyCompletedHistoryForExercise(String exerciseId) async {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<WorkoutHistorySetPerformance?> findLatestSetPerformance({
|
||||||
|
required String exerciseId,
|
||||||
|
required ActivePerformanceMeasures activeMeasures,
|
||||||
|
required int currentSetIndex,
|
||||||
|
}) async {
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<WorkoutHistoryMetricPerformance?> findBestMetricPerformance({
|
||||||
|
required String exerciseId,
|
||||||
|
required PerformanceMetric metric,
|
||||||
|
required ScoreInputMode scoreInputMode,
|
||||||
|
}) async {
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
final class _FakeActiveSessionRepository implements ActiveSessionRepository {
|
||||||
ActiveWorkoutSession? session;
|
ActiveWorkoutSession? session;
|
||||||
final results = <ActiveSetResult>[];
|
final results = <ActiveSetResult>[];
|
||||||
|
|||||||
Reference in New Issue
Block a user