merge(main): valeurs par défaut d'exercice (tickets #39/#40, parent #30)

Fusionne feature/#30-valeurs-defaut-exercice — analyze propre, 51/51
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:18:44 +02:00
24 changed files with 996 additions and 37 deletions

View File

@ -10,3 +10,4 @@
- [gametime-ux-score-chrono](gametime-ux-score-chrono.md) — memory note gametime-ux-score-chrono - [gametime-ux-score-chrono](gametime-ux-score-chrono.md) — memory note gametime-ux-score-chrono
- [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

View File

@ -0,0 +1,35 @@
---
name: gametime-ux-exercise-default-targets
description: memory note gametime-ux-exercise-default-targets
metadata:
type: project
---
# GameTime — Valeurs cibles par défaut à la création d'exercice (ticket #30, UX 2026-07-18)
Décision : ajouter les valeurs cibles par défaut au niveau **Exercice**, obligatoires pour les mesures activées (sauf Score chrono, optionnel).
## Formulaire exercice
- Temps activé → champ "Temps par défaut (s)", obligatoire, validation "Saisis un temps supérieur à 0."
- Répétitions activé → champ "Répétitions par défaut", obligatoire, validation "Saisis un nombre de répétitions supérieur à 0."
- Score activé + mode Saisie libre → nouveau champ "Score par défaut" en plus de "Score à saisir"/"Unité", validation "Saisis un score supérieur à 0."
- Score activé + mode Chrono intégré → PAS de "Score par défaut" ; à la place "Objectif de chrono par défaut (optionnel)", validation seulement si rempli ("Saisis un objectif supérieur à 0.").
- Mesure désactivée → son champ disparaît, pas de validation dessus. Réactivée → champ revient, doit être rempli avant sauvegarde.
## Champs domaine à ajouter sur Exercise
- `defaultTargetTimeSeconds` (obligatoire si hasTimeMeasure)
- `defaultTargetReps` (obligatoire si hasRepsMeasure)
- `defaultTargetScore` (obligatoire si hasScoreMeasure et scoreInputMode manual)
- `defaultTargetScoreTimeMs` (optionnel, seulement si scoreInputMode stopwatch)
Contrainte : valeur obligatoire toujours > 0 ; optionnelle (score chrono) mais si présente > 0.
## Utilisation dans programme
Quand un exercice est ajouté à un programme (ticket #20), les cibles sont préremplies depuis les valeurs par défaut de l'exercice au lieu des valeurs génériques actuelles :
- targetTimeSeconds ← defaultTargetTimeSeconds
- targetReps ← defaultTargetReps
- targetScore ← defaultTargetScore (score libre)
- targetScoreTimeMs ← defaultTargetScoreTimeMs si présent (score chrono)
Tout reste modifiable ensuite dans "Personnaliser l'exercice".
## Migration
Exercices existants sans valeurs par défaut : l'utilisateur devra compléter les champs manquants au prochain enregistrement de l'exercice (pas de blocage rétroactif immédiat, mais validation bloquante dès la prochaine sauvegarde).

View File

@ -1,6 +1,6 @@
--- ---
issueRef: "#30" issueRef: "#30"
version: 2 version: 4
updatedBy: {"kind":"user"} updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784328751807 updatedAt: 1784362687149
--- ---

View File

@ -2,15 +2,15 @@
id: "d4793c3f-a308-42f3-89e6-335cb2f95310" id: "d4793c3f-a308-42f3-89e6-335cb2f95310"
number: 30 number: 30
title: "Ajouter ajouter des valeurs par défaut à la création d'un exercice" title: "Ajouter ajouter des valeurs par défaut à la création d'un exercice"
status: "open" status: "closed"
priority: "critical" priority: "critical"
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: 1784328704260 createdAt: 1784328704260
updatedAt: 1784328751807 updatedAt: 1784362687149
version: 2 version: 4
--- ---
Il faut qu'a la création d'un exercice, il soit obligatoire de donner des valeurs par défaut pour les cibles choisies pour l'exercice. Il faut que ces valeurs oient positives et différentes de 0 pour éviter tout soucis. Il faut qu'a la création d'un exercice, il soit obligatoire de donner des valeurs par défaut pour les cibles choisies pour l'exercice. Il faut que ces valeurs oient positives et différentes de 0 pour éviter tout soucis.

View File

@ -0,0 +1,6 @@
---
issueRef: "#39"
version: 3
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784362685685
---

View File

@ -0,0 +1,16 @@
---
id: "8f581a08-3963-495a-863c-22e8a58b3867"
number: 39
title: "[DevBackend] Valeurs cibles par défaut sur Exercise"
status: "closed"
priority: "critical"
sprint: null
links: [{"target":"#30","kind":"relatesTo"}]
agentRefs: [{"agentId":"10ee045b-1c41-479e-ba03-dceed9edd495","role":"assigned"}]
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
createdAt: 1784361529389
updatedAt: 1784362685685
version: 3
---
Ajouter defaultTargetTimeSeconds/defaultTargetReps/defaultTargetScore/defaultTargetScoreTimeMs sur Exercise (migration Drift), avec validation domaine : valeur obligatoire >0 si la mesure correspondante est active (sauf score chrono où l'objectif par défaut reste optionnel mais >0 si renseigné). Cf. mémoire "gametime-ux-exercise-default-targets" pour le détail complet.

View File

@ -0,0 +1,6 @@
---
issueRef: "#40"
version: 4
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784362686414
---

View File

@ -0,0 +1,16 @@
---
id: "6bb171cc-2a3a-45b2-a952-5453de1cf1fc"
number: 40
title: "[DevFrontend] Formulaire exercice : champs de valeurs par défaut + préremplissage programme"
status: "closed"
priority: "critical"
sprint: null
links: [{"target":"#30","kind":"relatesTo"},{"target":"#39","kind":"dependsOn"}]
agentRefs: [{"agentId":"9933c93a-b8a1-4164-a3bb-7063fdad747d","role":"assigned"}]
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
createdAt: 1784361532790
updatedAt: 1784362686414
version: 4
---
Ajouter les champs de valeur par défaut dans le formulaire de création/édition d'exercice (Temps par défaut, Répétitions par défaut, Score par défaut, Objectif de chrono par défaut optionnel), avec validation bloquante >0. Utiliser ces valeurs par défaut pour préremplir les cibles quand l'exercice est ajouté à un programme (au lieu des valeurs génériques actuelles). Cf. mémoire "gametime-ux-exercise-default-targets" pour le détail complet.

View File

@ -1,3 +1,3 @@
{ {
"nextNumber": 39 "nextNumber": 41
} }

View File

@ -337,11 +337,11 @@
"issueRef": "#30", "issueRef": "#30",
"path": "30", "path": "30",
"title": "Ajouter ajouter des valeurs par défaut à la création d'un exercice", "title": "Ajouter ajouter des valeurs par défaut à la création d'un exercice",
"status": "open", "status": "closed",
"priority": "critical", "priority": "critical",
"sprint": null, "sprint": null,
"assignedAgentIds": [], "assignedAgentIds": [],
"updatedAt": 1784328751807 "updatedAt": 1784362687149
}, },
{ {
"issueRef": "#31", "issueRef": "#31",
@ -436,6 +436,30 @@
"57695b92-24d0-4876-837c-76116e70a6ae" "57695b92-24d0-4876-837c-76116e70a6ae"
], ],
"updatedAt": 1784330204091 "updatedAt": 1784330204091
},
{
"issueRef": "#39",
"path": "39",
"title": "[DevBackend] Valeurs cibles par défaut sur Exercise",
"status": "closed",
"priority": "critical",
"sprint": null,
"assignedAgentIds": [
"10ee045b-1c41-479e-ba03-dceed9edd495"
],
"updatedAt": 1784362685685
},
{
"issueRef": "#40",
"path": "40",
"title": "[DevFrontend] Formulaire exercice : champs de valeurs par défaut + préremplissage programme",
"status": "closed",
"priority": "critical",
"sprint": null,
"assignedAgentIds": [
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
],
"updatedAt": 1784362686414
} }
] ]
} }

View File

@ -25,12 +25,19 @@ final class AppBootstrap implements AppDependencies {
}); });
final AppDatabase database; final AppDatabase database;
@override
final ExerciseUseCases exerciseUseCases; final ExerciseUseCases exerciseUseCases;
@override
final MediaUseCases mediaUseCases; final MediaUseCases mediaUseCases;
@override
final ProgramUseCases programUseCases; final ProgramUseCases programUseCases;
@override
final WorkoutTemplateUseCases workoutTemplateUseCases; final WorkoutTemplateUseCases workoutTemplateUseCases;
@override
final ActiveWorkoutSessionUseCases activeWorkoutSessionUseCases; final ActiveWorkoutSessionUseCases activeWorkoutSessionUseCases;
@override
final CloseWorkoutSessionUseCase closeWorkoutSessionUseCase; final CloseWorkoutSessionUseCase closeWorkoutSessionUseCase;
@override
final WorkoutHistoryUseCases workoutHistoryUseCases; final WorkoutHistoryUseCases workoutHistoryUseCases;
final SyncGateway syncGateway; final SyncGateway syncGateway;

View File

