merge(main): compteur de série visible pendant l'exécution (ticket #34)

Fusionne feature/#34-compteur-serie-visible — analyze propre, 58/58
tests verts, build APK debug validé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 10:38:56 +02:00
7 changed files with 95 additions and 30 deletions

View File

@ -11,3 +11,4 @@
- [gametime-architecture-score-chrono](gametime-architecture-score-chrono.md) — memory note gametime-architecture-score-chrono - [gametime-architecture-score-chrono](gametime-architecture-score-chrono.md) — memory note gametime-architecture-score-chrono
- [gametime-resume-plan-2026-07-18](gametime-resume-plan-2026-07-18.md) — memory note gametime-resume-plan-2026-07-18 - [gametime-resume-plan-2026-07-18](gametime-resume-plan-2026-07-18.md) — memory note gametime-resume-plan-2026-07-18
- [gametime-ux-exercise-default-targets](gametime-ux-exercise-default-targets.md) — memory note gametime-ux-exercise-default-targets - [gametime-ux-exercise-default-targets](gametime-ux-exercise-default-targets.md) — memory note gametime-ux-exercise-default-targets
- [gametime-ux-series-counter](gametime-ux-series-counter.md) — memory note gametime-ux-series-counter

View File

@ -0,0 +1,19 @@
---
name: gametime-ux-series-counter
description: memory note gametime-ux-series-counter
metadata:
type: project
---
# GameTime — Compteur de série plus visible (ticket #34, UX 2026-07-18)
Sortir "Série X/Y" de la ligne "Programme · Exercice · Série" et en faire un bloc visuel principal proche de la série active.
## Écran d'exécution
- Header discret conservé : temps écoulé + "Programme 1/2 · Exercice 3/8" (SANS le compteur de série qui est retiré de cette ligne).
- Juste au-dessus du nom de l'exercice, nouveau bloc visible : label "SÉRIE" (Archivo 600, 12-13px, texte secondaire, uppercase) + valeur "2 / 4" (Anton, 44-52px, couleur PRIMAIRE OR — pas crimson, le crimson reste réservé à l'accent/liseré). Bloc dans une petite carte plate (surface existante, liseré supérieur crimson 2px, rayon 6px cohérent avec la DA Court Blazer).
## Écran Repos
Dans le texte "Ensuite : <exercice>", ajouter "Série 3 / 4" en Anton 28-32px or, pour préparer clairement à la prochaine série.
## Ne pas casser
Les compteurs Programme/Exercice restent affichés mais discrets (juste allégés du compteur série qu'ils portaient).

View File

@ -1,6 +1,7 @@
--- ---
issueRef: "#34" issueRef: "#34"
version: 3 version: 6
updatedBy: {"kind":"user"} updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784329283319 updatedAt: 1784363909962
--- ---
Décision UX consignée dans la mémoire "gametime-ux-series-counter" — lis-la en entier avant d'implémenter. Résumé : sortir "Série X/Y" de la ligne de progression existante, en faire un bloc visuel principal (Anton 44-52px, couleur or) juste au-dessus du nom de l'exercice, dans une petite carte avec liseré crimson. Même traitement allégé (Anton 28-32px or) pour "Série suivante" sur l'écran Repos.

View File

@ -2,15 +2,15 @@
id: "a78c5f84-3d2e-40ae-b800-880de78f2035" id: "a78c5f84-3d2e-40ae-b800-880de78f2035"
number: 34 number: 34
title: "Rendr ele compteur de série plus visible" title: "Rendr ele compteur de série plus visible"
status: "open" status: "closed"
priority: "high" priority: "high"
sprint: null sprint: null
links: [] links: []
agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}] agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}]
createdBy: {"kind":"user"} createdBy: {"kind":"user"}
updatedBy: {"kind":"user"} updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
createdAt: 1784329212434 createdAt: 1784329212434
updatedAt: 1784329283319 updatedAt: 1784363909962
version: 3 version: 6
--- ---
Il faudrait que le compteur de série soit plus visible. Actuellement il est perdu avec le compteur de programme et le compteur d'exercice. J'aimerais qu'il soit plus visible, pour ça il faudrait le déplacer plus proche de notre série en cours et pourquoi pas le mettre plus gros/d'une autre couleur ? Laisser UX gérer ça Il faudrait que le compteur de série soit plus visible. Actuellement il est perdu avec le compteur de programme et le compteur d'exercice. J'aimerais qu'il soit plus visible, pour ça il faudrait le déplacer plus proche de notre série en cours et pourquoi pas le mettre plus gros/d'une autre couleur ? Laisser UX gérer ça

View File

