feat(exercice): valeurs par défaut d'exercice (tickets #39/#40, parent #30)
Étend le modèle Drift (tables.dart, app_database.dart/.g.dart), les entités/use cases/repositories et les écrans bibliothèque d'exercices et programme pour supporter des valeurs par défaut par exercice (répétitions/temps/poids proposés à la création d'une série). flutter 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:
@ -25,12 +25,19 @@ final class AppBootstrap implements AppDependencies {
|
||||
});
|
||||
|
||||
final AppDatabase database;
|
||||
@override
|
||||
final ExerciseUseCases exerciseUseCases;
|
||||
@override
|
||||
final MediaUseCases mediaUseCases;
|
||||
@override
|
||||
final ProgramUseCases programUseCases;
|
||||
@override
|
||||
final WorkoutTemplateUseCases workoutTemplateUseCases;
|
||||
@override
|
||||
final ActiveWorkoutSessionUseCases activeWorkoutSessionUseCases;
|
||||
@override
|
||||
final CloseWorkoutSessionUseCase closeWorkoutSessionUseCase;
|
||||
@override
|
||||
final WorkoutHistoryUseCases workoutHistoryUseCases;
|
||||
final SyncGateway syncGateway;
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@ import 'dart:convert';
|
||||
import '../domain/domain.dart';
|
||||
import 'ports.dart';
|
||||
|
||||
const Object _useCaseUnchanged = Object();
|
||||
|
||||
final class ExerciseUseCases {
|
||||
const ExerciseUseCases({
|
||||
required this.repository,
|
||||
@ -27,7 +29,21 @@ final class ExerciseUseCases {
|
||||
ScoreInputMode scoreInputMode = ScoreInputMode.manual,
|
||||
String? scoreLabel,
|
||||
String? scoreUnit,
|
||||
int? defaultTargetTimeSeconds,
|
||||
int? defaultTargetReps,
|
||||
double? defaultTargetScore,
|
||||
int? defaultTargetScoreTimeMs,
|
||||
}) async {
|
||||
_validateExerciseDefaultTargets(
|
||||
hasTimeMeasure: hasTimeMeasure,
|
||||
hasRepsMeasure: hasRepsMeasure,
|
||||
hasScoreMeasure: hasScoreMeasure,
|
||||
scoreInputMode: scoreInputMode,
|
||||
defaultTargetTimeSeconds: defaultTargetTimeSeconds,
|
||||
defaultTargetReps: defaultTargetReps,
|
||||
defaultTargetScore: defaultTargetScore,
|
||||
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
|
||||
);
|
||||
final now = clock.now();
|
||||
final exercise = Exercise(
|
||||
metadata: _newMetadata(ids, originDeviceId, now),
|
||||
@ -41,6 +57,10 @@ final class ExerciseUseCases {
|
||||
scoreInputMode: scoreInputMode,
|
||||
scoreLabel: scoreLabel,
|
||||
scoreUnit: scoreUnit,
|
||||
defaultTargetTimeSeconds: defaultTargetTimeSeconds,
|
||||
defaultTargetReps: defaultTargetReps,
|
||||
defaultTargetScore: defaultTargetScore,
|
||||
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
|
||||
);
|
||||
await repository.save(exercise);
|
||||
return exercise;
|
||||
@ -66,11 +86,39 @@ final class ExerciseUseCases {
|
||||
ScoreInputMode scoreInputMode = ScoreInputMode.manual,
|
||||
String? scoreLabel,
|
||||
String? scoreUnit,
|
||||
Object? defaultTargetTimeSeconds = _useCaseUnchanged,
|
||||
Object? defaultTargetReps = _useCaseUnchanged,
|
||||
Object? defaultTargetScore = _useCaseUnchanged,
|
||||
Object? defaultTargetScoreTimeMs = _useCaseUnchanged,
|
||||
}) async {
|
||||
final exercise = await repository.findById(id);
|
||||
if (exercise == null) {
|
||||
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(
|
||||
metadata: exercise.metadata.touch(clock.now()),
|
||||
name: name,
|
||||
@ -83,6 +131,10 @@ final class ExerciseUseCases {
|
||||
scoreInputMode: scoreInputMode,
|
||||
scoreLabel: scoreLabel,
|
||||
scoreUnit: scoreUnit,
|
||||
defaultTargetTimeSeconds: resolvedDefaultTargetTimeSeconds,
|
||||
defaultTargetReps: resolvedDefaultTargetReps,
|
||||
defaultTargetScore: resolvedDefaultTargetScore,
|
||||
defaultTargetScoreTimeMs: resolvedDefaultTargetScoreTimeMs,
|
||||
);
|
||||
await repository.save(updated);
|
||||
return updated;
|
||||
@ -1271,6 +1323,49 @@ final class SetResultPositionState {
|
||||
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(
|
||||
String programSnapshotJson,
|
||||
WorkoutTemplateExerciseOverrideConfig input,
|
||||
|
||||
@ -132,6 +132,10 @@ final class Exercise {
|
||||
this.scoreInputMode = ScoreInputMode.manual,
|
||||
this.scoreLabel,
|
||||
this.scoreUnit,
|
||||
this.defaultTargetTimeSeconds,
|
||||
this.defaultTargetReps,
|
||||
this.defaultTargetScore,
|
||||
this.defaultTargetScoreTimeMs,
|
||||
this.archivedAt,
|
||||
}) : name = _nonBlank(name, 'Exercise name') {
|
||||
_requireAtLeastOneMeasure(
|
||||
@ -148,6 +152,16 @@ final class Exercise {
|
||||
_nonBlank(scoreLabel, 'Score label');
|
||||
_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;
|
||||
@ -161,6 +175,10 @@ final class Exercise {
|
||||
final ScoreInputMode scoreInputMode;
|
||||
final String? scoreLabel;
|
||||
final String? scoreUnit;
|
||||
final int? defaultTargetTimeSeconds;
|
||||
final int? defaultTargetReps;
|
||||
final double? defaultTargetScore;
|
||||
final int? defaultTargetScoreTimeMs;
|
||||
final DateTime? archivedAt;
|
||||
|
||||
Set<WorkoutMeasure> get availableMeasures => {
|
||||
@ -185,6 +203,10 @@ final class Exercise {
|
||||
ScoreInputMode? scoreInputMode,
|
||||
Object? scoreLabel = _unchanged,
|
||||
Object? scoreUnit = _unchanged,
|
||||
Object? defaultTargetTimeSeconds = _unchanged,
|
||||
Object? defaultTargetReps = _unchanged,
|
||||
Object? defaultTargetScore = _unchanged,
|
||||
Object? defaultTargetScoreTimeMs = _unchanged,
|
||||
Object? archivedAt = _unchanged,
|
||||
}) {
|
||||
return Exercise(
|
||||
@ -209,6 +231,18 @@ final class Exercise {
|
||||
scoreUnit: scoreUnit == _unchanged
|
||||
? this.scoreUnit
|
||||
: 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
|
||||
? this.archivedAt
|
||||
: archivedAt as DateTime?,
|
||||
@ -371,10 +405,10 @@ final class ProgramExercise {
|
||||
timeEnabled: enabledMeasures.contains(WorkoutMeasure.time),
|
||||
repsEnabled: enabledMeasures.contains(WorkoutMeasure.reps),
|
||||
scoreEnabled: enabledMeasures.contains(WorkoutMeasure.score),
|
||||
targetTimeSeconds: targetTimeSeconds,
|
||||
targetReps: targetReps,
|
||||
targetScore: targetScore,
|
||||
targetScoreTimeMs: targetScoreTimeMs,
|
||||
targetTimeSeconds: targetTimeSeconds ?? exercise.defaultTargetTimeSeconds,
|
||||
targetReps: targetReps ?? exercise.defaultTargetReps,
|
||||
targetScore: targetScore ?? exercise.defaultTargetScore,
|
||||
targetScoreTimeMs: targetScoreTimeMs ?? exercise.defaultTargetScoreTimeMs,
|
||||
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({
|
||||
required bool scoreEnabled,
|
||||
required ScoreInputMode scoreInputMode,
|
||||
|
||||
@ -36,7 +36,7 @@ final class AppDatabase extends _$AppDatabase {
|
||||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 3;
|
||||
int get schemaVersion => 4;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration {
|
||||
@ -60,6 +60,9 @@ final class AppDatabase extends _$AppDatabase {
|
||||
if (from < 3) {
|
||||
await _migrateToSchema3(migrator);
|
||||
}
|
||||
if (from < 4) {
|
||||
await _migrateToSchema4();
|
||||
}
|
||||
await _createIndexes();
|
||||
},
|
||||
beforeOpen: (details) async {
|
||||
@ -202,4 +205,25 @@ extension on AppDatabase {
|
||||
);
|
||||
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)',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8063,6 +8063,50 @@ class $ExercisesTable extends Exercises
|
||||
type: DriftSqlType.string,
|
||||
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(
|
||||
'archivedAt',
|
||||
);
|
||||
@ -8097,6 +8141,10 @@ class $ExercisesTable extends Exercises
|
||||
scoreInputMode,
|
||||
scoreLabel,
|
||||
scoreUnit,
|
||||
defaultTargetTimeSeconds,
|
||||
defaultTargetReps,
|
||||
defaultTargetScore,
|
||||
defaultTargetScoreTimeMs,
|
||||
archivedAt,
|
||||
];
|
||||
@override
|
||||
@ -8293,6 +8341,42 @@ class $ExercisesTable extends Exercises
|
||||
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')) {
|
||||
context.handle(
|
||||
_archivedAtMeta,
|
||||
@ -8392,6 +8476,22 @@ class $ExercisesTable extends Exercises
|
||||
DriftSqlType.string,
|
||||
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(
|
||||
DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}archived_at'],
|
||||
@ -8427,6 +8527,10 @@ class Exercise extends DataClass implements Insertable<Exercise> {
|
||||
final String scoreInputMode;
|
||||
final String? scoreLabel;
|
||||
final String? scoreUnit;
|
||||
final int? defaultTargetTimeSeconds;
|
||||
final int? defaultTargetReps;
|
||||
final double? defaultTargetScore;
|
||||
final int? defaultTargetScoreTimeMs;
|
||||
final DateTime? archivedAt;
|
||||
const Exercise({
|
||||
required this.id,
|
||||
@ -8450,6 +8554,10 @@ class Exercise extends DataClass implements Insertable<Exercise> {
|
||||
required this.scoreInputMode,
|
||||
this.scoreLabel,
|
||||
this.scoreUnit,
|
||||
this.defaultTargetTimeSeconds,
|
||||
this.defaultTargetReps,
|
||||
this.defaultTargetScore,
|
||||
this.defaultTargetScoreTimeMs,
|
||||
this.archivedAt,
|
||||
});
|
||||
@override
|
||||
@ -8494,6 +8602,22 @@ class Exercise extends DataClass implements Insertable<Exercise> {
|
||||
if (!nullToAbsent || scoreUnit != null) {
|
||||
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) {
|
||||
map['archived_at'] = Variable<DateTime>(archivedAt);
|
||||
}
|
||||
@ -8541,6 +8665,18 @@ class Exercise extends DataClass implements Insertable<Exercise> {
|
||||
scoreUnit: scoreUnit == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: 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
|
||||
? const Value.absent()
|
||||
: Value(archivedAt),
|
||||
@ -8576,6 +8712,16 @@ class Exercise extends DataClass implements Insertable<Exercise> {
|
||||
scoreInputMode: serializer.fromJson<String>(json['scoreInputMode']),
|
||||
scoreLabel: serializer.fromJson<String?>(json['scoreLabel']),
|
||||
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']),
|
||||
);
|
||||
}
|
||||
@ -8604,6 +8750,14 @@ class Exercise extends DataClass implements Insertable<Exercise> {
|
||||
'scoreInputMode': serializer.toJson<String>(scoreInputMode),
|
||||
'scoreLabel': serializer.toJson<String?>(scoreLabel),
|
||||
'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),
|
||||
};
|
||||
}
|
||||
@ -8630,6 +8784,10 @@ class Exercise extends DataClass implements Insertable<Exercise> {
|
||||
String? scoreInputMode,
|
||||
Value<String?> scoreLabel = 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(),
|
||||
}) => Exercise(
|
||||
id: id ?? this.id,
|
||||
@ -8657,6 +8815,18 @@ class Exercise extends DataClass implements Insertable<Exercise> {
|
||||
scoreInputMode: scoreInputMode ?? this.scoreInputMode,
|
||||
scoreLabel: scoreLabel.present ? scoreLabel.value : this.scoreLabel,
|
||||
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,
|
||||
);
|
||||
Exercise copyWithCompanion(ExercisesCompanion data) {
|
||||
@ -8710,6 +8880,18 @@ class Exercise extends DataClass implements Insertable<Exercise> {
|
||||
? data.scoreLabel.value
|
||||
: this.scoreLabel,
|
||||
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
|
||||
? data.archivedAt.value
|
||||
: this.archivedAt,
|
||||
@ -8740,6 +8922,10 @@ class Exercise extends DataClass implements Insertable<Exercise> {
|
||||
..write('scoreInputMode: $scoreInputMode, ')
|
||||
..write('scoreLabel: $scoreLabel, ')
|
||||
..write('scoreUnit: $scoreUnit, ')
|
||||
..write('defaultTargetTimeSeconds: $defaultTargetTimeSeconds, ')
|
||||
..write('defaultTargetReps: $defaultTargetReps, ')
|
||||
..write('defaultTargetScore: $defaultTargetScore, ')
|
||||
..write('defaultTargetScoreTimeMs: $defaultTargetScoreTimeMs, ')
|
||||
..write('archivedAt: $archivedAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
@ -8768,6 +8954,10 @@ class Exercise extends DataClass implements Insertable<Exercise> {
|
||||
scoreInputMode,
|
||||
scoreLabel,
|
||||
scoreUnit,
|
||||
defaultTargetTimeSeconds,
|
||||
defaultTargetReps,
|
||||
defaultTargetScore,
|
||||
defaultTargetScoreTimeMs,
|
||||
archivedAt,
|
||||
]);
|
||||
@override
|
||||
@ -8795,6 +8985,10 @@ class Exercise extends DataClass implements Insertable<Exercise> {
|
||||
other.scoreInputMode == this.scoreInputMode &&
|
||||
other.scoreLabel == this.scoreLabel &&
|
||||
other.scoreUnit == this.scoreUnit &&
|
||||
other.defaultTargetTimeSeconds == this.defaultTargetTimeSeconds &&
|
||||
other.defaultTargetReps == this.defaultTargetReps &&
|
||||
other.defaultTargetScore == this.defaultTargetScore &&
|
||||
other.defaultTargetScoreTimeMs == this.defaultTargetScoreTimeMs &&
|
||||
other.archivedAt == this.archivedAt);
|
||||
}
|
||||
|
||||
@ -8820,6 +9014,10 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
|
||||
final Value<String> scoreInputMode;
|
||||
final Value<String?> scoreLabel;
|
||||
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<int> rowid;
|
||||
const ExercisesCompanion({
|
||||
@ -8844,6 +9042,10 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
|
||||
this.scoreInputMode = const Value.absent(),
|
||||
this.scoreLabel = 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.rowid = const Value.absent(),
|
||||
});
|
||||
@ -8869,6 +9071,10 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
|
||||
this.scoreInputMode = const Value.absent(),
|
||||
this.scoreLabel = 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.rowid = const Value.absent(),
|
||||
}) : id = Value(id),
|
||||
@ -8903,6 +9109,10 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
|
||||
Expression<String>? scoreInputMode,
|
||||
Expression<String>? scoreLabel,
|
||||
Expression<String>? scoreUnit,
|
||||
Expression<int>? defaultTargetTimeSeconds,
|
||||
Expression<int>? defaultTargetReps,
|
||||
Expression<double>? defaultTargetScore,
|
||||
Expression<int>? defaultTargetScoreTimeMs,
|
||||
Expression<DateTime>? archivedAt,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
@ -8929,6 +9139,13 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
|
||||
if (scoreInputMode != null) 'score_input_mode': scoreInputMode,
|
||||
if (scoreLabel != null) 'score_label': scoreLabel,
|
||||
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 (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
@ -8956,6 +9173,10 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
|
||||
Value<String>? scoreInputMode,
|
||||
Value<String?>? scoreLabel,
|
||||
Value<String?>? scoreUnit,
|
||||
Value<int?>? defaultTargetTimeSeconds,
|
||||
Value<int?>? defaultTargetReps,
|
||||
Value<double?>? defaultTargetScore,
|
||||
Value<int?>? defaultTargetScoreTimeMs,
|
||||
Value<DateTime?>? archivedAt,
|
||||
Value<int>? rowid,
|
||||
}) {
|
||||
@ -8981,6 +9202,12 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
|
||||
scoreInputMode: scoreInputMode ?? this.scoreInputMode,
|
||||
scoreLabel: scoreLabel ?? this.scoreLabel,
|
||||
scoreUnit: scoreUnit ?? this.scoreUnit,
|
||||
defaultTargetTimeSeconds:
|
||||
defaultTargetTimeSeconds ?? this.defaultTargetTimeSeconds,
|
||||
defaultTargetReps: defaultTargetReps ?? this.defaultTargetReps,
|
||||
defaultTargetScore: defaultTargetScore ?? this.defaultTargetScore,
|
||||
defaultTargetScoreTimeMs:
|
||||
defaultTargetScoreTimeMs ?? this.defaultTargetScoreTimeMs,
|
||||
archivedAt: archivedAt ?? this.archivedAt,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
@ -9054,6 +9281,22 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
|
||||
if (scoreUnit.present) {
|
||||
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) {
|
||||
map['archived_at'] = Variable<DateTime>(archivedAt.value);
|
||||
}
|
||||
@ -9087,6 +9330,10 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
|
||||
..write('scoreInputMode: $scoreInputMode, ')
|
||||
..write('scoreLabel: $scoreLabel, ')
|
||||
..write('scoreUnit: $scoreUnit, ')
|
||||
..write('defaultTargetTimeSeconds: $defaultTargetTimeSeconds, ')
|
||||
..write('defaultTargetReps: $defaultTargetReps, ')
|
||||
..write('defaultTargetScore: $defaultTargetScore, ')
|
||||
..write('defaultTargetScoreTimeMs: $defaultTargetScoreTimeMs, ')
|
||||
..write('archivedAt: $archivedAt, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
@ -22641,6 +22888,10 @@ typedef $$ExercisesTableCreateCompanionBuilder =
|
||||
Value<String> scoreInputMode,
|
||||
Value<String?> scoreLabel,
|
||||
Value<String?> scoreUnit,
|
||||
Value<int?> defaultTargetTimeSeconds,
|
||||
Value<int?> defaultTargetReps,
|
||||
Value<double?> defaultTargetScore,
|
||||
Value<int?> defaultTargetScoreTimeMs,
|
||||
Value<DateTime?> archivedAt,
|
||||
Value<int> rowid,
|
||||
});
|
||||
@ -22667,6 +22918,10 @@ typedef $$ExercisesTableUpdateCompanionBuilder =
|
||||
Value<String> scoreInputMode,
|
||||
Value<String?> scoreLabel,
|
||||
Value<String?> scoreUnit,
|
||||
Value<int?> defaultTargetTimeSeconds,
|
||||
Value<int?> defaultTargetReps,
|
||||
Value<double?> defaultTargetScore,
|
||||
Value<int?> defaultTargetScoreTimeMs,
|
||||
Value<DateTime?> archivedAt,
|
||||
Value<int> rowid,
|
||||
});
|
||||
@ -22834,6 +23089,26 @@ class $$ExercisesTableFilterComposer
|
||||
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(
|
||||
column: $table.archivedAt,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
@ -23015,6 +23290,26 @@ class $$ExercisesTableOrderingComposer
|
||||
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(
|
||||
column: $table.archivedAt,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
@ -23157,6 +23452,26 @@ class $$ExercisesTableAnnotationComposer
|
||||
GeneratedColumn<String> get scoreUnit =>
|
||||
$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(
|
||||
column: $table.archivedAt,
|
||||
builder: (column) => column,
|
||||
@ -23287,6 +23602,10 @@ class $$ExercisesTableTableManager
|
||||
Value<String> scoreInputMode = const Value.absent(),
|
||||
Value<String?> scoreLabel = 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<int> rowid = const Value.absent(),
|
||||
}) => ExercisesCompanion(
|
||||
@ -23311,6 +23630,10 @@ class $$ExercisesTableTableManager
|
||||
scoreInputMode: scoreInputMode,
|
||||
scoreLabel: scoreLabel,
|
||||
scoreUnit: scoreUnit,
|
||||
defaultTargetTimeSeconds: defaultTargetTimeSeconds,
|
||||
defaultTargetReps: defaultTargetReps,
|
||||
defaultTargetScore: defaultTargetScore,
|
||||
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
|
||||
archivedAt: archivedAt,
|
||||
rowid: rowid,
|
||||
),
|
||||
@ -23337,6 +23660,10 @@ class $$ExercisesTableTableManager
|
||||
Value<String> scoreInputMode = const Value.absent(),
|
||||
Value<String?> scoreLabel = 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<int> rowid = const Value.absent(),
|
||||
}) => ExercisesCompanion.insert(
|
||||
@ -23361,6 +23688,10 @@ class $$ExercisesTableTableManager
|
||||
scoreInputMode: scoreInputMode,
|
||||
scoreLabel: scoreLabel,
|
||||
scoreUnit: scoreUnit,
|
||||
defaultTargetTimeSeconds: defaultTargetTimeSeconds,
|
||||
defaultTargetReps: defaultTargetReps,
|
||||
defaultTargetScore: defaultTargetScore,
|
||||
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
|
||||
archivedAt: archivedAt,
|
||||
rowid: rowid,
|
||||
),
|
||||
|
||||
@ -966,6 +966,10 @@ db.ExercisesCompanion _exerciseCompanion(domain.Exercise exercise) {
|
||||
scoreInputMode: Value(_scoreInputModeToDb(exercise.scoreInputMode)),
|
||||
scoreLabel: Value(exercise.scoreLabel),
|
||||
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)),
|
||||
);
|
||||
}
|
||||
@ -983,6 +987,10 @@ domain.Exercise _exerciseFromRow(db.Exercise row) {
|
||||
scoreInputMode: _scoreInputModeFromDb(row.scoreInputMode),
|
||||
scoreLabel: row.scoreLabel,
|
||||
scoreUnit: row.scoreUnit,
|
||||
defaultTargetTimeSeconds: row.defaultTargetTimeSeconds,
|
||||
defaultTargetReps: row.defaultTargetReps,
|
||||
defaultTargetScore: row.defaultTargetScore,
|
||||
defaultTargetScoreTimeMs: row.defaultTargetScoreTimeMs,
|
||||
archivedAt: _utcOrNull(row.archivedAt),
|
||||
);
|
||||
}
|
||||
|
||||
@ -69,6 +69,10 @@ class Exercises extends SyncableTable {
|
||||
text().withDefault(const Constant('manual'))();
|
||||
TextColumn get scoreLabel => 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()();
|
||||
|
||||
@override
|
||||
@ -79,6 +83,12 @@ class Exercises extends SyncableTable {
|
||||
"CHECK (score_input_mode != 'manual' OR NOT has_score_measure OR "
|
||||
'(score_label IS NOT NULL AND length(trim(score_label)) > 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)',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -287,6 +287,10 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
|
||||
late final TextEditingController _descriptionController;
|
||||
late final TextEditingController _scoreLabelController;
|
||||
late final TextEditingController _scoreUnitController;
|
||||
late final TextEditingController _defaultTimeController;
|
||||
late final TextEditingController _defaultRepsController;
|
||||
late final TextEditingController _defaultScoreController;
|
||||
late final TextEditingController _defaultScoreTimeController;
|
||||
String? _imageMediaId;
|
||||
String? _videoMediaId;
|
||||
String? _selectedImageName;
|
||||
@ -311,6 +315,20 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
|
||||
_descriptionController = TextEditingController(text: exercise?.description);
|
||||
_scoreLabelController = TextEditingController(text: exercise?.scoreLabel);
|
||||
_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;
|
||||
_videoMediaId = exercise?.videoMediaId;
|
||||
_hasTime = exercise?.hasTimeMeasure ?? true;
|
||||
@ -325,6 +343,10 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
|
||||
_descriptionController.dispose();
|
||||
_scoreLabelController.dispose();
|
||||
_scoreUnitController.dispose();
|
||||
_defaultTimeController.dispose();
|
||||
_defaultRepsController.dispose();
|
||||
_defaultScoreController.dispose();
|
||||
_defaultScoreTimeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -389,12 +411,44 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
|
||||
value: _hasTime,
|
||||
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(
|
||||
title: 'Répétitions',
|
||||
subtitle: 'Compter le nombre de mouvements réalisés.',
|
||||
value: _hasReps,
|
||||
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(
|
||||
title: 'Score',
|
||||
subtitle: 'Saisir une valeur libre avec son unité.',
|
||||
@ -467,6 +521,49 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
|
||||
: 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) ...[
|
||||
@ -586,6 +683,20 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
|
||||
final scoreUnit = _hasScore && scoreInputMode == ScoreInputMode.manual
|
||||
? _scoreUnitController.text.trim()
|
||||
: 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 {
|
||||
if (exercise == null) {
|
||||
await widget.exerciseUseCases.create(
|
||||
@ -599,6 +710,10 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
|
||||
scoreInputMode: scoreInputMode,
|
||||
scoreLabel: scoreLabel,
|
||||
scoreUnit: scoreUnit,
|
||||
defaultTargetTimeSeconds: defaultTargetTimeSeconds,
|
||||
defaultTargetReps: defaultTargetReps,
|
||||
defaultTargetScore: defaultTargetScore,
|
||||
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
|
||||
);
|
||||
} else {
|
||||
await widget.exerciseUseCases.update(
|
||||
@ -613,6 +728,10 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
|
||||
scoreInputMode: scoreInputMode,
|
||||
scoreLabel: scoreLabel,
|
||||
scoreUnit: scoreUnit,
|
||||
defaultTargetTimeSeconds: defaultTargetTimeSeconds,
|
||||
defaultTargetReps: defaultTargetReps,
|
||||
defaultTargetScore: defaultTargetScore,
|
||||
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
|
||||
);
|
||||
}
|
||||
if (mounted) {
|
||||
@ -642,6 +761,48 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
|
||||
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) {
|
||||
final parts = path.split(RegExp(r'[/\\]'));
|
||||
return parts.isEmpty ? path : parts.last;
|
||||
|
||||
@ -223,7 +223,7 @@ final class _ProgramFormScreenState extends State<ProgramFormScreen> {
|
||||
buildDefaultDragHandles: false,
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
itemCount: _exercises.length,
|
||||
onReorder: _reorderExercise,
|
||||
onReorderItem: _reorderExercise,
|
||||
itemBuilder: (context, index) {
|
||||
final exercise = _exercises[index];
|
||||
return _ProgramExerciseCard(
|
||||
@ -517,7 +517,6 @@ final class _ProgramExerciseCustomizationScreen extends StatefulWidget {
|
||||
const _ProgramExerciseCustomizationScreen({
|
||||
required _ProgramExerciseDraft draft,
|
||||
required this.defaultRestSeconds,
|
||||
super.key,
|
||||
}) : _initialDraft = draft;
|
||||
|
||||
final _ProgramExerciseDraft _initialDraft;
|
||||
@ -853,6 +852,14 @@ final class _ProgramExerciseDraft {
|
||||
scoreUnitSnapshot: exercise.scoreUnit,
|
||||
setsCount: 3,
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
@ -38,6 +38,7 @@ void main() {
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: false,
|
||||
defaultTargetReps: 10,
|
||||
);
|
||||
final mediaRepository = _FakeMediaAssetRepository()
|
||||
..asset = MediaAsset(
|
||||
|
||||
@ -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 {
|
||||
final useCase = WorkoutTemplateUseCases(
|
||||
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
|
||||
implements WorkoutTemplateRepository {
|
||||
@override
|
||||
|
||||
@ -36,6 +36,7 @@ void main() {
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: false,
|
||||
defaultTargetReps: 10,
|
||||
);
|
||||
|
||||
await exerciseRepository.save(exercise);
|
||||
|
||||
@ -51,6 +51,9 @@ void main() {
|
||||
hasScoreMeasure: true,
|
||||
scoreLabel: 'Charge',
|
||||
scoreUnit: 'kg',
|
||||
defaultTargetTimeSeconds: 45,
|
||||
defaultTargetReps: 10,
|
||||
defaultTargetScore: 80,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -71,6 +74,9 @@ void main() {
|
||||
) async {
|
||||
final exerciseRepository = _FakeExerciseRepository();
|
||||
|
||||
await tester.binding.setSurfaceSize(const Size(400, 2400));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: ExerciseFormScreen(
|
||||
@ -81,43 +87,28 @@ void main() {
|
||||
);
|
||||
|
||||
await tester.enterText(find.widgetWithText(TextFormField, 'Nom'), 'Run');
|
||||
await tester.dragUntilVisible(
|
||||
find.text('Score'),
|
||||
find.byType(ListView),
|
||||
const Offset(0, -200),
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Temps par défaut (s)'),
|
||||
'30',
|
||||
);
|
||||
await tester.tap(find.widgetWithText(SwitchListTile, 'Score'));
|
||||
await tester.pump();
|
||||
|
||||
await tester.dragUntilVisible(
|
||||
find.text('Unité'),
|
||||
find.byType(ListView),
|
||||
const Offset(0, -200),
|
||||
);
|
||||
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.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.widgetWithText(TextFormField, 'Unité'), findsNothing);
|
||||
|
||||
await tester.ensureVisible(find.text('Enregistrer'));
|
||||
await tester.tap(find.text('Enregistrer'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final saved = exerciseRepository.saved.single;
|
||||
expect(saved.scoreInputMode, ScoreInputMode.stopwatch);
|
||||
expect(saved.defaultTargetTimeSeconds, 30);
|
||||
expect(saved.defaultTargetScoreTimeMs, isNull);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
|
||||
@ -17,6 +17,8 @@ void main() {
|
||||
hasTimeMeasure: true,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: false,
|
||||
defaultTargetTimeSeconds: 45,
|
||||
defaultTargetReps: 10,
|
||||
),
|
||||
);
|
||||
|
||||
@ -55,6 +57,8 @@ void main() {
|
||||
expect(savedExercise.timeEnabled, isTrue);
|
||||
expect(savedExercise.repsEnabled, isTrue);
|
||||
expect(savedExercise.scoreEnabled, isFalse);
|
||||
expect(savedExercise.targetTimeSeconds, 45);
|
||||
expect(savedExercise.targetReps, 10);
|
||||
expect(savedExercise.restSecondsOverride, 60);
|
||||
},
|
||||
);
|
||||
@ -70,6 +74,7 @@ void main() {
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: false,
|
||||
defaultTargetReps: 12,
|
||||
),
|
||||
);
|
||||
|
||||
@ -114,6 +119,7 @@ void main() {
|
||||
hasTimeMeasure: true,
|
||||
hasRepsMeasure: false,
|
||||
hasScoreMeasure: false,
|
||||
defaultTargetTimeSeconds: 45,
|
||||
),
|
||||
);
|
||||
|
||||
@ -171,6 +177,9 @@ void main() {
|
||||
hasScoreMeasure: true,
|
||||
scoreInputMode: ScoreInputMode.stopwatch,
|
||||
scoreLabel: 'Temps réalisé',
|
||||
defaultTargetTimeSeconds: 30,
|
||||
defaultTargetReps: 5,
|
||||
defaultTargetScoreTimeMs: 12000,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user