@ -3,6 +3,8 @@ import 'dart:convert';
import '../domain/domain.dart'; import '../domain/domain.dart';
import 'ports.dart'; import 'ports.dart';
const Object _useCaseUnchanged = Object();
final class ExerciseUseCases { final class ExerciseUseCases {
const ExerciseUseCases({ const ExerciseUseCases({
required this.repository, required this.repository,
@ -27,7 +29,21 @@ final class ExerciseUseCases {
ScoreInputMode scoreInputMode = ScoreInputMode.manual, ScoreInputMode scoreInputMode = ScoreInputMode.manual,
String? scoreLabel, String? scoreLabel,
String? scoreUnit, String? scoreUnit,
int? defaultTargetTimeSeconds,
int? defaultTargetReps,
double? defaultTargetScore,
int? defaultTargetScoreTimeMs,
}) async { }) async {
_validateExerciseDefaultTargets(
hasTimeMeasure: hasTimeMeasure,
hasRepsMeasure: hasRepsMeasure,
hasScoreMeasure: hasScoreMeasure,
scoreInputMode: scoreInputMode,
defaultTargetTimeSeconds: defaultTargetTimeSeconds,
defaultTargetReps: defaultTargetReps,
defaultTargetScore: defaultTargetScore,
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
);
final now = clock.now(); final now = clock.now();
final exercise = Exercise( final exercise = Exercise(
metadata: _newMetadata(ids, originDeviceId, now), metadata: _newMetadata(ids, originDeviceId, now),
@ -41,6 +57,10 @@ final class ExerciseUseCases {
scoreInputMode: scoreInputMode, scoreInputMode: scoreInputMode,
scoreLabel: scoreLabel, scoreLabel: scoreLabel,
scoreUnit: scoreUnit, scoreUnit: scoreUnit,
defaultTargetTimeSeconds: defaultTargetTimeSeconds,
defaultTargetReps: defaultTargetReps,
defaultTargetScore: defaultTargetScore,
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
); );
await repository.save(exercise); await repository.save(exercise);
return exercise; return exercise;
@ -66,11 +86,39 @@ final class ExerciseUseCases {
ScoreInputMode scoreInputMode = ScoreInputMode.manual, ScoreInputMode scoreInputMode = ScoreInputMode.manual,
String? scoreLabel, String? scoreLabel,
String? scoreUnit, String? scoreUnit,
Object? defaultTargetTimeSeconds = _useCaseUnchanged,
Object? defaultTargetReps = _useCaseUnchanged,
Object? defaultTargetScore = _useCaseUnchanged,
Object? defaultTargetScoreTimeMs = _useCaseUnchanged,
}) async { }) async {
final exercise = await repository.findById(id); final exercise = await repository.findById(id);
if (exercise == null) { if (exercise == null) {
throw const DomainException('Exercise not found.'); throw const DomainException('Exercise not found.');
} }
final resolvedDefaultTargetTimeSeconds =
defaultTargetTimeSeconds == _useCaseUnchanged
? exercise.defaultTargetTimeSeconds
: defaultTargetTimeSeconds as int?;
final resolvedDefaultTargetReps = defaultTargetReps == _useCaseUnchanged
? exercise.defaultTargetReps
: defaultTargetReps as int?;
final resolvedDefaultTargetScore = defaultTargetScore == _useCaseUnchanged
? exercise.defaultTargetScore
: (defaultTargetScore as num?)?.toDouble();
final resolvedDefaultTargetScoreTimeMs =
defaultTargetScoreTimeMs == _useCaseUnchanged
? exercise.defaultTargetScoreTimeMs
: defaultTargetScoreTimeMs as int?;
_validateExerciseDefaultTargets(
hasTimeMeasure: hasTimeMeasure,
hasRepsMeasure: hasRepsMeasure,
hasScoreMeasure: hasScoreMeasure,
scoreInputMode: scoreInputMode,
defaultTargetTimeSeconds: resolvedDefaultTargetTimeSeconds,
defaultTargetReps: resolvedDefaultTargetReps,
defaultTargetScore: resolvedDefaultTargetScore,
defaultTargetScoreTimeMs: resolvedDefaultTargetScoreTimeMs,
);
final updated = exercise.copyWith( final updated = exercise.copyWith(
metadata: exercise.metadata.touch(clock.now()), metadata: exercise.metadata.touch(clock.now()),
name: name, name: name,
@ -83,6 +131,10 @@ final class ExerciseUseCases {
scoreInputMode: scoreInputMode, scoreInputMode: scoreInputMode,
scoreLabel: scoreLabel, scoreLabel: scoreLabel,
scoreUnit: scoreUnit, scoreUnit: scoreUnit,
defaultTargetTimeSeconds: resolvedDefaultTargetTimeSeconds,
defaultTargetReps: resolvedDefaultTargetReps,
defaultTargetScore: resolvedDefaultTargetScore,
defaultTargetScoreTimeMs: resolvedDefaultTargetScoreTimeMs,
); );
await repository.save(updated); await repository.save(updated);
return updated; return updated;
@ -1271,6 +1323,49 @@ final class SetResultPositionState {
final ActiveSetResult? result; final ActiveSetResult? result;
} }
void _validateExerciseDefaultTargets({
required bool hasTimeMeasure,
required bool hasRepsMeasure,
required bool hasScoreMeasure,
required ScoreInputMode scoreInputMode,
required int? defaultTargetTimeSeconds,
required int? defaultTargetReps,
required double? defaultTargetScore,
required int? defaultTargetScoreTimeMs,
}) {
if (hasTimeMeasure &&
(defaultTargetTimeSeconds == null || defaultTargetTimeSeconds <= 0)) {
throw const DomainException(
'Default target time seconds must be positive.',
);
}
if (hasRepsMeasure && (defaultTargetReps == null || defaultTargetReps <= 0)) {
throw const DomainException('Default target reps must be positive.');
}
if (hasScoreMeasure &&
scoreInputMode == ScoreInputMode.manual &&
(defaultTargetScore == null || defaultTargetScore <= 0)) {
throw const DomainException('Default target score must be positive.');
}
if (defaultTargetScore != null &&
(!hasScoreMeasure || scoreInputMode != ScoreInputMode.manual)) {
throw const DomainException(
'Default target score requires manual score mode.',
);
}
if (defaultTargetScoreTimeMs != null && defaultTargetScoreTimeMs <= 0) {
throw const DomainException(
'Default target score time ms must be positive.',
);
}
if (defaultTargetScoreTimeMs != null &&
(!hasScoreMeasure || scoreInputMode != ScoreInputMode.stopwatch)) {
throw const DomainException(
'Default target score time ms requires stopwatch score mode.',
);
}
}
void _validateOverrideTargets( void _validateOverrideTargets(
String programSnapshotJson, String programSnapshotJson,
WorkoutTemplateExerciseOverrideConfig input, WorkoutTemplateExerciseOverrideConfig input,

View File

@ -132,6 +132,10 @@ final class Exercise {
this.scoreInputMode = ScoreInputMode.manual, this.scoreInputMode = ScoreInputMode.manual,
this.scoreLabel, this.scoreLabel,
this.scoreUnit, this.scoreUnit,
this.defaultTargetTimeSeconds,
this.defaultTargetReps,
this.defaultTargetScore,
this.defaultTargetScoreTimeMs,
this.archivedAt, this.archivedAt,
}) : name = _nonBlank(name, 'Exercise name') { }) : name = _nonBlank(name, 'Exercise name') {
_requireAtLeastOneMeasure( _requireAtLeastOneMeasure(
@ -148,6 +152,16 @@ final class Exercise {
_nonBlank(scoreLabel, 'Score label'); _nonBlank(scoreLabel, 'Score label');
_nonBlank(scoreUnit, 'Score unit'); _nonBlank(scoreUnit, 'Score unit');
} }
_requireNullablePositive(
defaultTargetTimeSeconds,
'Default target time seconds',
);
_requireNullablePositive(defaultTargetReps, 'Default target reps');
_requireNullablePositiveDouble(defaultTargetScore, 'Default target score');
_requireNullablePositive(
defaultTargetScoreTimeMs,
'Default target score time ms',
);
} }
final EntityMetadata metadata; final EntityMetadata metadata;
@ -161,6 +175,10 @@ final class Exercise {
final ScoreInputMode scoreInputMode; final ScoreInputMode scoreInputMode;
final String? scoreLabel; final String? scoreLabel;
final String? scoreUnit; final String? scoreUnit;
final int? defaultTargetTimeSeconds;
final int? defaultTargetReps;
final double? defaultTargetScore;
final int? defaultTargetScoreTimeMs;
final DateTime? archivedAt; final DateTime? archivedAt;
Set<WorkoutMeasure> get availableMeasures => { Set<WorkoutMeasure> get availableMeasures => {
@ -185,6 +203,10 @@ final class Exercise {
ScoreInputMode? scoreInputMode, ScoreInputMode? scoreInputMode,
Object? scoreLabel = _unchanged, Object? scoreLabel = _unchanged,
Object? scoreUnit = _unchanged, Object? scoreUnit = _unchanged,
Object? defaultTargetTimeSeconds = _unchanged,
Object? defaultTargetReps = _unchanged,
Object? defaultTargetScore = _unchanged,
Object? defaultTargetScoreTimeMs = _unchanged,
Object? archivedAt = _unchanged, Object? archivedAt = _unchanged,
}) { }) {
return Exercise( return Exercise(
@ -209,6 +231,18 @@ final class Exercise {
scoreUnit: scoreUnit == _unchanged scoreUnit: scoreUnit == _unchanged
? this.scoreUnit ? this.scoreUnit
: scoreUnit as String?, : scoreUnit as String?,
defaultTargetTimeSeconds: defaultTargetTimeSeconds == _unchanged
? this.defaultTargetTimeSeconds
: defaultTargetTimeSeconds as int?,
defaultTargetReps: defaultTargetReps == _unchanged
? this.defaultTargetReps
: defaultTargetReps as int?,
defaultTargetScore: defaultTargetScore == _unchanged
? this.defaultTargetScore
: defaultTargetScore as double?,
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs == _unchanged
? this.defaultTargetScoreTimeMs
: defaultTargetScoreTimeMs as int?,
archivedAt: archivedAt == _unchanged archivedAt: archivedAt == _unchanged
? this.archivedAt ? this.archivedAt
: archivedAt as DateTime?, : archivedAt as DateTime?,
@ -371,10 +405,10 @@ final class ProgramExercise {
timeEnabled: enabledMeasures.contains(WorkoutMeasure.time), timeEnabled: enabledMeasures.contains(WorkoutMeasure.time),
repsEnabled: enabledMeasures.contains(WorkoutMeasure.reps), repsEnabled: enabledMeasures.contains(WorkoutMeasure.reps),
scoreEnabled: enabledMeasures.contains(WorkoutMeasure.score), scoreEnabled: enabledMeasures.contains(WorkoutMeasure.score),
targetTimeSeconds: targetTimeSeconds, targetTimeSeconds: targetTimeSeconds ?? exercise.defaultTargetTimeSeconds,
targetReps: targetReps, targetReps: targetReps ?? exercise.defaultTargetReps,
targetScore: targetScore, targetScore: targetScore ?? exercise.defaultTargetScore,
targetScoreTimeMs: targetScoreTimeMs, targetScoreTimeMs: targetScoreTimeMs ?? exercise.defaultTargetScoreTimeMs,
restSecondsOverride: restSecondsOverride, restSecondsOverride: restSecondsOverride,
); );
} }
@ -927,6 +961,12 @@ void _requireNullableNonNegativeDouble(double? value, String label) {
} }
} }
void _requireNullablePositiveDouble(double? value, String label) {
if (value != null && value <= 0) {
throw DomainException('$label must be positive.');
}
}
void _requireScoreTargetShape({ void _requireScoreTargetShape({
required bool scoreEnabled, required bool scoreEnabled,
required ScoreInputMode scoreInputMode, required ScoreInputMode scoreInputMode,

View File

@ -36,7 +36,7 @@ final class AppDatabase extends _$AppDatabase {
} }
@override @override
int get schemaVersion => 3; int get schemaVersion => 4;
@override @override
MigrationStrategy get migration { MigrationStrategy get migration {
@ -60,6 +60,9 @@ final class AppDatabase extends _$AppDatabase {
if (from < 3) { if (from < 3) {
await _migrateToSchema3(migrator); await _migrateToSchema3(migrator);
} }
if (from < 4) {
await _migrateToSchema4();
}
await _createIndexes(); await _createIndexes();
}, },
beforeOpen: (details) async { beforeOpen: (details) async {
@ -202,4 +205,25 @@ extension on AppDatabase {
); );
await migrator.createTable(activeScoreStopwatchStates); await migrator.createTable(activeScoreStopwatchStates);
} }
Future<void> _migrateToSchema4() async {
await customStatement(
'ALTER TABLE exercises ADD COLUMN default_target_time_seconds INTEGER '
'CHECK (default_target_time_seconds IS NULL OR '
'default_target_time_seconds > 0)',
);
await customStatement(
'ALTER TABLE exercises ADD COLUMN default_target_reps INTEGER '
'CHECK (default_target_reps IS NULL OR default_target_reps > 0)',
);
await customStatement(
'ALTER TABLE exercises ADD COLUMN default_target_score REAL '
'CHECK (default_target_score IS NULL OR default_target_score > 0)',
);
await customStatement(
'ALTER TABLE exercises ADD COLUMN default_target_score_time_ms INTEGER '
'CHECK (default_target_score_time_ms IS NULL OR '
'default_target_score_time_ms > 0)',
);
}
} }

View File

@ -8063,6 +8063,50 @@ class $ExercisesTable extends Exercises
type: DriftSqlType.string, type: DriftSqlType.string,
requiredDuringInsert: false, requiredDuringInsert: false,
); );
static const VerificationMeta _defaultTargetTimeSecondsMeta =
const VerificationMeta('defaultTargetTimeSeconds');
@override
late final GeneratedColumn<int> defaultTargetTimeSeconds =
GeneratedColumn<int>(
'default_target_time_seconds',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
static const VerificationMeta _defaultTargetRepsMeta = const VerificationMeta(
'defaultTargetReps',
);
@override
late final GeneratedColumn<int> defaultTargetReps = GeneratedColumn<int>(
'default_target_reps',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
static const VerificationMeta _defaultTargetScoreMeta =
const VerificationMeta('defaultTargetScore');
@override
late final GeneratedColumn<double> defaultTargetScore =
GeneratedColumn<double>(
'default_target_score',
aliasedName,
true,
type: DriftSqlType.double,
requiredDuringInsert: false,
);
static const VerificationMeta _defaultTargetScoreTimeMsMeta =
const VerificationMeta('defaultTargetScoreTimeMs');
@override
late final GeneratedColumn<int> defaultTargetScoreTimeMs =
GeneratedColumn<int>(
'default_target_score_time_ms',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
static const VerificationMeta _archivedAtMeta = const VerificationMeta( static const VerificationMeta _archivedAtMeta = const VerificationMeta(
'archivedAt', 'archivedAt',
); );
@ -8097,6 +8141,10 @@ class $ExercisesTable extends Exercises
scoreInputMode, scoreInputMode,
scoreLabel, scoreLabel,
scoreUnit, scoreUnit,
defaultTargetTimeSeconds,
defaultTargetReps,
defaultTargetScore,
defaultTargetScoreTimeMs,
archivedAt, archivedAt,
]; ];
@override @override
@ -8293,6 +8341,42 @@ class $ExercisesTable extends Exercises
scoreUnit.isAcceptableOrUnknown(data['score_unit']!, _scoreUnitMeta), scoreUnit.isAcceptableOrUnknown(data['score_unit']!, _scoreUnitMeta),
); );
} }
if (data.containsKey('default_target_time_seconds')) {
context.handle(
_defaultTargetTimeSecondsMeta,
defaultTargetTimeSeconds.isAcceptableOrUnknown(
data['default_target_time_seconds']!,
_defaultTargetTimeSecondsMeta,
),
);
}
if (data.containsKey('default_target_reps')) {
context.handle(
_defaultTargetRepsMeta,
defaultTargetReps.isAcceptableOrUnknown(
data['default_target_reps']!,
_defaultTargetRepsMeta,
),
);
}
if (data.containsKey('default_target_score')) {
context.handle(
_defaultTargetScoreMeta,
defaultTargetScore.isAcceptableOrUnknown(
data['default_target_score']!,
_defaultTargetScoreMeta,
),
);
}
if (data.containsKey('default_target_score_time_ms')) {
context.handle(
_defaultTargetScoreTimeMsMeta,
defaultTargetScoreTimeMs.isAcceptableOrUnknown(
data['default_target_score_time_ms']!,
_defaultTargetScoreTimeMsMeta,
),
);
}
if (data.containsKey('archived_at')) { if (data.containsKey('archived_at')) {
context.handle( context.handle(
_archivedAtMeta, _archivedAtMeta,
@ -8392,6 +8476,22 @@ class $ExercisesTable extends Exercises
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}score_unit'], data['${effectivePrefix}score_unit'],
), ),
defaultTargetTimeSeconds: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}default_target_time_seconds'],
),
defaultTargetReps: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}default_target_reps'],
),
defaultTargetScore: attachedDatabase.typeMapping.read(
DriftSqlType.double,
data['${effectivePrefix}default_target_score'],
),
defaultTargetScoreTimeMs: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}default_target_score_time_ms'],
),
archivedAt: attachedDatabase.typeMapping.read( archivedAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime, DriftSqlType.dateTime,
data['${effectivePrefix}archived_at'], data['${effectivePrefix}archived_at'],
@ -8427,6 +8527,10 @@ class Exercise extends DataClass implements Insertable<Exercise> {
final String scoreInputMode; final String scoreInputMode;
final String? scoreLabel; final String? scoreLabel;
final String? scoreUnit; final String? scoreUnit;
final int? defaultTargetTimeSeconds;
final int? defaultTargetReps;
final double? defaultTargetScore;
final int? defaultTargetScoreTimeMs;
final DateTime? archivedAt; final DateTime? archivedAt;
const Exercise({ const Exercise({
required this.id, required this.id,
@ -8450,6 +8554,10 @@ class Exercise extends DataClass implements Insertable<Exercise> {
required this.scoreInputMode, required this.scoreInputMode,
this.scoreLabel, this.scoreLabel,
this.scoreUnit, this.scoreUnit,
this.defaultTargetTimeSeconds,
this.defaultTargetReps,
this.defaultTargetScore,
this.defaultTargetScoreTimeMs,
this.archivedAt, this.archivedAt,
}); });
@override @override
@ -8494,6 +8602,22 @@ class Exercise extends DataClass implements Insertable<Exercise> {
if (!nullToAbsent || scoreUnit != null) { if (!nullToAbsent || scoreUnit != null) {
map['score_unit'] = Variable<String>(scoreUnit); map['score_unit'] = Variable<String>(scoreUnit);
} }
if (!nullToAbsent || defaultTargetTimeSeconds != null) {
map['default_target_time_seconds'] = Variable<int>(
defaultTargetTimeSeconds,
);
}
if (!nullToAbsent || defaultTargetReps != null) {
map['default_target_reps'] = Variable<int>(defaultTargetReps);
}
if (!nullToAbsent || defaultTargetScore != null) {
map['default_target_score'] = Variable<double>(defaultTargetScore);
}
if (!nullToAbsent || defaultTargetScoreTimeMs != null) {
map['default_target_score_time_ms'] = Variable<int>(
defaultTargetScoreTimeMs,
);
}
if (!nullToAbsent || archivedAt != null) { if (!nullToAbsent || archivedAt != null) {
map['archived_at'] = Variable<DateTime>(archivedAt); map['archived_at'] = Variable<DateTime>(archivedAt);
} }
@ -8541,6 +8665,18 @@ class Exercise extends DataClass implements Insertable<Exercise> {
scoreUnit: scoreUnit == null && nullToAbsent scoreUnit: scoreUnit == null && nullToAbsent
? const Value.absent() ? const Value.absent()
: Value(scoreUnit), : Value(scoreUnit),
defaultTargetTimeSeconds: defaultTargetTimeSeconds == null && nullToAbsent
? const Value.absent()
: Value(defaultTargetTimeSeconds),
defaultTargetReps: defaultTargetReps == null && nullToAbsent
? const Value.absent()
: Value(defaultTargetReps),
defaultTargetScore: defaultTargetScore == null && nullToAbsent
? const Value.absent()
: Value(defaultTargetScore),
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs == null && nullToAbsent
? const Value.absent()
: Value(defaultTargetScoreTimeMs),
archivedAt: archivedAt == null && nullToAbsent archivedAt: archivedAt == null && nullToAbsent
? const Value.absent() ? const Value.absent()
: Value(archivedAt), : Value(archivedAt),
@ -8576,6 +8712,16 @@ class Exercise extends DataClass implements Insertable<Exercise> {
scoreInputMode: serializer.fromJson<String>(json['scoreInputMode']), scoreInputMode: serializer.fromJson<String>(json['scoreInputMode']),
scoreLabel: serializer.fromJson<String?>(json['scoreLabel']), scoreLabel: serializer.fromJson<String?>(json['scoreLabel']),
scoreUnit: serializer.fromJson<String?>(json['scoreUnit']), scoreUnit: serializer.fromJson<String?>(json['scoreUnit']),
defaultTargetTimeSeconds: serializer.fromJson<int?>(
json['defaultTargetTimeSeconds'],
),
defaultTargetReps: serializer.fromJson<int?>(json['defaultTargetReps']),
defaultTargetScore: serializer.fromJson<double?>(
json['defaultTargetScore'],
),
defaultTargetScoreTimeMs: serializer.fromJson<int?>(
json['defaultTargetScoreTimeMs'],
),
archivedAt: serializer.fromJson<DateTime?>(json['archivedAt']), archivedAt: serializer.fromJson<DateTime?>(json['archivedAt']),
); );
} }
@ -8604,6 +8750,14 @@ class Exercise extends DataClass implements Insertable<Exercise> {
'scoreInputMode': serializer.toJson<String>(scoreInputMode), 'scoreInputMode': serializer.toJson<String>(scoreInputMode),
'scoreLabel': serializer.toJson<String?>(scoreLabel), 'scoreLabel': serializer.toJson<String?>(scoreLabel),
'scoreUnit': serializer.toJson<String?>(scoreUnit), 'scoreUnit': serializer.toJson<String?>(scoreUnit),
'defaultTargetTimeSeconds': serializer.toJson<int?>(
defaultTargetTimeSeconds,
),
'defaultTargetReps': serializer.toJson<int?>(defaultTargetReps),
'defaultTargetScore': serializer.toJson<double?>(defaultTargetScore),
'defaultTargetScoreTimeMs': serializer.toJson<int?>(
defaultTargetScoreTimeMs,
),
'archivedAt': serializer.toJson<DateTime?>(archivedAt), 'archivedAt': serializer.toJson<DateTime?>(archivedAt),
}; };
} }
@ -8630,6 +8784,10 @@ class Exercise extends DataClass implements Insertable<Exercise> {
String? scoreInputMode, String? scoreInputMode,
Value<String?> scoreLabel = const Value.absent(), Value<String?> scoreLabel = const Value.absent(),
Value<String?> scoreUnit = const Value.absent(), Value<String?> scoreUnit = const Value.absent(),
Value<int?> defaultTargetTimeSeconds = const Value.absent(),
Value<int?> defaultTargetReps = const Value.absent(),
Value<double?> defaultTargetScore = const Value.absent(),
Value<int?> defaultTargetScoreTimeMs = const Value.absent(),
Value<DateTime?> archivedAt = const Value.absent(), Value<DateTime?> archivedAt = const Value.absent(),
}) => Exercise( }) => Exercise(
id: id ?? this.id, id: id ?? this.id,
@ -8657,6 +8815,18 @@ class Exercise extends DataClass implements Insertable<Exercise> {
scoreInputMode: scoreInputMode ?? this.scoreInputMode, scoreInputMode: scoreInputMode ?? this.scoreInputMode,
scoreLabel: scoreLabel.present ? scoreLabel.value : this.scoreLabel, scoreLabel: scoreLabel.present ? scoreLabel.value : this.scoreLabel,
scoreUnit: scoreUnit.present ? scoreUnit.value : this.scoreUnit, scoreUnit: scoreUnit.present ? scoreUnit.value : this.scoreUnit,
defaultTargetTimeSeconds: defaultTargetTimeSeconds.present
? defaultTargetTimeSeconds.value
: this.defaultTargetTimeSeconds,
defaultTargetReps: defaultTargetReps.present
? defaultTargetReps.value
: this.defaultTargetReps,
defaultTargetScore: defaultTargetScore.present
? defaultTargetScore.value
: this.defaultTargetScore,
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs.present
? defaultTargetScoreTimeMs.value
: this.defaultTargetScoreTimeMs,
archivedAt: archivedAt.present ? archivedAt.value : this.archivedAt, archivedAt: archivedAt.present ? archivedAt.value : this.archivedAt,
); );
Exercise copyWithCompanion(ExercisesCompanion data) { Exercise copyWithCompanion(ExercisesCompanion data) {
@ -8710,6 +8880,18 @@ class Exercise extends DataClass implements Insertable<Exercise> {
? data.scoreLabel.value ? data.scoreLabel.value
: this.scoreLabel, : this.scoreLabel,
scoreUnit: data.scoreUnit.present ? data.scoreUnit.value : this.scoreUnit, scoreUnit: data.scoreUnit.present ? data.scoreUnit.value : this.scoreUnit,
defaultTargetTimeSeconds: data.defaultTargetTimeSeconds.present
? data.defaultTargetTimeSeconds.value
: this.defaultTargetTimeSeconds,
defaultTargetReps: data.defaultTargetReps.present
? data.defaultTargetReps.value
: this.defaultTargetReps,
defaultTargetScore: data.defaultTargetScore.present
? data.defaultTargetScore.value
: this.defaultTargetScore,
defaultTargetScoreTimeMs: data.defaultTargetScoreTimeMs.present
? data.defaultTargetScoreTimeMs.value
: this.defaultTargetScoreTimeMs,
archivedAt: data.archivedAt.present archivedAt: data.archivedAt.present
? data.archivedAt.value ? data.archivedAt.value
: this.archivedAt, : this.archivedAt,
@ -8740,6 +8922,10 @@ class Exercise extends DataClass implements Insertable<Exercise> {
..write('scoreInputMode: $scoreInputMode, ') ..write('scoreInputMode: $scoreInputMode, ')
..write('scoreLabel: $scoreLabel, ') ..write('scoreLabel: $scoreLabel, ')
..write('scoreUnit: $scoreUnit, ') ..write('scoreUnit: $scoreUnit, ')
..write('defaultTargetTimeSeconds: $defaultTargetTimeSeconds, ')
..write('defaultTargetReps: $defaultTargetReps, ')
..write('defaultTargetScore: $defaultTargetScore, ')
..write('defaultTargetScoreTimeMs: $defaultTargetScoreTimeMs, ')
..write('archivedAt: $archivedAt') ..write('archivedAt: $archivedAt')
..write(')')) ..write(')'))
.toString(); .toString();
@ -8768,6 +8954,10 @@ class Exercise extends DataClass implements Insertable<Exercise> {
scoreInputMode, scoreInputMode,
scoreLabel, scoreLabel,
scoreUnit, scoreUnit,
defaultTargetTimeSeconds,
defaultTargetReps,
defaultTargetScore,
defaultTargetScoreTimeMs,
archivedAt, archivedAt,
]); ]);
@override @override
@ -8795,6 +8985,10 @@ class Exercise extends DataClass implements Insertable<Exercise> {
other.scoreInputMode == this.scoreInputMode && other.scoreInputMode == this.scoreInputMode &&
other.scoreLabel == this.scoreLabel && other.scoreLabel == this.scoreLabel &&
other.scoreUnit == this.scoreUnit && other.scoreUnit == this.scoreUnit &&
other.defaultTargetTimeSeconds == this.defaultTargetTimeSeconds &&
other.defaultTargetReps == this.defaultTargetReps &&
other.defaultTargetScore == this.defaultTargetScore &&
other.defaultTargetScoreTimeMs == this.defaultTargetScoreTimeMs &&
other.archivedAt == this.archivedAt); other.archivedAt == this.archivedAt);
} }
@ -8820,6 +9014,10 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
final Value<String> scoreInputMode; final Value<String> scoreInputMode;
final Value<String?> scoreLabel; final Value<String?> scoreLabel;
final Value<String?> scoreUnit; final Value<String?> scoreUnit;
final Value<int?> defaultTargetTimeSeconds;
final Value<int?> defaultTargetReps;
final Value<double?> defaultTargetScore;
final Value<int?> defaultTargetScoreTimeMs;
final Value<DateTime?> archivedAt; final Value<DateTime?> archivedAt;
final Value<int> rowid; final Value<int> rowid;
const ExercisesCompanion({ const ExercisesCompanion({
@ -8844,6 +9042,10 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
this.scoreInputMode = const Value.absent(), this.scoreInputMode = const Value.absent(),
this.scoreLabel = const Value.absent(), this.scoreLabel = const Value.absent(),
this.scoreUnit = const Value.absent(), this.scoreUnit = const Value.absent(),
this.defaultTargetTimeSeconds = const Value.absent(),
this.defaultTargetReps = const Value.absent(),
this.defaultTargetScore = const Value.absent(),
this.defaultTargetScoreTimeMs = const Value.absent(),
this.archivedAt = const Value.absent(), this.archivedAt = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}); });
@ -8869,6 +9071,10 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
this.scoreInputMode = const Value.absent(), this.scoreInputMode = const Value.absent(),
this.scoreLabel = const Value.absent(), this.scoreLabel = const Value.absent(),
this.scoreUnit = const Value.absent(), this.scoreUnit = const Value.absent(),
this.defaultTargetTimeSeconds = const Value.absent(),
this.defaultTargetReps = const Value.absent(),
this.defaultTargetScore = const Value.absent(),
this.defaultTargetScoreTimeMs = const Value.absent(),
this.archivedAt = const Value.absent(), this.archivedAt = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}) : id = Value(id), }) : id = Value(id),
@ -8903,6 +9109,10 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
Expression<String>? scoreInputMode, Expression<String>? scoreInputMode,
Expression<String>? scoreLabel, Expression<String>? scoreLabel,
Expression<String>? scoreUnit, Expression<String>? scoreUnit,
Expression<int>? defaultTargetTimeSeconds,
Expression<int>? defaultTargetReps,
Expression<double>? defaultTargetScore,
Expression<int>? defaultTargetScoreTimeMs,
Expression<DateTime>? archivedAt, Expression<DateTime>? archivedAt,
Expression<int>? rowid, Expression<int>? rowid,
}) { }) {
@ -8929,6 +9139,13 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
if (scoreInputMode != null) 'score_input_mode': scoreInputMode, if (scoreInputMode != null) 'score_input_mode': scoreInputMode,
if (scoreLabel != null) 'score_label': scoreLabel, if (scoreLabel != null) 'score_label': scoreLabel,
if (scoreUnit != null) 'score_unit': scoreUnit, if (scoreUnit != null) 'score_unit': scoreUnit,
if (defaultTargetTimeSeconds != null)
'default_target_time_seconds': defaultTargetTimeSeconds,
if (defaultTargetReps != null) 'default_target_reps': defaultTargetReps,
if (defaultTargetScore != null)
'default_target_score': defaultTargetScore,
if (defaultTargetScoreTimeMs != null)
'default_target_score_time_ms': defaultTargetScoreTimeMs,
if (archivedAt != null) 'archived_at': archivedAt, if (archivedAt != null) 'archived_at': archivedAt,
if (rowid != null) 'rowid': rowid, if (rowid != null) 'rowid': rowid,
}); });
@ -8956,6 +9173,10 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
Value<String>? scoreInputMode, Value<String>? scoreInputMode,
Value<String?>? scoreLabel, Value<String?>? scoreLabel,
Value<String?>? scoreUnit, Value<String?>? scoreUnit,
Value<int?>? defaultTargetTimeSeconds,
Value<int?>? defaultTargetReps,
Value<double?>? defaultTargetScore,
Value<int?>? defaultTargetScoreTimeMs,
Value<DateTime?>? archivedAt, Value<DateTime?>? archivedAt,
Value<int>? rowid, Value<int>? rowid,
}) { }) {
@ -8981,6 +9202,12 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
scoreInputMode: scoreInputMode ?? this.scoreInputMode, scoreInputMode: scoreInputMode ?? this.scoreInputMode,
scoreLabel: scoreLabel ?? this.scoreLabel, scoreLabel: scoreLabel ?? this.scoreLabel,
scoreUnit: scoreUnit ?? this.scoreUnit, scoreUnit: scoreUnit ?? this.scoreUnit,
defaultTargetTimeSeconds:
defaultTargetTimeSeconds ?? this.defaultTargetTimeSeconds,
defaultTargetReps: defaultTargetReps ?? this.defaultTargetReps,
defaultTargetScore: defaultTargetScore ?? this.defaultTargetScore,
defaultTargetScoreTimeMs:
defaultTargetScoreTimeMs ?? this.defaultTargetScoreTimeMs,
archivedAt: archivedAt ?? this.archivedAt, archivedAt: archivedAt ?? this.archivedAt,
rowid: rowid ?? this.rowid, rowid: rowid ?? this.rowid,
); );
@ -9054,6 +9281,22 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
if (scoreUnit.present) { if (scoreUnit.present) {
map['score_unit'] = Variable<String>(scoreUnit.value); map['score_unit'] = Variable<String>(scoreUnit.value);
} }
if (defaultTargetTimeSeconds.present) {
map['default_target_time_seconds'] = Variable<int>(
defaultTargetTimeSeconds.value,
);
}
if (defaultTargetReps.present) {
map['default_target_reps'] = Variable<int>(defaultTargetReps.value);
}
if (defaultTargetScore.present) {
map['default_target_score'] = Variable<double>(defaultTargetScore.value);
}
if (defaultTargetScoreTimeMs.present) {
map['default_target_score_time_ms'] = Variable<int>(
defaultTargetScoreTimeMs.value,
);
}
if (archivedAt.present) { if (archivedAt.present) {
map['archived_at'] = Variable<DateTime>(archivedAt.value); map['archived_at'] = Variable<DateTime>(archivedAt.value);
} }
@ -9087,6 +9330,10 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
..write('scoreInputMode: $scoreInputMode, ') ..write('scoreInputMode: $scoreInputMode, ')
..write('scoreLabel: $scoreLabel, ') ..write('scoreLabel: $scoreLabel, ')
..write('scoreUnit: $scoreUnit, ') ..write('scoreUnit: $scoreUnit, ')
..write('defaultTargetTimeSeconds: $defaultTargetTimeSeconds, ')
..write('defaultTargetReps: $defaultTargetReps, ')
..write('defaultTargetScore: $defaultTargetScore, ')
..write('defaultTargetScoreTimeMs: $defaultTargetScoreTimeMs, ')
..write('archivedAt: $archivedAt, ') ..write('archivedAt: $archivedAt, ')
..write('rowid: $rowid') ..write('rowid: $rowid')
..write(')')) ..write(')'))
@ -22641,6 +22888,10 @@ typedef $$ExercisesTableCreateCompanionBuilder =
Value<String> scoreInputMode, Value<String> scoreInputMode,
Value<String?> scoreLabel, Value<String?> scoreLabel,
Value<String?> scoreUnit, Value<String?> scoreUnit,
Value<int?> defaultTargetTimeSeconds,
Value<int?> defaultTargetReps,
Value<double?> defaultTargetScore,
Value<int?> defaultTargetScoreTimeMs,
Value<DateTime?> archivedAt, Value<DateTime?> archivedAt,
Value<int> rowid, Value<int> rowid,
}); });
@ -22667,6 +22918,10 @@ typedef $$ExercisesTableUpdateCompanionBuilder =
Value<String> scoreInputMode, Value<String> scoreInputMode,
Value<String?> scoreLabel, Value<String?> scoreLabel,
Value<String?> scoreUnit, Value<String?> scoreUnit,
Value<int?> defaultTargetTimeSeconds,
Value<int?> defaultTargetReps,
Value<double?> defaultTargetScore,
Value<int?> defaultTargetScoreTimeMs,
Value<DateTime?> archivedAt, Value<DateTime?> archivedAt,
Value<int> rowid, Value<int> rowid,
}); });
@ -22834,6 +23089,26 @@ class $$ExercisesTableFilterComposer
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
ColumnFilters<int> get defaultTargetTimeSeconds => $composableBuilder(
column: $table.defaultTargetTimeSeconds,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<int> get defaultTargetReps => $composableBuilder(
column: $table.defaultTargetReps,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<double> get defaultTargetScore => $composableBuilder(
column: $table.defaultTargetScore,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<int> get defaultTargetScoreTimeMs => $composableBuilder(
column: $table.defaultTargetScoreTimeMs,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<DateTime> get archivedAt => $composableBuilder( ColumnFilters<DateTime> get archivedAt => $composableBuilder(
column: $table.archivedAt, column: $table.archivedAt,
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
@ -23015,6 +23290,26 @@ class $$ExercisesTableOrderingComposer
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<int> get defaultTargetTimeSeconds => $composableBuilder(
column: $table.defaultTargetTimeSeconds,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<int> get defaultTargetReps => $composableBuilder(
column: $table.defaultTargetReps,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<double> get defaultTargetScore => $composableBuilder(
column: $table.defaultTargetScore,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<int> get defaultTargetScoreTimeMs => $composableBuilder(
column: $table.defaultTargetScoreTimeMs,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<DateTime> get archivedAt => $composableBuilder( ColumnOrderings<DateTime> get archivedAt => $composableBuilder(
column: $table.archivedAt, column: $table.archivedAt,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
@ -23157,6 +23452,26 @@ class $$ExercisesTableAnnotationComposer
GeneratedColumn<String> get scoreUnit => GeneratedColumn<String> get scoreUnit =>
$composableBuilder(column: $table.scoreUnit, builder: (column) => column); $composableBuilder(column: $table.scoreUnit, builder: (column) => column);
GeneratedColumn<int> get defaultTargetTimeSeconds => $composableBuilder(
column: $table.defaultTargetTimeSeconds,
builder: (column) => column,
);
GeneratedColumn<int> get defaultTargetReps => $composableBuilder(
column: $table.defaultTargetReps,
builder: (column) => column,
);
GeneratedColumn<double> get defaultTargetScore => $composableBuilder(
column: $table.defaultTargetScore,
builder: (column) => column,
);
GeneratedColumn<int> get defaultTargetScoreTimeMs => $composableBuilder(
column: $table.defaultTargetScoreTimeMs,
builder: (column) => column,
);
GeneratedColumn<DateTime> get archivedAt => $composableBuilder( GeneratedColumn<DateTime> get archivedAt => $composableBuilder(
column: $table.archivedAt, column: $table.archivedAt,
builder: (column) => column, builder: (column) => column,
@ -23287,6 +23602,10 @@ class $$ExercisesTableTableManager
Value<String> scoreInputMode = const Value.absent(), Value<String> scoreInputMode = const Value.absent(),
Value<String?> scoreLabel = const Value.absent(), Value<String?> scoreLabel = const Value.absent(),
Value<String?> scoreUnit = const Value.absent(), Value<String?> scoreUnit = const Value.absent(),
Value<int?> defaultTargetTimeSeconds = const Value.absent(),
Value<int?> defaultTargetReps = const Value.absent(),
Value<double?> defaultTargetScore = const Value.absent(),
Value<int?> defaultTargetScoreTimeMs = const Value.absent(),
Value<DateTime?> archivedAt = const Value.absent(), Value<DateTime?> archivedAt = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => ExercisesCompanion( }) => ExercisesCompanion(
@ -23311,6 +23630,10 @@ class $$ExercisesTableTableManager
scoreInputMode: scoreInputMode, scoreInputMode: scoreInputMode,
scoreLabel: scoreLabel, scoreLabel: scoreLabel,
scoreUnit: scoreUnit, scoreUnit: scoreUnit,
defaultTargetTimeSeconds: defaultTargetTimeSeconds,
defaultTargetReps: defaultTargetReps,
defaultTargetScore: defaultTargetScore,
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
archivedAt: archivedAt, archivedAt: archivedAt,
rowid: rowid, rowid: rowid,
), ),
@ -23337,6 +23660,10 @@ class $$ExercisesTableTableManager
Value<String> scoreInputMode = const Value.absent(), Value<String> scoreInputMode = const Value.absent(),
Value<String?> scoreLabel = const Value.absent(), Value<String?> scoreLabel = const Value.absent(),
Value<String?> scoreUnit = const Value.absent(), Value<String?> scoreUnit = const Value.absent(),
Value<int?> defaultTargetTimeSeconds = const Value.absent(),
Value<int?> defaultTargetReps = const Value.absent(),
Value<double?> defaultTargetScore = const Value.absent(),
Value<int?> defaultTargetScoreTimeMs = const Value.absent(),
Value<DateTime?> archivedAt = const Value.absent(), Value<DateTime?> archivedAt = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => ExercisesCompanion.insert( }) => ExercisesCompanion.insert(
@ -23361,6 +23688,10 @@ class $$ExercisesTableTableManager
scoreInputMode: scoreInputMode, scoreInputMode: scoreInputMode,
scoreLabel: scoreLabel, scoreLabel: scoreLabel,
scoreUnit: scoreUnit, scoreUnit: scoreUnit,
defaultTargetTimeSeconds: defaultTargetTimeSeconds,
defaultTargetReps: defaultTargetReps,
defaultTargetScore: defaultTargetScore,
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
archivedAt: archivedAt, archivedAt: archivedAt,
rowid: rowid, rowid: rowid,
), ),

View File

@ -966,6 +966,10 @@ db.ExercisesCompanion _exerciseCompanion(domain.Exercise exercise) {
scoreInputMode: Value(_scoreInputModeToDb(exercise.scoreInputMode)), scoreInputMode: Value(_scoreInputModeToDb(exercise.scoreInputMode)),
scoreLabel: Value(exercise.scoreLabel), scoreLabel: Value(exercise.scoreLabel),
scoreUnit: Value(exercise.scoreUnit), scoreUnit: Value(exercise.scoreUnit),
defaultTargetTimeSeconds: Value(exercise.defaultTargetTimeSeconds),
defaultTargetReps: Value(exercise.defaultTargetReps),
defaultTargetScore: Value(exercise.defaultTargetScore),
defaultTargetScoreTimeMs: Value(exercise.defaultTargetScoreTimeMs),
archivedAt: Value(_utcOrNull(exercise.archivedAt)), archivedAt: Value(_utcOrNull(exercise.archivedAt)),
); );
} }
@ -983,6 +987,10 @@ domain.Exercise _exerciseFromRow(db.Exercise row) {
scoreInputMode: _scoreInputModeFromDb(row.scoreInputMode), scoreInputMode: _scoreInputModeFromDb(row.scoreInputMode),
scoreLabel: row.scoreLabel, scoreLabel: row.scoreLabel,
scoreUnit: row.scoreUnit, scoreUnit: row.scoreUnit,
defaultTargetTimeSeconds: row.defaultTargetTimeSeconds,
defaultTargetReps: row.defaultTargetReps,
defaultTargetScore: row.defaultTargetScore,
defaultTargetScoreTimeMs: row.defaultTargetScoreTimeMs,
archivedAt: _utcOrNull(row.archivedAt), archivedAt: _utcOrNull(row.archivedAt),
); );
} }

View File

@ -69,6 +69,10 @@ class Exercises extends SyncableTable {
text().withDefault(const Constant('manual'))(); text().withDefault(const Constant('manual'))();
TextColumn get scoreLabel => text().nullable()(); TextColumn get scoreLabel => text().nullable()();
TextColumn get scoreUnit => text().nullable()(); TextColumn get scoreUnit => text().nullable()();
IntColumn get defaultTargetTimeSeconds => integer().nullable()();
IntColumn get defaultTargetReps => integer().nullable()();
RealColumn get defaultTargetScore => real().nullable()();
IntColumn get defaultTargetScoreTimeMs => integer().nullable()();
DateTimeColumn get archivedAt => dateTime().nullable()(); DateTimeColumn get archivedAt => dateTime().nullable()();
@override @override
@ -79,6 +83,12 @@ class Exercises extends SyncableTable {
"CHECK (score_input_mode != 'manual' OR NOT has_score_measure OR " "CHECK (score_input_mode != 'manual' OR NOT has_score_measure OR "
'(score_label IS NOT NULL AND length(trim(score_label)) > 0 ' '(score_label IS NOT NULL AND length(trim(score_label)) > 0 '
'AND score_unit IS NOT NULL AND length(trim(score_unit)) > 0))', 'AND score_unit IS NOT NULL AND length(trim(score_unit)) > 0))',
'CHECK (default_target_time_seconds IS NULL OR '
'default_target_time_seconds > 0)',
'CHECK (default_target_reps IS NULL OR default_target_reps > 0)',
'CHECK (default_target_score IS NULL OR default_target_score > 0)',
'CHECK (default_target_score_time_ms IS NULL OR '
'default_target_score_time_ms > 0)',
]; ];
} }

View File

@ -287,6 +287,10 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
late final TextEditingController _descriptionController; late final TextEditingController _descriptionController;
late final TextEditingController _scoreLabelController; late final TextEditingController _scoreLabelController;
late final TextEditingController _scoreUnitController; late final TextEditingController _scoreUnitController;
late final TextEditingController _defaultTimeController;
late final TextEditingController _defaultRepsController;
late final TextEditingController _defaultScoreController;
late final TextEditingController _defaultScoreTimeController;
String? _imageMediaId; String? _imageMediaId;
String? _videoMediaId; String? _videoMediaId;
String? _selectedImageName; String? _selectedImageName;
@ -311,6 +315,20 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
_descriptionController = TextEditingController(text: exercise?.description); _descriptionController = TextEditingController(text: exercise?.description);
_scoreLabelController = TextEditingController(text: exercise?.scoreLabel); _scoreLabelController = TextEditingController(text: exercise?.scoreLabel);
_scoreUnitController = TextEditingController(text: exercise?.scoreUnit); _scoreUnitController = TextEditingController(text: exercise?.scoreUnit);
_defaultTimeController = TextEditingController(
text: _optionalIntText(exercise?.defaultTargetTimeSeconds),
);
_defaultRepsController = TextEditingController(
text: _optionalIntText(exercise?.defaultTargetReps),
);
_defaultScoreController = TextEditingController(
text: _optionalDoubleText(exercise?.defaultTargetScore),
);
_defaultScoreTimeController = TextEditingController(
text: _optionalDoubleText(
_millisecondsToSeconds(exercise?.defaultTargetScoreTimeMs),
),
);
_imageMediaId = exercise?.imageMediaId; _imageMediaId = exercise?.imageMediaId;
_videoMediaId = exercise?.videoMediaId; _videoMediaId = exercise?.videoMediaId;
_hasTime = exercise?.hasTimeMeasure ?? true; _hasTime = exercise?.hasTimeMeasure ?? true;
@ -325,6 +343,10 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
_descriptionController.dispose(); _descriptionController.dispose();
_scoreLabelController.dispose(); _scoreLabelController.dispose();
_scoreUnitController.dispose(); _scoreUnitController.dispose();
_defaultTimeController.dispose();
_defaultRepsController.dispose();
_defaultScoreController.dispose();
_defaultScoreTimeController.dispose();
super.dispose(); super.dispose();
} }
@ -389,12 +411,44 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
value: _hasTime, value: _hasTime,
onChanged: (value) => _setMeasure(() => _hasTime = value), onChanged: (value) => _setMeasure(() => _hasTime = value),
), ),
if (_hasTime) ...[
const SizedBox(height: 8),
TextFormField(
controller: _defaultTimeController,
decoration: const InputDecoration(
labelText: 'Temps par défaut (s)',
),
keyboardType: TextInputType.number,
validator: (value) => _hasTime
? _positiveIntValidator(
value,
'Saisis un temps supérieur à 0.',
)
: null,
),
],
_MeasureSwitch( _MeasureSwitch(
title: 'Répétitions', title: 'Répétitions',
subtitle: 'Compter le nombre de mouvements réalisés.', subtitle: 'Compter le nombre de mouvements réalisés.',
value: _hasReps, value: _hasReps,
onChanged: (value) => _setMeasure(() => _hasReps = value), onChanged: (value) => _setMeasure(() => _hasReps = value),
), ),
if (_hasReps) ...[
const SizedBox(height: 8),
TextFormField(
controller: _defaultRepsController,
decoration: const InputDecoration(
labelText: 'Répétitions par défaut',
),
keyboardType: TextInputType.number,
validator: (value) => _hasReps
? _positiveIntValidator(
value,
'Saisis un nombre de répétitions supérieur à 0.',
)
: null,
),
],
_MeasureSwitch( _MeasureSwitch(
title: 'Score', title: 'Score',
subtitle: 'Saisir une valeur libre avec son unité.', subtitle: 'Saisir une valeur libre avec son unité.',
@ -467,6 +521,49 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
: null; : null;
}, },
), ),
const SizedBox(height: 12),
TextFormField(
controller: _defaultScoreController,
decoration: const InputDecoration(
labelText: 'Score par défaut',
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
validator: (value) {
if (!_hasScore ||
_scoreInputMode != ScoreInputMode.manual) {
return null;
}
return _positiveDoubleValidator(
value,
'Saisis un score supérieur à 0.',
);
},
),
] else ...[
const SizedBox(height: 12),
TextFormField(
controller: _defaultScoreTimeController,
decoration: const InputDecoration(
labelText: 'Objectif de chrono par défaut (optionnel)',
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
validator: (value) {
if (!_hasScore ||
_scoreInputMode != ScoreInputMode.stopwatch ||
value == null ||
value.trim().isEmpty) {
return null;
}
return _positiveDoubleValidator(
value,
'Saisis un objectif supérieur à 0.',
);
},
),
], ],
], ],
if (_warning != null) ...[ if (_warning != null) ...[
@ -586,6 +683,20 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
final scoreUnit = _hasScore && scoreInputMode == ScoreInputMode.manual final scoreUnit = _hasScore && scoreInputMode == ScoreInputMode.manual
? _scoreUnitController.text.trim() ? _scoreUnitController.text.trim()
: null; : null;
final defaultTargetTimeSeconds = _hasTime
? int.parse(_defaultTimeController.text.trim())
: null;
final defaultTargetReps = _hasReps
? int.parse(_defaultRepsController.text.trim())
: null;
final defaultTargetScore =
_hasScore && scoreInputMode == ScoreInputMode.manual
? double.parse(_defaultScoreController.text.trim())
: null;
final defaultTargetScoreTimeMs =
_hasScore && scoreInputMode == ScoreInputMode.stopwatch
? _optionalSecondsToMilliseconds(_defaultScoreTimeController.text)
: null;
try { try {
if (exercise == null) { if (exercise == null) {
await widget.exerciseUseCases.create( await widget.exerciseUseCases.create(
@ -599,6 +710,10 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
scoreInputMode: scoreInputMode, scoreInputMode: scoreInputMode,
scoreLabel: scoreLabel, scoreLabel: scoreLabel,
scoreUnit: scoreUnit, scoreUnit: scoreUnit,
defaultTargetTimeSeconds: defaultTargetTimeSeconds,
defaultTargetReps: defaultTargetReps,
defaultTargetScore: defaultTargetScore,
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
); );
} else { } else {
await widget.exerciseUseCases.update( await widget.exerciseUseCases.update(
@ -613,6 +728,10 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
scoreInputMode: scoreInputMode, scoreInputMode: scoreInputMode,
scoreLabel: scoreLabel, scoreLabel: scoreLabel,
scoreUnit: scoreUnit, scoreUnit: scoreUnit,
defaultTargetTimeSeconds: defaultTargetTimeSeconds,
defaultTargetReps: defaultTargetReps,
defaultTargetScore: defaultTargetScore,
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
); );
} }
if (mounted) { if (mounted) {
@ -642,6 +761,48 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
return text.isEmpty ? null : text; return text.isEmpty ? null : text;
} }
String? _positiveIntValidator(String? value, String message) {
final number = int.tryParse(value?.trim() ?? '');
if (number == null || number <= 0) {
return message;
}
return null;
}
String? _positiveDoubleValidator(String? value, String message) {
final number = double.tryParse(value?.trim() ?? '');
if (number == null || number <= 0) {
return message;
}
return null;
}
String _optionalIntText(int? value) {
return value == null ? '' : '$value';
}
String _optionalDoubleText(double? value) {
if (value == null) {
return '';
}
if (value == value.roundToDouble()) {
return '${value.round()}';
}
return '$value';
}
double? _millisecondsToSeconds(int? milliseconds) {
return milliseconds == null ? null : milliseconds / 1000;
}
int? _optionalSecondsToMilliseconds(String value) {
final text = value.trim();
if (text.isEmpty) {
return null;
}
return (double.parse(text) * 1000).round();
}
String _fileNameFromPath(String path) { String _fileNameFromPath(String path) {
final parts = path.split(RegExp(r'[/\\]')); final parts = path.split(RegExp(r'[/\\]'));
return parts.isEmpty ? path : parts.last; return parts.isEmpty ? path : parts.last;

View File

@ -223,7 +223,7 @@ final class _ProgramFormScreenState extends State<ProgramFormScreen> {
buildDefaultDragHandles: false, buildDefaultDragHandles: false,
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
itemCount: _exercises.length, itemCount: _exercises.length,
onReorder: _reorderExercise, onReorderItem: _reorderExercise,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final exercise = _exercises[index]; final exercise = _exercises[index];
return _ProgramExerciseCard( return _ProgramExerciseCard(
@ -517,7 +517,6 @@ final class _ProgramExerciseCustomizationScreen extends StatefulWidget {
const _ProgramExerciseCustomizationScreen({ const _ProgramExerciseCustomizationScreen({
required _ProgramExerciseDraft draft, required _ProgramExerciseDraft draft,
required this.defaultRestSeconds, required this.defaultRestSeconds,
super.key,
}) : _initialDraft = draft; }) : _initialDraft = draft;
final _ProgramExerciseDraft _initialDraft; final _ProgramExerciseDraft _initialDraft;
@ -853,6 +852,14 @@ final class _ProgramExerciseDraft {
scoreUnitSnapshot: exercise.scoreUnit, scoreUnitSnapshot: exercise.scoreUnit,
setsCount: 3, setsCount: 3,
enabledMeasures: exercise.availableMeasures, enabledMeasures: exercise.availableMeasures,
targetTimeSeconds: exercise.defaultTargetTimeSeconds,
targetReps: exercise.defaultTargetReps,
targetScore: exercise.scoreInputMode == ScoreInputMode.manual
? exercise.defaultTargetScore
: null,
targetScoreTimeMs: exercise.scoreInputMode == ScoreInputMode.stopwatch
? exercise.defaultTargetScoreTimeMs
: null,
restSecondsOverride: restSecondsOverride, restSecondsOverride: restSecondsOverride,
); );
} }

View File

@ -38,6 +38,7 @@ void main() {
hasTimeMeasure: false, hasTimeMeasure: false,
hasRepsMeasure: true, hasRepsMeasure: true,
hasScoreMeasure: false, hasScoreMeasure: false,
defaultTargetReps: 10,
); );
final mediaRepository = _FakeMediaAssetRepository() final mediaRepository = _FakeMediaAssetRepository()
..asset = MediaAsset( ..asset = MediaAsset(

View File

@ -18,6 +18,144 @@ void main() {
); );
}); });
test(
'Exercise use case rejects active measures without default targets',
() async {
final useCase = _exerciseUseCase(_FakeExerciseRepository());
await expectLater(
useCase.create(
name: 'Squat',
hasTimeMeasure: true,
hasRepsMeasure: false,
hasScoreMeasure: false,
),
throwsA(isA<DomainException>()),
);
await expectLater(
useCase.create(
name: 'Pompes',
hasTimeMeasure: false,
hasRepsMeasure: true,
hasScoreMeasure: false,
),
throwsA(isA<DomainException>()),
);
await expectLater(
useCase.create(
name: 'Charge',
hasTimeMeasure: false,
hasRepsMeasure: false,
hasScoreMeasure: true,
scoreLabel: 'Charge',
scoreUnit: 'kg',
),
throwsA(isA<DomainException>()),
);
},
);
test('Exercise use case rejects non-positive default targets', () async {
final useCase = _exerciseUseCase(_FakeExerciseRepository());
await expectLater(
useCase.create(
name: 'Squat',
hasTimeMeasure: true,
hasRepsMeasure: false,
hasScoreMeasure: false,
defaultTargetTimeSeconds: 0,
),
throwsA(isA<DomainException>()),
);
await expectLater(
useCase.create(
name: 'Pompes',
hasTimeMeasure: false,
hasRepsMeasure: true,
hasScoreMeasure: false,
defaultTargetReps: -1,
),
throwsA(isA<DomainException>()),
);
await expectLater(
useCase.create(
name: 'Charge',
hasTimeMeasure: false,
hasRepsMeasure: false,
hasScoreMeasure: true,
scoreLabel: 'Charge',
scoreUnit: 'kg',
defaultTargetScore: 0,
),
throwsA(isA<DomainException>()),
);
});
test(
'Exercise use case accepts stopwatch score without default target',
() async {
final repository = _FakeExerciseRepository();
final useCase = _exerciseUseCase(repository);
final exercise = await useCase.create(
name: 'Sprint',
hasTimeMeasure: false,
hasRepsMeasure: false,
hasScoreMeasure: true,
scoreInputMode: ScoreInputMode.stopwatch,
scoreLabel: 'Temps réalisé',
);
expect(exercise.defaultTargetScoreTimeMs, isNull);
expect(repository.saved.single.metadata.id, exercise.metadata.id);
},
);
test(
'Exercise use case rejects non-positive stopwatch score default target',
() async {
final useCase = _exerciseUseCase(_FakeExerciseRepository());
await expectLater(
useCase.create(
name: 'Sprint',
hasTimeMeasure: false,
hasRepsMeasure: false,
hasScoreMeasure: true,
scoreInputMode: ScoreInputMode.stopwatch,
scoreLabel: 'Temps réalisé',
defaultTargetScoreTimeMs: 0,
),
throwsA(isA<DomainException>()),
);
},
);
test('Exercise update validates default targets too', () async {
final repository = _FakeExerciseRepository()
..exercise = Exercise(
metadata: _metadata('exercise-1'),
name: 'Squat',
hasTimeMeasure: false,
hasRepsMeasure: true,
hasScoreMeasure: false,
defaultTargetReps: 10,
);
final useCase = _exerciseUseCase(repository);
await expectLater(
useCase.update(
id: 'exercise-1',
name: 'Squat',
hasTimeMeasure: true,
hasRepsMeasure: false,
hasScoreMeasure: false,
),
throwsA(isA<DomainException>()),
);
});
test('Workout template use case rejects active measure overrides', () async { test('Workout template use case rejects active measure overrides', () async {
final useCase = WorkoutTemplateUseCases( final useCase = WorkoutTemplateUseCases(
templateRepository: _FakeWorkoutTemplateRepository(), templateRepository: _FakeWorkoutTemplateRepository(),
@ -474,6 +612,38 @@ final class _FakeIds implements IdGenerator {
} }
} }
ExerciseUseCases _exerciseUseCase(_FakeExerciseRepository repository) {
return ExerciseUseCases(
repository: repository,
clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
ids: _FakeIds(),
originDeviceId: 'device-1',
);
}
final class _FakeExerciseRepository implements ExerciseRepository {
Exercise? exercise;
final saved = <Exercise>[];
@override
Future<Exercise?> findById(String id) async {
return exercise?.metadata.id == id ? exercise : null;
}
@override
Future<bool> isReferencedByProgram(String id) async => false;
@override
Future<List<Exercise>> listActive() async =>
exercise == null ? const [] : [exercise!];
@override
Future<void> save(Exercise exercise) async {
this.exercise = exercise;
saved.add(exercise);
}
}
final class _FakeWorkoutTemplateRepository final class _FakeWorkoutTemplateRepository
implements WorkoutTemplateRepository { implements WorkoutTemplateRepository {
@override @override

View File

@ -36,6 +36,7 @@ void main() {
hasTimeMeasure: false, hasTimeMeasure: false,
hasRepsMeasure: true, hasRepsMeasure: true,
hasScoreMeasure: false, hasScoreMeasure: false,
defaultTargetReps: 10,
); );
await exerciseRepository.save(exercise); await exerciseRepository.save(exercise);

View File

@ -51,6 +51,9 @@ void main() {
hasScoreMeasure: true, hasScoreMeasure: true,
scoreLabel: 'Charge', scoreLabel: 'Charge',
scoreUnit: 'kg', scoreUnit: 'kg',
defaultTargetTimeSeconds: 45,
defaultTargetReps: 10,
defaultTargetScore: 80,
); );
await tester.pumpWidget( await tester.pumpWidget(
@ -71,6 +74,9 @@ void main() {
) async { ) async {
final exerciseRepository = _FakeExerciseRepository(); final exerciseRepository = _FakeExerciseRepository();
await tester.binding.setSurfaceSize(const Size(400, 2400));
addTearDown(() => tester.binding.setSurfaceSize(null));
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: ExerciseFormScreen( home: ExerciseFormScreen(
@ -81,43 +87,28 @@ void main() {
); );
await tester.enterText(find.widgetWithText(TextFormField, 'Nom'), 'Run'); await tester.enterText(find.widgetWithText(TextFormField, 'Nom'), 'Run');
await tester.dragUntilVisible( await tester.enterText(
find.text('Score'), find.widgetWithText(TextFormField, 'Temps par défaut (s)'),
find.byType(ListView), '30',
const Offset(0, -200),
); );
await tester.tap(find.widgetWithText(SwitchListTile, 'Score')); await tester.tap(find.widgetWithText(SwitchListTile, 'Score'));
await tester.pump(); await tester.pump();
await tester.dragUntilVisible(
find.text('Unité'),
find.byType(ListView),
const Offset(0, -200),
);
expect(find.widgetWithText(TextFormField, 'Unité'), findsOneWidget); expect(find.widgetWithText(TextFormField, 'Unité'), findsOneWidget);
await tester.dragUntilVisible(
find.text('Chrono intégré'),
find.byType(ListView),
const Offset(0, 200),
);
await tester.tap(find.text('Chrono intégré')); await tester.tap(find.text('Chrono intégré'));
await tester.pump(); await tester.pump();
await tester.dragUntilVisible(
find.text('Temps réalisé'),
find.byType(ListView),
const Offset(0, -200),
);
expect(find.text('Temps réalisé'), findsOneWidget); expect(find.text('Temps réalisé'), findsOneWidget);
expect(find.widgetWithText(TextFormField, 'Unité'), findsNothing); expect(find.widgetWithText(TextFormField, 'Unité'), findsNothing);
await tester.ensureVisible(find.text('Enregistrer'));
await tester.tap(find.text('Enregistrer')); await tester.tap(find.text('Enregistrer'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
final saved = exerciseRepository.saved.single; final saved = exerciseRepository.saved.single;
expect(saved.scoreInputMode, ScoreInputMode.stopwatch); expect(saved.scoreInputMode, ScoreInputMode.stopwatch);
expect(saved.defaultTargetTimeSeconds, 30);
expect(saved.defaultTargetScoreTimeMs, isNull);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(

View File

@ -17,6 +17,8 @@ void main() {
hasTimeMeasure: true, hasTimeMeasure: true,
hasRepsMeasure: true, hasRepsMeasure: true,
hasScoreMeasure: false, hasScoreMeasure: false,
defaultTargetTimeSeconds: 45,
defaultTargetReps: 10,
), ),
); );
@ -55,6 +57,8 @@ void main() {
expect(savedExercise.timeEnabled, isTrue); expect(savedExercise.timeEnabled, isTrue);
expect(savedExercise.repsEnabled, isTrue); expect(savedExercise.repsEnabled, isTrue);
expect(savedExercise.scoreEnabled, isFalse); expect(savedExercise.scoreEnabled, isFalse);
expect(savedExercise.targetTimeSeconds, 45);
expect(savedExercise.targetReps, 10);
expect(savedExercise.restSecondsOverride, 60); expect(savedExercise.restSecondsOverride, 60);
}, },
); );
@ -70,6 +74,7 @@ void main() {
hasTimeMeasure: false, hasTimeMeasure: false,
hasRepsMeasure: true, hasRepsMeasure: true,
hasScoreMeasure: false, hasScoreMeasure: false,
defaultTargetReps: 12,
), ),
); );
@ -114,6 +119,7 @@ void main() {
hasTimeMeasure: true, hasTimeMeasure: true,
hasRepsMeasure: false, hasRepsMeasure: false,
hasScoreMeasure: false, hasScoreMeasure: false,
defaultTargetTimeSeconds: 45,
), ),
); );
@ -171,6 +177,9 @@ void main() {
hasScoreMeasure: true, hasScoreMeasure: true,
scoreInputMode: ScoreInputMode.stopwatch, scoreInputMode: ScoreInputMode.stopwatch,
scoreLabel: 'Temps réalisé', scoreLabel: 'Temps réalisé',
defaultTargetTimeSeconds: 30,
defaultTargetReps: 5,
defaultTargetScoreTimeMs: 12000,
), ),
); );