@ -381,13 +381,13 @@
"issueRef": "#34", "issueRef": "#34",
"path": "34", "path": "34",
"title": "Rendr ele compteur de série plus visible", "title": "Rendr ele compteur de série plus visible",
"status": "open", "status": "closed",
"priority": "high", "priority": "high",
"sprint": null, "sprint": null,
"assignedAgentIds": [ "assignedAgentIds": [
"57695b92-24d0-4876-837c-76116e70a6ae" "57695b92-24d0-4876-837c-76116e70a6ae"
], ],
"updatedAt": 1784329283319 "updatedAt": 1784363909962
}, },
{ {
"issueRef": "#35", "issueRef": "#35",

View File

@ -124,6 +124,11 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
progressLabel: _plan.progressLabel(_position), progressLabel: _plan.progressLabel(_position),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
_SeriesCounterCard(
currentSet: _position.setIndex + 1,
totalSets: _exercise.setsCount,
),
const SizedBox(height: 16),
Text(_exercise.name, style: Theme.of(context).textTheme.headlineMedium), Text(_exercise.name, style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 16), const SizedBox(height: 16),
SetMeasureInput( SetMeasureInput(
@ -168,10 +173,15 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
), ),
if (next != null && nextExercise != null) ...[ if (next != null && nextExercise != null) ...[
const SizedBox(height: 24), const SizedBox(height: 24),
Text('Ensuite : ${nextExercise.name}', textAlign: TextAlign.center),
const SizedBox(height: 8),
Text( Text(
'Ensuite : ${nextExercise.name} · '
'Série ${next.setIndex + 1} / ${nextExercise.setsCount}', 'Série ${next.setIndex + 1} / ${nextExercise.setsCount}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.displaySmall?.copyWith(
color: Theme.of(context).colorScheme.primary,
fontSize: 30,
),
), ),
], ],
const Spacer(), const Spacer(),
@ -1304,10 +1314,8 @@ final class WorkoutExecutionPlan {
String progressLabel(ExecutionPosition position) { String progressLabel(ExecutionPosition position) {
final program = programAt(position); final program = programAt(position);
final exercise = exerciseAt(position);
return 'Programme ${position.programIndex + 1}/${programs.length} · ' return 'Programme ${position.programIndex + 1}/${programs.length} · '
'Exercice ${position.exerciseIndex + 1}/${program.exercises.length} · ' 'Exercice ${position.exerciseIndex + 1}/${program.exercises.length}';
'Série ${position.setIndex + 1}/${exercise.setsCount}';
} }
} }
@ -1463,6 +1471,42 @@ final class _Header extends StatelessWidget {
} }
} }
final class _SeriesCounterCard extends StatelessWidget {
const _SeriesCounterCard({required this.currentSet, required this.totalSets});
final int currentSet;
final int totalSets;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final tokens = courtBlazerTokensOf(context);
return CourtBlazerAccentPanel(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'SÉRIE',
style: theme.textTheme.labelSmall?.copyWith(
color: tokens.mutedText,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 6),
Text(
'$currentSet / $totalSets',
style: theme.textTheme.displayLarge?.copyWith(
color: theme.colorScheme.primary,
fontSize: 48,
),
),
],
),
);
}
}
String _formatDuration(Duration duration) { String _formatDuration(Duration duration) {
final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0'); final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0'); final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');

View File

@ -165,6 +165,7 @@ void main() {
expect(find.text('Repos avant la prochaine série'), findsOneWidget); expect(find.text('Repos avant la prochaine série'), findsOneWidget);
expect(find.text('00:45'), findsOneWidget); expect(find.text('00:45'), findsOneWidget);
expect(find.text('Série 2 / 2'), findsOneWidget);
await tester.tap(find.text('+15 s')); await tester.tap(find.text('+15 s'));
await tester.pump(); await tester.pump();
@ -181,6 +182,9 @@ void main() {
testWidgets('la mesure temps enregistre une durée réelle de série', ( testWidgets('la mesure temps enregistre une durée réelle de série', (
tester, tester,
) async { ) async {
await tester.binding.setSurfaceSize(const Size(400, 1400));
addTearDown(() => tester.binding.setSurfaceSize(null));
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12)); final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
final repository = _FakeActiveSessionRepository(); final repository = _FakeActiveSessionRepository();
final session = ActiveWorkoutSession( final session = ActiveWorkoutSession(
@ -221,6 +225,9 @@ void main() {
testWidgets( testWidgets(
'terminer la série 2 avec temps répétitions et score manuel avance', 'terminer la série 2 avec temps répétitions et score manuel avance',
(tester) async { (tester) async {
await tester.binding.setSurfaceSize(const Size(400, 1400));
addTearDown(() => tester.binding.setSurfaceSize(null));
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12)); final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
final repository = _FakeActiveSessionRepository(); final repository = _FakeActiveSessionRepository();
final session = ActiveWorkoutSession( final session = ActiveWorkoutSession(
@ -256,10 +263,9 @@ void main() {
), ),
); );
expect( expect(find.text('Programme 1/1 · Exercice 1/1'), findsOneWidget);
find.text('Programme 1/1 · Exercice 1/1 · Série 2/3'), expect(find.text('SÉRIE'), findsOneWidget);
findsOneWidget, expect(find.text('2 / 3'), findsOneWidget);
);
expect(find.text('30 s'), findsOneWidget); expect(find.text('30 s'), findsOneWidget);
for (var i = 0; i < 3; i++) { for (var i = 0; i < 3; i++) {
@ -279,10 +285,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( expect(find.text('Programme 1/1 · Exercice 1/1'), findsOneWidget);
find.text('Programme 1/1 · Exercice 1/1 · Série 3/3'), expect(find.text('3 / 3'), findsOneWidget);
findsOneWidget,
);
}, },
); );
@ -445,10 +449,8 @@ void main() {
await tester.tap(find.byTooltip('Fermer')); await tester.tap(find.byTooltip('Fermer'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(find.text('Programme 1/1 · Exercice 1/1'), findsOneWidget);
find.text('Programme 1/1 · Exercice 1/1 · Série 3/4'), expect(find.text('3 / 4'), findsOneWidget);
findsOneWidget,
);
}, },
); );
@ -493,10 +495,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( expect(find.text('Programme 1/1 · Exercice 1/1'), findsOneWidget);
find.text('Programme 1/1 · Exercice 1/1 · Série 2/3'), expect(find.text('2 / 3'), findsOneWidget);
findsOneWidget,
);
}, },
); );
} }