merge(main): affichage du chrono de score dans le plan et l'historique (ticket #28)
Fusionne feature/#28-affichage-chrono-plan-historique — analyze propre, 77/77 tests verts, build APK debug validé. Dernier ticket du backlog, clôture aussi le ticket parent #18 (score chronométré). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#18"
|
issueRef: "#18"
|
||||||
version: 2
|
version: 3
|
||||||
updatedBy: {"kind":"user"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784324398610
|
updatedAt: 1784378420355
|
||||||
---
|
---
|
||||||
|
|||||||
@ -2,15 +2,15 @@
|
|||||||
id: "bf1aef7d-5f3b-4354-aba9-851df92be313"
|
id: "bf1aef7d-5f3b-4354-aba9-851df92be313"
|
||||||
number: 18
|
number: 18
|
||||||
title: "Ajouter un type de score spécial temps"
|
title: "Ajouter un type de score spécial temps"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "medium"
|
priority: "medium"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: []
|
links: []
|
||||||
agentRefs: []
|
agentRefs: []
|
||||||
createdBy: {"kind":"user"}
|
createdBy: {"kind":"user"}
|
||||||
updatedBy: {"kind":"user"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784324324152
|
createdAt: 1784324324152
|
||||||
updatedAt: 1784324398610
|
updatedAt: 1784378420355
|
||||||
version: 2
|
version: 3
|
||||||
---
|
---
|
||||||
J'aimerais ajouter un type de score qui serait un temps avec un chrono intégré. Le but serait que l'utilisateur au début de sa série puisse lancer le chrono et l'arrêter à la fin. Ca lui permettrait ainsi d'avoir comme score le temps qu'il a mit à faire sa série sans avoir à lancer un autre chrono
|
J'aimerais ajouter un type de score qui serait un temps avec un chrono intégré. Le but serait que l'utilisateur au début de sa série puisse lancer le chrono et l'arrêter à la fin. Ca lui permettrait ainsi d'avoir comme score le temps qu'il a mit à faire sa série sans avoir à lancer un autre chrono
|
||||||
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#28"
|
issueRef: "#28"
|
||||||
version: 2
|
version: 4
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784328260662
|
updatedAt: 1784378419582
|
||||||
---
|
---
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: "dd96c2f3-63a7-4e0d-b976-b1e4a2f58240"
|
id: "dd96c2f3-63a7-4e0d-b976-b1e4a2f58240"
|
||||||
number: 28
|
number: 28
|
||||||
title: "[DevFrontend] Affichage du Score chrono dans le plan de séance et l'historique"
|
title: "[DevFrontend] Affichage du Score chrono dans le plan de séance et l'historique"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "medium"
|
priority: "medium"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: [{"target":"#18","kind":"relatesTo"},{"target":"#27","kind":"dependsOn"}]
|
links: [{"target":"#18","kind":"relatesTo"},{"target":"#27","kind":"dependsOn"}]
|
||||||
@ -10,7 +10,7 @@ agentRefs: [{"agentId":"9933c93a-b8a1-4164-a3bb-7063fdad747d","role":"assigned"}
|
|||||||
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784328251633
|
createdAt: 1784328251633
|
||||||
updatedAt: 1784328260662
|
updatedAt: 1784378419582
|
||||||
version: 2
|
version: 4
|
||||||
---
|
---
|
||||||
Dans le plan de séance (bottom sheet de workout_execution_screen.dart) et l'écran d'historique (history_screen.dart) : afficher "Score chrono" avec la durée formatée (ex "00:38.7") au lieu d'un score numérique brut, avec l'objectif si défini ("00:38.7 / objectif 00:45"). La bottom sheet "Modifier la série" (édition ponctuelle, ticket #23) doit permettre de saisir/corriger manuellement une durée (min/s/dixièmes) pour une série en Score chrono. Cf. mémoire "gametime-ux-score-chrono".
|
Dans le plan de séance (bottom sheet de workout_execution_screen.dart) et l'écran d'historique (history_screen.dart) : afficher "Score chrono" avec la durée formatée (ex "00:38.7") au lieu d'un score numérique brut, avec l'objectif si défini ("00:38.7 / objectif 00:45"). La bottom sheet "Modifier la série" (édition ponctuelle, ticket #23) doit permettre de saisir/corriger manuellement une durée (min/s/dixièmes) pour une série en Score chrono. Cf. mémoire "gametime-ux-score-chrono".
|
||||||
@ -197,11 +197,11 @@
|
|||||||
"issueRef": "#18",
|
"issueRef": "#18",
|
||||||
"path": "18",
|
"path": "18",
|
||||||
"title": "Ajouter un type de score spécial temps",
|
"title": "Ajouter un type de score spécial temps",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "medium",
|
"priority": "medium",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [],
|
"assignedAgentIds": [],
|
||||||
"updatedAt": 1784324398610
|
"updatedAt": 1784378420355
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#19",
|
"issueRef": "#19",
|
||||||
@ -315,13 +315,13 @@
|
|||||||
"issueRef": "#28",
|
"issueRef": "#28",
|
||||||
"path": "28",
|
"path": "28",
|
||||||
"title": "[DevFrontend] Affichage du Score chrono dans le plan de séance et l'historique",
|
"title": "[DevFrontend] Affichage du Score chrono dans le plan de séance et l'historique",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "medium",
|
"priority": "medium",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
|
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
|
||||||
],
|
],
|
||||||
"updatedAt": 1784328260662
|
"updatedAt": 1784378419582
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#29",
|
"issueRef": "#29",
|
||||||
|
|||||||
@ -314,6 +314,22 @@ final class HistorySetDetail {
|
|||||||
final value = _formatScoreValue(result['actualScore'] as num);
|
final value = _formatScoreValue(result['actualScore'] as num);
|
||||||
parts.add(unit == null ? 'Score $value' : 'Score $value $unit');
|
parts.add(unit == null ? 'Score $value' : 'Score $value $unit');
|
||||||
}
|
}
|
||||||
|
if (exercise['scoreEnabled'] == true &&
|
||||||
|
_historyScoreInputMode(exercise, result) == ScoreInputMode.stopwatch &&
|
||||||
|
result['actualScoreTimeMs'] != null) {
|
||||||
|
final value = _formatScoreStopwatch(
|
||||||
|
Duration(milliseconds: result['actualScoreTimeMs'] as int),
|
||||||
|
);
|
||||||
|
final target = _targetScoreTimeMsFromSnapshot(exercise, result);
|
||||||
|
if (target == null) {
|
||||||
|
parts.add('Temps réalisé : $value');
|
||||||
|
} else {
|
||||||
|
parts.add(
|
||||||
|
'Temps réalisé : $value / objectif '
|
||||||
|
'${_formatScoreStopwatchTarget(Duration(milliseconds: target))}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
return HistorySetDetail(
|
return HistorySetDetail(
|
||||||
setIndex: result['setIndex'] as int,
|
setIndex: result['setIndex'] as int,
|
||||||
summary: parts.isEmpty ? 'Aucune mesure saisie' : parts.join(' · '),
|
summary: parts.isEmpty ? 'Aucune mesure saisie' : parts.join(' · '),
|
||||||
@ -361,6 +377,9 @@ Map<String, dynamic> _resultFromDomain(WorkoutHistorySetResult result) {
|
|||||||
'actualTimeMs': result.actualTimeMs,
|
'actualTimeMs': result.actualTimeMs,
|
||||||
'actualReps': result.actualReps,
|
'actualReps': result.actualReps,
|
||||||
'actualScore': result.actualScore,
|
'actualScore': result.actualScore,
|
||||||
|
'actualScoreTimeMs': result.actualScoreTimeMs,
|
||||||
|
'scoreInputModeSnapshot': result.scoreInputModeSnapshot.name,
|
||||||
|
'targetScoreTimeMsSnapshot': result.targetScoreTimeMsSnapshot,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,7 +391,11 @@ String _historySummary(WorkoutHistory history) {
|
|||||||
: (snapshot['results'] as List<dynamic>? ?? const [])
|
: (snapshot['results'] as List<dynamic>? ?? const [])
|
||||||
.cast<Map<String, dynamic>>();
|
.cast<Map<String, dynamic>>();
|
||||||
final scoreCount = rawResults
|
final scoreCount = rawResults
|
||||||
.where((result) => result['actualScore'] != null)
|
.where(
|
||||||
|
(result) =>
|
||||||
|
result['actualScore'] != null ||
|
||||||
|
result['actualScoreTimeMs'] != null,
|
||||||
|
)
|
||||||
.length;
|
.length;
|
||||||
return '${rawResults.length} séries · $scoreCount score${scoreCount > 1 ? 's' : ''}';
|
return '${rawResults.length} séries · $scoreCount score${scoreCount > 1 ? 's' : ''}';
|
||||||
}
|
}
|
||||||
@ -403,6 +426,47 @@ String _formatDurationMs(int milliseconds) {
|
|||||||
return '$minutes:$seconds';
|
return '$minutes:$seconds';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _formatScoreStopwatch(Duration duration) {
|
||||||
|
final totalTenths = (duration.inMilliseconds / 100).floor();
|
||||||
|
final minutes = (totalTenths ~/ 600).toString().padLeft(2, '0');
|
||||||
|
final seconds = ((totalTenths ~/ 10) % 60).toString().padLeft(2, '0');
|
||||||
|
final tenths = totalTenths % 10;
|
||||||
|
return '$minutes:$seconds.$tenths';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatScoreStopwatchTarget(Duration duration) {
|
||||||
|
if (duration.inMilliseconds % 1000 == 0) {
|
||||||
|
return _formatDurationMs(duration.inMilliseconds);
|
||||||
|
}
|
||||||
|
return _formatScoreStopwatch(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScoreInputMode _scoreInputModeFromSnapshot(Object? value) {
|
||||||
|
return switch (value) {
|
||||||
|
'stopwatch' => ScoreInputMode.stopwatch,
|
||||||
|
_ => ScoreInputMode.manual,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _targetScoreTimeMsFromSnapshot(
|
||||||
|
Map<String, dynamic> exercise,
|
||||||
|
Map<String, dynamic> result,
|
||||||
|
) {
|
||||||
|
return result['targetScoreTimeMsSnapshot'] as int? ??
|
||||||
|
(exercise['targetScoreTimeMs'] as int?);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScoreInputMode _historyScoreInputMode(
|
||||||
|
Map<String, dynamic> exercise,
|
||||||
|
Map<String, dynamic> result,
|
||||||
|
) {
|
||||||
|
final resultMode = result['scoreInputModeSnapshot'];
|
||||||
|
if (resultMode != null) {
|
||||||
|
return _scoreInputModeFromSnapshot(resultMode);
|
||||||
|
}
|
||||||
|
return _scoreInputModeFromSnapshot(exercise['scoreInputModeSnapshot']);
|
||||||
|
}
|
||||||
|
|
||||||
final class _CenteredMessage extends StatelessWidget {
|
final class _CenteredMessage extends StatelessWidget {
|
||||||
const _CenteredMessage({required this.title, this.message});
|
const _CenteredMessage({required this.title, this.message});
|
||||||
|
|
||||||
|
|||||||
@ -1515,7 +1515,7 @@ final class _WorkoutPlanSetTile extends StatelessWidget {
|
|||||||
child: ListTile(
|
child: ListTile(
|
||||||
enabled: tappable,
|
enabled: tappable,
|
||||||
title: Text('Série ${position.setIndex + 1}'),
|
title: Text('Série ${position.setIndex + 1}'),
|
||||||
subtitle: Text(_planSetSubtitle(planState, state?.result)),
|
subtitle: Text(_planSetSubtitle(planState, state?.result, exercise)),
|
||||||
trailing: _PlanStatusBadge(status: planState, onTap: onTap),
|
trailing: _PlanStatusBadge(status: planState, onTap: onTap),
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
),
|
),
|
||||||
@ -1851,9 +1851,13 @@ _PlanSetStatus _planSetState(
|
|||||||
return _PlanSetStatus.todo;
|
return _PlanSetStatus.todo;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _planSetSubtitle(_PlanSetStatus status, ActiveSetResult? result) {
|
String _planSetSubtitle(
|
||||||
|
_PlanSetStatus status,
|
||||||
|
ActiveSetResult? result,
|
||||||
|
ExecutionExercise exercise,
|
||||||
|
) {
|
||||||
if (status == _PlanSetStatus.completed && result != null) {
|
if (status == _PlanSetStatus.completed && result != null) {
|
||||||
return _setResultSummary(result);
|
return _setResultSummary(result, exercise: exercise);
|
||||||
}
|
}
|
||||||
if (status == _PlanSetStatus.passed) {
|
if (status == _PlanSetStatus.passed) {
|
||||||
return 'Aucun résultat';
|
return 'Aucun résultat';
|
||||||
@ -1861,7 +1865,10 @@ String _planSetSubtitle(_PlanSetStatus status, ActiveSetResult? result) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
String _setResultSummary(ActiveSetResult result) {
|
String _setResultSummary(
|
||||||
|
ActiveSetResult result, {
|
||||||
|
ExecutionExercise? exercise,
|
||||||
|
}) {
|
||||||
final parts = <String>[];
|
final parts = <String>[];
|
||||||
if (result.actualTimeMs != null) {
|
if (result.actualTimeMs != null) {
|
||||||
parts.add(_formatDuration(Duration(milliseconds: result.actualTimeMs!)));
|
parts.add(_formatDuration(Duration(milliseconds: result.actualTimeMs!)));
|
||||||
@ -1878,7 +1885,15 @@ String _setResultSummary(ActiveSetResult result) {
|
|||||||
final value = _formatScoreStopwatch(
|
final value = _formatScoreStopwatch(
|
||||||
Duration(milliseconds: result.actualScoreTimeMs!),
|
Duration(milliseconds: result.actualScoreTimeMs!),
|
||||||
);
|
);
|
||||||
parts.add('Temps réalisé $value');
|
final targetScoreTimeMs = exercise?.targetScoreTimeMs;
|
||||||
|
if (targetScoreTimeMs == null) {
|
||||||
|
parts.add('Score chrono $value');
|
||||||
|
} else {
|
||||||
|
final targetValue = _formatScoreStopwatchTarget(
|
||||||
|
Duration(milliseconds: targetScoreTimeMs),
|
||||||
|
);
|
||||||
|
parts.add('Score chrono $value / objectif $targetValue');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return parts.isEmpty ? 'Aucun résultat' : parts.join(' · ');
|
return parts.isEmpty ? 'Aucun résultat' : parts.join(' · ');
|
||||||
}
|
}
|
||||||
@ -2050,6 +2065,7 @@ final class ExecutionExercise {
|
|||||||
this.targetTimeSeconds,
|
this.targetTimeSeconds,
|
||||||
this.targetReps,
|
this.targetReps,
|
||||||
this.targetScore,
|
this.targetScore,
|
||||||
|
this.targetScoreTimeMs,
|
||||||
this.scoreUnit,
|
this.scoreUnit,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2084,6 +2100,9 @@ final class ExecutionExercise {
|
|||||||
targetScore:
|
targetScore:
|
||||||
(override?['targetScoreOverride'] as num?)?.toDouble() ??
|
(override?['targetScoreOverride'] as num?)?.toDouble() ??
|
||||||
(exercise['targetScore'] as num?)?.toDouble(),
|
(exercise['targetScore'] as num?)?.toDouble(),
|
||||||
|
targetScoreTimeMs:
|
||||||
|
(override?['targetScoreTimeMsOverride'] as int?) ??
|
||||||
|
(exercise['targetScoreTimeMs'] as int?),
|
||||||
scoreUnit: exercise['scoreUnitSnapshot'] as String?,
|
scoreUnit: exercise['scoreUnitSnapshot'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2102,6 +2121,7 @@ final class ExecutionExercise {
|
|||||||
final int? targetTimeSeconds;
|
final int? targetTimeSeconds;
|
||||||
final int? targetReps;
|
final int? targetReps;
|
||||||
final double? targetScore;
|
final double? targetScore;
|
||||||
|
final int? targetScoreTimeMs;
|
||||||
final String? scoreUnit;
|
final String? scoreUnit;
|
||||||
|
|
||||||
bool get manualScoreEnabled {
|
bool get manualScoreEnabled {
|
||||||
@ -2235,3 +2255,10 @@ String _formatScoreStopwatch(Duration duration) {
|
|||||||
final tenths = totalTenths % 10;
|
final tenths = totalTenths % 10;
|
||||||
return '$minutes:$seconds.$tenths';
|
return '$minutes:$seconds.$tenths';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _formatScoreStopwatchTarget(Duration duration) {
|
||||||
|
if (duration.inMilliseconds % 1000 == 0) {
|
||||||
|
return _formatDuration(duration);
|
||||||
|
}
|
||||||
|
return _formatScoreStopwatch(duration);
|
||||||
|
}
|
||||||
|
|||||||
@ -50,6 +50,31 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('le détail affiche le score chrono comme temps réalisé', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: HistoryDetailScreen(
|
||||||
|
history: _history(id: 'history-1', stopwatchScore: true),
|
||||||
|
historyUseCases: _historyUseCases(_FakeWorkoutHistoryRepository()),
|
||||||
|
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||||
|
activeUseCases: _activeUseCases(),
|
||||||
|
closeUseCase: _closeUseCase(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.text(
|
||||||
|
'Temps 00:45 · 10 répétitions · '
|
||||||
|
'Temps réalisé : 00:38.7 / objectif 00:45',
|
||||||
|
),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
expect(find.textContaining('Score 38.7'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('la suppression demande une confirmation', (tester) async {
|
testWidgets('la suppression demande une confirmation', (tester) async {
|
||||||
final repository = _FakeWorkoutHistoryRepository();
|
final repository = _FakeWorkoutHistoryRepository();
|
||||||
|
|
||||||
@ -85,7 +110,11 @@ void main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkoutHistory _history({required String id, DateTime? startedAt}) {
|
WorkoutHistory _history({
|
||||||
|
required String id,
|
||||||
|
DateTime? startedAt,
|
||||||
|
bool stopwatchScore = false,
|
||||||
|
}) {
|
||||||
final start = startedAt ?? DateTime.utc(2026, 7, 17, 10);
|
final start = startedAt ?? DateTime.utc(2026, 7, 17, 10);
|
||||||
return WorkoutHistory(
|
return WorkoutHistory(
|
||||||
metadata: _metadata(id),
|
metadata: _metadata(id),
|
||||||
@ -98,7 +127,9 @@ WorkoutHistory _history({required String id, DateTime? startedAt}) {
|
|||||||
completed: true,
|
completed: true,
|
||||||
historySnapshotJson: jsonEncode({
|
historySnapshotJson: jsonEncode({
|
||||||
'sessionId': 'session-1',
|
'sessionId': 'session-1',
|
||||||
'resolvedTemplateSnapshotJson': _resolvedSnapshot(),
|
'resolvedTemplateSnapshotJson': _resolvedSnapshot(
|
||||||
|
stopwatchScore: stopwatchScore,
|
||||||
|
),
|
||||||
'results': [
|
'results': [
|
||||||
{
|
{
|
||||||
'programSnapshotId': 'program-snapshot-1',
|
'programSnapshotId': 'program-snapshot-1',
|
||||||
@ -108,16 +139,21 @@ WorkoutHistory _history({required String id, DateTime? startedAt}) {
|
|||||||
'setIndex': 0,
|
'setIndex': 0,
|
||||||
'actualTimeMs': 45000,
|
'actualTimeMs': 45000,
|
||||||
'actualReps': 10,
|
'actualReps': 10,
|
||||||
'actualScore': 80,
|
if (stopwatchScore) 'actualScoreTimeMs': 38700,
|
||||||
|
if (!stopwatchScore) 'actualScore': 80,
|
||||||
'scoreLabelSnapshot': 'Charge',
|
'scoreLabelSnapshot': 'Charge',
|
||||||
'scoreUnitSnapshot': 'kg',
|
'scoreInputModeSnapshot': stopwatchScore
|
||||||
|
? ScoreInputMode.stopwatch.name
|
||||||
|
: ScoreInputMode.manual.name,
|
||||||
|
if (!stopwatchScore) 'scoreUnitSnapshot': 'kg',
|
||||||
|
if (stopwatchScore) 'targetScoreTimeMsSnapshot': 45000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _resolvedSnapshot() {
|
String _resolvedSnapshot({bool stopwatchScore = false}) {
|
||||||
return jsonEncode({
|
return jsonEncode({
|
||||||
'name': 'Séance jambes',
|
'name': 'Séance jambes',
|
||||||
'programs': [
|
'programs': [
|
||||||
@ -135,9 +171,13 @@ String _resolvedSnapshot() {
|
|||||||
'scoreEnabled': true,
|
'scoreEnabled': true,
|
||||||
'targetTimeSeconds': 45,
|
'targetTimeSeconds': 45,
|
||||||
'targetReps': 10,
|
'targetReps': 10,
|
||||||
'targetScore': 80,
|
if (stopwatchScore) 'targetScoreTimeMs': 45000,
|
||||||
|
if (!stopwatchScore) 'targetScore': 80,
|
||||||
|
'scoreInputModeSnapshot': stopwatchScore
|
||||||
|
? ScoreInputMode.stopwatch.name
|
||||||
|
: ScoreInputMode.manual.name,
|
||||||
'scoreLabelSnapshot': 'Charge',
|
'scoreLabelSnapshot': 'Charge',
|
||||||
'scoreUnitSnapshot': 'kg',
|
if (!stopwatchScore) 'scoreUnitSnapshot': 'kg',
|
||||||
'restSecondsOverride': 0,
|
'restSecondsOverride': 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -673,6 +673,62 @@ void main() {
|
|||||||
expect(find.text('Plan de séance'), findsNothing);
|
expect(find.text('Plan de séance'), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('le plan affiche le score chrono formaté avec objectif', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
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: 1,
|
||||||
|
resolvedTemplateSnapshotJson: _sessionSnapshot(
|
||||||
|
setsCount: 2,
|
||||||
|
restSeconds: 0,
|
||||||
|
scoreInputMode: ScoreInputMode.stopwatch,
|
||||||
|
targetScoreTimeMs: 45000,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
repository.session = session;
|
||||||
|
repository.results.add(
|
||||||
|
ActiveSetResult(
|
||||||
|
metadata: _metadata('result-1'),
|
||||||
|
activeWorkoutSessionId: 'session-1',
|
||||||
|
programSnapshotId: 'template-program-1',
|
||||||
|
exerciseSnapshotId: 'exercise-1',
|
||||||
|
programIndex: 0,
|
||||||
|
exerciseIndex: 0,
|
||||||
|
setIndex: 0,
|
||||||
|
actualScoreTimeMs: 38700,
|
||||||
|
scoreInputModeSnapshot: ScoreInputMode.stopwatch,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: WorkoutExecutionScreen(
|
||||||
|
initialSession: session,
|
||||||
|
activeUseCases: _activeUseCases(repository, clock),
|
||||||
|
closeUseCase: _closeUseCase(repository, clock),
|
||||||
|
historyUseCases: _historyUseCases(clock),
|
||||||
|
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byTooltip('Voir le plan'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('Score chrono 00:38.7 / objectif 00:45'), findsOneWidget);
|
||||||
|
expect(find.textContaining('Temps réalisé 00:38.7'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'modifier une série passée la termine sans changer la position courante',
|
'modifier une série passée la termine sans changer la position courante',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
@ -836,6 +892,7 @@ String _sessionSnapshot({
|
|||||||
int targetTimeSeconds = 45,
|
int targetTimeSeconds = 45,
|
||||||
int targetReps = 10,
|
int targetReps = 10,
|
||||||
double targetScore = 80,
|
double targetScore = 80,
|
||||||
|
int? targetScoreTimeMs,
|
||||||
String scoreUnit = 'kg',
|
String scoreUnit = 'kg',
|
||||||
ScoreInputMode scoreInputMode = ScoreInputMode.manual,
|
ScoreInputMode scoreInputMode = ScoreInputMode.manual,
|
||||||
List<String> imageMediaIds = const [],
|
List<String> imageMediaIds = const [],
|
||||||
@ -859,6 +916,7 @@ String _sessionSnapshot({
|
|||||||
'targetTimeSeconds': targetTimeSeconds,
|
'targetTimeSeconds': targetTimeSeconds,
|
||||||
'targetReps': targetReps,
|
'targetReps': targetReps,
|
||||||
'targetScore': targetScore,
|
'targetScore': targetScore,
|
||||||
|
'targetScoreTimeMs': targetScoreTimeMs,
|
||||||
'scoreInputModeSnapshot': scoreInputMode.name,
|
'scoreInputModeSnapshot': scoreInputMode.name,
|
||||||
'scoreLabelSnapshot': 'Score',
|
'scoreLabelSnapshot': 'Score',
|
||||||
'scoreUnitSnapshot': scoreUnit,
|
'scoreUnitSnapshot': scoreUnit,
|
||||||
|
|||||||
Reference in New Issue
Block a user