feat(bibliotheque): tags et duplication profonde

Ticket #83 B1-B3 : tags JSON dénormalisés, migration Drift v19, mappings sync, helpers de filtre/suggestions, duplication profonde Program/WorkoutTemplate, tests associés.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 10:01:31 +02:00
parent 10fba8d2ec
commit add5025dd2
9 changed files with 1012 additions and 10 deletions

View File

@ -27,6 +27,13 @@ final class StarterContent {
final WorkoutTemplate workoutTemplate; final WorkoutTemplate workoutTemplate;
} }
final class TagUsage {
const TagUsage({required this.tag, required this.count});
final String tag;
final int count;
}
enum ProgressionPeriod { fourWeeks, threeMonths, all } enum ProgressionPeriod { fourWeeks, threeMonths, all }
enum ProgressionMeasure { manualScore, stopwatchScore, reps, time } enum ProgressionMeasure { manualScore, stopwatchScore, reps, time }

View File

@ -7,6 +7,48 @@ import 'starter_content/starter_content.dart';
const Object _useCaseUnchanged = Object(); const Object _useCaseUnchanged = Object();
List<T> filterByRequiredTags<T>(
List<T> items,
Set<String> requiredTags,
List<String> Function(T item) tagsOf,
) {
final normalizedRequiredTags = normalizeTags(requiredTags).toSet();
if (normalizedRequiredTags.isEmpty) {
return items;
}
return items
.where(
(item) => normalizeTags(
tagsOf(item),
).toSet().containsAll(normalizedRequiredTags),
)
.toList();
}
List<TagUsage> tagSuggestionsFor<T>(
List<T> items,
List<String> Function(T item) tagsOf,
) {
final counts = <String, int>{};
for (final item in items) {
for (final tag in normalizeTags(tagsOf(item))) {
counts[tag] = (counts[tag] ?? 0) + 1;
}
}
final suggestions = [
for (final entry in counts.entries)
TagUsage(tag: entry.key, count: entry.value),
];
suggestions.sort((left, right) {
final countComparison = right.count.compareTo(left.count);
if (countComparison != 0) {
return countComparison;
}
return left.tag.compareTo(right.tag);
});
return suggestions;
}
enum StarterSeedStatus { inserted, skippedAlreadyApplied, skippedNotEmpty } enum StarterSeedStatus { inserted, skippedAlreadyApplied, skippedNotEmpty }
final class StarterSeedResult { final class StarterSeedResult {
@ -846,6 +888,7 @@ final class ExerciseUseCases {
double? defaultTargetScore, double? defaultTargetScore,
int? defaultTargetScoreTimeMs, int? defaultTargetScoreTimeMs,
bool autoStartNextTimedStep = true, bool autoStartNextTimedStep = true,
List<String> tags = const [],
List<ExerciseStep> steps = const [], List<ExerciseStep> steps = const [],
}) async { }) async {
_validateExerciseSteps(steps); _validateExerciseSteps(steps);
@ -878,6 +921,7 @@ final class ExerciseUseCases {
defaultTargetScore: defaultTargetScore, defaultTargetScore: defaultTargetScore,
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs, defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
autoStartNextTimedStep: autoStartNextTimedStep, autoStartNextTimedStep: autoStartNextTimedStep,
tags: tags,
steps: steps, steps: steps,
); );
await repository.save(exercise); await repository.save(exercise);
@ -911,6 +955,7 @@ final class ExerciseUseCases {
Object? defaultTargetScore = _useCaseUnchanged, Object? defaultTargetScore = _useCaseUnchanged,
Object? defaultTargetScoreTimeMs = _useCaseUnchanged, Object? defaultTargetScoreTimeMs = _useCaseUnchanged,
bool? autoStartNextTimedStep, bool? autoStartNextTimedStep,
List<String> tags = const [],
Object? steps = _useCaseUnchanged, Object? steps = _useCaseUnchanged,
}) async { }) async {
final exercise = await repository.findById(id); final exercise = await repository.findById(id);
@ -972,6 +1017,7 @@ final class ExerciseUseCases {
defaultTargetScore: resolvedDefaultTargetScore, defaultTargetScore: resolvedDefaultTargetScore,
defaultTargetScoreTimeMs: resolvedDefaultTargetScoreTimeMs, defaultTargetScoreTimeMs: resolvedDefaultTargetScoreTimeMs,
autoStartNextTimedStep: autoStartNextTimedStep, autoStartNextTimedStep: autoStartNextTimedStep,
tags: tags,
steps: resolvedSteps, steps: resolvedSteps,
); );
await repository.save(updated); await repository.save(updated);
@ -1232,12 +1278,14 @@ final class ProgramUseCases {
Future<Program> create({ Future<Program> create({
required String name, required String name,
required int defaultRestSeconds, required int defaultRestSeconds,
List<String> tags = const [],
}) async { }) async {
final now = clock.now(); final now = clock.now();
final program = Program( final program = Program(
metadata: _newMetadata(ids, originDeviceId, now), metadata: _newMetadata(ids, originDeviceId, now),
name: name, name: name,
defaultRestSeconds: defaultRestSeconds, defaultRestSeconds: defaultRestSeconds,
tags: tags,
); );
await programRepository.save(program); await programRepository.save(program);
return program; return program;
@ -1247,6 +1295,7 @@ final class ProgramUseCases {
String? id, String? id,
required String name, required String name,
required int defaultRestSeconds, required int defaultRestSeconds,
List<String> tags = const [],
required List<ProgramExerciseConfig> exercises, required List<ProgramExerciseConfig> exercises,
}) async { }) async {
final now = clock.now(); final now = clock.now();
@ -1307,12 +1356,71 @@ final class ProgramUseCases {
name: name, name: name,
defaultRestSeconds: defaultRestSeconds, defaultRestSeconds: defaultRestSeconds,
isExample: existing?.isExample ?? false, isExample: existing?.isExample ?? false,
tags: tags,
exercises: programExercises, exercises: programExercises,
); );
await programRepository.replaceExercises(program, now); await programRepository.replaceExercises(program, now);
return program; return program;
} }
Future<Program> duplicate(String id) async {
final source = await programRepository.findById(id);
if (source == null) {
throw const DomainException('Program not found.');
}
final activePrograms = await programRepository.listActive();
final now = clock.now();
final copyId = ids.newId();
final copyExercises = [
for (final exercise in source.exercises)
ProgramExercise(
metadata: _newMetadata(ids, originDeviceId, now),
programId: copyId,
sourceExerciseId: exercise.sourceExerciseId,
position: exercise.position,
exerciseNameSnapshot: exercise.exerciseNameSnapshot,
exerciseDescriptionSnapshot: exercise.exerciseDescriptionSnapshot,
exerciseImageMediaIdSnapshot: exercise.exerciseImageMediaIdSnapshot,
exerciseImageMediaIdsSnapshot: exercise.exerciseImageMediaIdsSnapshot,
exerciseVideoMediaIdSnapshot: exercise.exerciseVideoMediaIdSnapshot,
exerciseStepsSnapshot: exercise.exerciseStepsSnapshot,
autoStartNextTimedStepSnapshot:
exercise.autoStartNextTimedStepSnapshot,
autoStartNextTimedStepOverride:
exercise.autoStartNextTimedStepOverride,
exerciseArchivedSnapshot: exercise.exerciseArchivedSnapshot,
availableTimeSnapshot: exercise.availableTimeSnapshot,
availableRepsSnapshot: exercise.availableRepsSnapshot,
availableScoreSnapshot: exercise.availableScoreSnapshot,
scoreInputModeSnapshot: exercise.scoreInputModeSnapshot,
scoreLabelSnapshot: exercise.scoreLabelSnapshot,
scoreUnitSnapshot: exercise.scoreUnitSnapshot,
setsCount: exercise.setsCount,
timeEnabled: exercise.timeEnabled,
repsEnabled: exercise.repsEnabled,
scoreEnabled: exercise.scoreEnabled,
targetTimeSeconds: exercise.targetTimeSeconds,
targetReps: exercise.targetReps,
targetScore: exercise.targetScore,
targetScoreTimeMs: exercise.targetScoreTimeMs,
restSecondsOverride: exercise.restSecondsOverride,
),
];
final copy = Program(
metadata: _newMetadataWithId(copyId, originDeviceId, now),
name: _copyName(
source.name,
activePrograms.map((program) => program.name),
),
defaultRestSeconds: source.defaultRestSeconds,
isExample: false,
tags: source.tags,
exercises: copyExercises,
);
await programRepository.replaceExercises(copy, now);
return copy;
}
Future<ProgramExercise> addExercise({ Future<ProgramExercise> addExercise({
required String programId, required String programId,
required String exerciseId, required String exerciseId,
@ -1376,6 +1484,8 @@ final class ProgramUseCases {
metadata: template.metadata.touch(now), metadata: template.metadata.touch(now),
name: template.name, name: template.name,
lastStartedAt: template.lastStartedAt, lastStartedAt: template.lastStartedAt,
isExample: template.isExample,
tags: template.tags,
programs: repositionedPrograms, programs: repositionedPrograms,
overrides: template.overrides overrides: template.overrides
.where( .where(
@ -1469,11 +1579,15 @@ final class WorkoutTemplateUseCases {
return templateRepository.listActive(); return templateRepository.listActive();
} }
Future<WorkoutTemplate> create({required String name}) async { Future<WorkoutTemplate> create({
required String name,
List<String> tags = const [],
}) async {
final now = clock.now(); final now = clock.now();
final template = WorkoutTemplate( final template = WorkoutTemplate(
metadata: _newMetadata(ids, originDeviceId, now), metadata: _newMetadata(ids, originDeviceId, now),
name: name, name: name,
tags: tags,
); );
await templateRepository.save(template); await templateRepository.save(template);
return template; return template;
@ -1482,6 +1596,7 @@ final class WorkoutTemplateUseCases {
Future<WorkoutTemplate> saveConfigured({ Future<WorkoutTemplate> saveConfigured({
String? id, String? id,
required String name, required String name,
List<String> tags = const [],
required List<WorkoutTemplateProgramConfig> programs, required List<WorkoutTemplateProgramConfig> programs,
required List<WorkoutTemplateExerciseOverrideConfig> overrides, required List<WorkoutTemplateExerciseOverrideConfig> overrides,
}) async { }) async {
@ -1553,6 +1668,7 @@ final class WorkoutTemplateUseCases {
name: name, name: name,
lastStartedAt: existing?.lastStartedAt, lastStartedAt: existing?.lastStartedAt,
isExample: existing?.isExample ?? false, isExample: existing?.isExample ?? false,
tags: tags,
programs: templatePrograms, programs: templatePrograms,
overrides: templateOverrides, overrides: templateOverrides,
); );
@ -1560,6 +1676,64 @@ final class WorkoutTemplateUseCases {
return template; return template;
} }
Future<WorkoutTemplate> duplicate(String id) async {
final source = await templateRepository.findById(id);
if (source == null) {
throw const DomainException('Workout template not found.');
}
final activeTemplates = await templateRepository.listActive();
final now = clock.now();
final copyId = ids.newId();
final templateProgramIdsBySourceId = <String, String>{};
final copyPrograms = [
for (final program in source.programs)
() {
final copyProgramId = ids.newId();
templateProgramIdsBySourceId[program.metadata.id] = copyProgramId;
return WorkoutTemplateProgram(
metadata: _newMetadataWithId(copyProgramId, originDeviceId, now),
workoutTemplateId: copyId,
sourceProgramId: program.sourceProgramId,
position: program.position,
programNameSnapshot: program.programNameSnapshot,
defaultRestSecondsSnapshot: program.defaultRestSecondsSnapshot,
programSnapshotJson: program.programSnapshotJson,
);
}(),
];
final copyOverrides = [
for (final override in source.overrides)
if (templateProgramIdsBySourceId[override.workoutTemplateProgramId]
case final copyProgramId?)
WorkoutTemplateExerciseOverride(
metadata: _newMetadata(ids, originDeviceId, now),
workoutTemplateProgramId: copyProgramId,
snapshotProgramExerciseId: override.snapshotProgramExerciseId,
setsCountOverride: override.setsCountOverride,
targetTimeSecondsOverride: override.targetTimeSecondsOverride,
targetRepsOverride: override.targetRepsOverride,
targetScoreOverride: override.targetScoreOverride,
targetScoreTimeMsOverride: override.targetScoreTimeMsOverride,
autoStartNextTimedStepOverride:
override.autoStartNextTimedStepOverride,
),
];
final copy = WorkoutTemplate(
metadata: _newMetadataWithId(copyId, originDeviceId, now),
name: _copyName(
source.name,
activeTemplates.map((template) => template.name),
),
lastStartedAt: null,
isExample: false,
tags: source.tags,
programs: copyPrograms,
overrides: copyOverrides,
);
await templateRepository.replaceComposition(copy, now);
return copy;
}
Future<WorkoutTemplateProgram> addProgramSnapshot({ Future<WorkoutTemplateProgram> addProgramSnapshot({
required String workoutTemplateId, required String workoutTemplateId,
required String programId, required String programId,
@ -1622,6 +1796,7 @@ final class WorkoutTemplateUseCases {
metadata: template.metadata.markDeleted(now), metadata: template.metadata.markDeleted(now),
name: template.name, name: template.name,
lastStartedAt: template.lastStartedAt, lastStartedAt: template.lastStartedAt,
tags: template.tags,
); );
await templateRepository.save(deleted); await templateRepository.save(deleted);
return deleted; return deleted;
@ -4505,11 +4680,37 @@ EntityMetadata _newMetadata(
IdGenerator ids, IdGenerator ids,
String originDeviceId, String originDeviceId,
DateTime now, DateTime now,
) {
return _newMetadataWithId(ids.newId(), originDeviceId, now);
}
EntityMetadata _newMetadataWithId(
String id,
String originDeviceId,
DateTime now,
) { ) {
return EntityMetadata( return EntityMetadata(
id: ids.newId(), id: id,
createdAt: now, createdAt: now,
updatedAt: now, updatedAt: now,
originDeviceId: originDeviceId, originDeviceId: originDeviceId,
); );
} }
String _copyName(String sourceName, Iterable<String> existingNames) {
final normalizedNames = existingNames
.map((name) => name.trim().toLowerCase())
.toSet();
final firstCandidate = 'Copie de $sourceName';
if (!normalizedNames.contains(firstCandidate.trim().toLowerCase())) {
return firstCandidate;
}
var copyNumber = 2;
while (true) {
final candidate = 'Copie $copyNumber de $sourceName';
if (!normalizedNames.contains(candidate.trim().toLowerCase())) {
return candidate;
}
copyNumber += 1;
}
}

View File

@ -334,9 +334,11 @@ final class Exercise {
this.autoStartNextTimedStep = true, this.autoStartNextTimedStep = true,
this.category = ExerciseCategory.uncategorized, this.category = ExerciseCategory.uncategorized,
this.isExample = false, this.isExample = false,
List<String> tags = const [],
List<ExerciseStep> steps = const [], List<ExerciseStep> steps = const [],
this.archivedAt, this.archivedAt,
}) : name = _nonBlank(name, 'Exercise name'), }) : name = _nonBlank(name, 'Exercise name'),
tags = normalizeTags(tags),
imageMediaIds = _resolvedExerciseImageMediaIds( imageMediaIds = _resolvedExerciseImageMediaIds(
imageMediaId, imageMediaId,
imageMediaIds, imageMediaIds,
@ -394,6 +396,7 @@ final class Exercise {
final bool autoStartNextTimedStep; final bool autoStartNextTimedStep;
final ExerciseCategory category; final ExerciseCategory category;
final bool isExample; final bool isExample;
final List<String> tags;
final List<ExerciseStep> steps; final List<ExerciseStep> steps;
final DateTime? archivedAt; final DateTime? archivedAt;
@ -431,6 +434,7 @@ final class Exercise {
bool? autoStartNextTimedStep, bool? autoStartNextTimedStep,
ExerciseCategory? category, ExerciseCategory? category,
bool? isExample, bool? isExample,
Object? tags = _unchanged,
Object? steps = _unchanged, Object? steps = _unchanged,
Object? archivedAt = _unchanged, Object? archivedAt = _unchanged,
}) { }) {
@ -482,6 +486,7 @@ final class Exercise {
autoStartNextTimedStep ?? this.autoStartNextTimedStep, autoStartNextTimedStep ?? this.autoStartNextTimedStep,
category: category ?? this.category, category: category ?? this.category,
isExample: isExample ?? this.isExample, isExample: isExample ?? this.isExample,
tags: tags == _unchanged ? this.tags : tags as List<String>,
steps: steps == _unchanged ? this.steps : steps as List<ExerciseStep>, steps: steps == _unchanged ? this.steps : steps as List<ExerciseStep>,
archivedAt: archivedAt == _unchanged archivedAt: archivedAt == _unchanged
? this.archivedAt ? this.archivedAt
@ -552,8 +557,10 @@ final class Program {
required String name, required String name,
required this.defaultRestSeconds, required this.defaultRestSeconds,
this.isExample = false, this.isExample = false,
List<String> tags = const [],
this.exercises = const [], this.exercises = const [],
}) : name = _nonBlank(name, 'Program name') { }) : name = _nonBlank(name, 'Program name'),
tags = normalizeTags(tags) {
_requireNonNegative(defaultRestSeconds, 'Default rest seconds'); _requireNonNegative(defaultRestSeconds, 'Default rest seconds');
} }
@ -561,6 +568,7 @@ final class Program {
final String name; final String name;
final int defaultRestSeconds; final int defaultRestSeconds;
final bool isExample; final bool isExample;
final List<String> tags;
final List<ProgramExercise> exercises; final List<ProgramExercise> exercises;
Program copyWith({ Program copyWith({
@ -569,12 +577,14 @@ final class Program {
String? name, String? name,
int? defaultRestSeconds, int? defaultRestSeconds,
bool? isExample, bool? isExample,
Object? tags = _unchanged,
}) { }) {
return Program( return Program(
metadata: metadata ?? this.metadata, metadata: metadata ?? this.metadata,
name: name ?? this.name, name: name ?? this.name,
defaultRestSeconds: defaultRestSeconds ?? this.defaultRestSeconds, defaultRestSeconds: defaultRestSeconds ?? this.defaultRestSeconds,
isExample: isExample ?? this.isExample, isExample: isExample ?? this.isExample,
tags: tags == _unchanged ? this.tags : tags as List<String>,
exercises: exercises ?? this.exercises, exercises: exercises ?? this.exercises,
); );
} }
@ -769,14 +779,17 @@ final class WorkoutTemplate {
required String name, required String name,
this.lastStartedAt, this.lastStartedAt,
this.isExample = false, this.isExample = false,
List<String> tags = const [],
this.programs = const [], this.programs = const [],
this.overrides = const [], this.overrides = const [],
}) : name = _nonBlank(name, 'Workout template name'); }) : name = _nonBlank(name, 'Workout template name'),
tags = normalizeTags(tags);
final EntityMetadata metadata; final EntityMetadata metadata;
final String name; final String name;
final DateTime? lastStartedAt; final DateTime? lastStartedAt;
final bool isExample; final bool isExample;
final List<String> tags;
final List<WorkoutTemplateProgram> programs; final List<WorkoutTemplateProgram> programs;
final List<WorkoutTemplateExerciseOverride> overrides; final List<WorkoutTemplateExerciseOverride> overrides;
} }
@ -1591,6 +1604,28 @@ String _nonBlank(String? value, String label) {
return trimmed; return trimmed;
} }
List<String> normalizeTags(Iterable<String> tags) {
final normalized = <String>[];
final seen = <String>{};
for (final tag in tags) {
final value = tag.trim().replaceAll(RegExp(r'\s+'), ' ').toLowerCase();
if (value.isEmpty) {
throw const DomainException('Tag must not be blank.');
}
if (value.length > 24) {
throw const DomainException('Tag must be 24 characters or less.');
}
if (!seen.add(value)) {
throw const DomainException('Tags must be unique.');
}
normalized.add(value);
}
if (normalized.length > 8) {
throw const DomainException('An item cannot have more than 8 tags.');
}
return List.unmodifiable(normalized);
}
List<String> _validatedImageMediaIds(List<String> ids) { List<String> _validatedImageMediaIds(List<String> ids) {
if (ids.length > 5) { if (ids.length > 5) {
throw const DomainException('An exercise cannot have more than 5 images.'); throw const DomainException('An exercise cannot have more than 5 images.');

View File

@ -48,7 +48,7 @@ final class AppDatabase extends _$AppDatabase {
} }
@override @override
int get schemaVersion => 18; int get schemaVersion => 19;
@override @override
MigrationStrategy get migration { MigrationStrategy get migration {
@ -116,6 +116,9 @@ final class AppDatabase extends _$AppDatabase {
if (from < 17) { if (from < 17) {
await _migrateToSchema17(); await _migrateToSchema17();
} }
if (from < 19) {
await _migrateToSchema19();
}
await _createIndexes(); await _createIndexes();
}, },
beforeOpen: (details) async { beforeOpen: (details) async {
@ -746,6 +749,27 @@ CREATE TABLE IF NOT EXISTS active_set_timer_states (
await _backfillWorkoutHistoryStepSourceExerciseIds(); await _backfillWorkoutHistoryStepSourceExerciseIds();
} }
Future<void> _migrateToSchema19() async {
await _addColumnIfMissing(
tableName: 'exercises',
columnName: 'tags_json',
definition:
"tags_json TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags_json))",
);
await _addColumnIfMissing(
tableName: 'programs',
columnName: 'tags_json',
definition:
"tags_json TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags_json))",
);
await _addColumnIfMissing(
tableName: 'workout_templates',
columnName: 'tags_json',
definition:
"tags_json TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags_json))",
);
}
Future<void> _backfillWorkoutHistorySetSourceExerciseIds() async { Future<void> _backfillWorkoutHistorySetSourceExerciseIds() async {
await customStatement(r''' await customStatement(r'''
UPDATE workout_history_set_results AS result UPDATE workout_history_set_results AS result

View File

@ -170,6 +170,18 @@ class $WorkoutTemplatesTable extends WorkoutTemplates
), ),
defaultValue: const Constant(false), defaultValue: const Constant(false),
); );
static const VerificationMeta _tagsJsonMeta = const VerificationMeta(
'tagsJson',
);
@override
late final GeneratedColumn<String> tagsJson = GeneratedColumn<String>(
'tags_json',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant('[]'),
);
@override @override
List<GeneratedColumn> get $columns => [ List<GeneratedColumn> get $columns => [
id, id,
@ -186,6 +198,7 @@ class $WorkoutTemplatesTable extends WorkoutTemplates
name, name,
lastStartedAt, lastStartedAt,
isExample, isExample,
tagsJson,
]; ];
@override @override
String get aliasedName => _alias ?? actualTableName; String get aliasedName => _alias ?? actualTableName;
@ -315,6 +328,12 @@ class $WorkoutTemplatesTable extends WorkoutTemplates
isExample.isAcceptableOrUnknown(data['is_example']!, _isExampleMeta), isExample.isAcceptableOrUnknown(data['is_example']!, _isExampleMeta),
); );
} }
if (data.containsKey('tags_json')) {
context.handle(
_tagsJsonMeta,
tagsJson.isAcceptableOrUnknown(data['tags_json']!, _tagsJsonMeta),
);
}
return context; return context;
} }
@ -380,6 +399,10 @@ class $WorkoutTemplatesTable extends WorkoutTemplates
DriftSqlType.bool, DriftSqlType.bool,
data['${effectivePrefix}is_example'], data['${effectivePrefix}is_example'],
)!, )!,
tagsJson: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}tags_json'],
)!,
); );
} }
@ -404,6 +427,7 @@ class WorkoutTemplate extends DataClass implements Insertable<WorkoutTemplate> {
final String name; final String name;
final DateTime? lastStartedAt; final DateTime? lastStartedAt;
final bool isExample; final bool isExample;
final String tagsJson;
const WorkoutTemplate({ const WorkoutTemplate({
required this.id, required this.id,
required this.createdAt, required this.createdAt,
@ -419,6 +443,7 @@ class WorkoutTemplate extends DataClass implements Insertable<WorkoutTemplate> {
required this.name, required this.name,
this.lastStartedAt, this.lastStartedAt,
required this.isExample, required this.isExample,
required this.tagsJson,
}); });
@override @override
Map<String, Expression> toColumns(bool nullToAbsent) { Map<String, Expression> toColumns(bool nullToAbsent) {
@ -447,6 +472,7 @@ class WorkoutTemplate extends DataClass implements Insertable<WorkoutTemplate> {
map['last_started_at'] = Variable<DateTime>(lastStartedAt); map['last_started_at'] = Variable<DateTime>(lastStartedAt);
} }
map['is_example'] = Variable<bool>(isExample); map['is_example'] = Variable<bool>(isExample);
map['tags_json'] = Variable<String>(tagsJson);
return map; return map;
} }
@ -476,6 +502,7 @@ class WorkoutTemplate extends DataClass implements Insertable<WorkoutTemplate> {
? const Value.absent() ? const Value.absent()
: Value(lastStartedAt), : Value(lastStartedAt),
isExample: Value(isExample), isExample: Value(isExample),
tagsJson: Value(tagsJson),
); );
} }
@ -501,6 +528,7 @@ class WorkoutTemplate extends DataClass implements Insertable<WorkoutTemplate> {
name: serializer.fromJson<String>(json['name']), name: serializer.fromJson<String>(json['name']),
lastStartedAt: serializer.fromJson<DateTime?>(json['lastStartedAt']), lastStartedAt: serializer.fromJson<DateTime?>(json['lastStartedAt']),
isExample: serializer.fromJson<bool>(json['isExample']), isExample: serializer.fromJson<bool>(json['isExample']),
tagsJson: serializer.fromJson<String>(json['tagsJson']),
); );
} }
@override @override
@ -521,6 +549,7 @@ class WorkoutTemplate extends DataClass implements Insertable<WorkoutTemplate> {
'name': serializer.toJson<String>(name), 'name': serializer.toJson<String>(name),
'lastStartedAt': serializer.toJson<DateTime?>(lastStartedAt), 'lastStartedAt': serializer.toJson<DateTime?>(lastStartedAt),
'isExample': serializer.toJson<bool>(isExample), 'isExample': serializer.toJson<bool>(isExample),
'tagsJson': serializer.toJson<String>(tagsJson),
}; };
} }
@ -539,6 +568,7 @@ class WorkoutTemplate extends DataClass implements Insertable<WorkoutTemplate> {
String? name, String? name,
Value<DateTime?> lastStartedAt = const Value.absent(), Value<DateTime?> lastStartedAt = const Value.absent(),
bool? isExample, bool? isExample,
String? tagsJson,
}) => WorkoutTemplate( }) => WorkoutTemplate(
id: id ?? this.id, id: id ?? this.id,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
@ -560,6 +590,7 @@ class WorkoutTemplate extends DataClass implements Insertable<WorkoutTemplate> {
? lastStartedAt.value ? lastStartedAt.value
: this.lastStartedAt, : this.lastStartedAt,
isExample: isExample ?? this.isExample, isExample: isExample ?? this.isExample,
tagsJson: tagsJson ?? this.tagsJson,
); );
WorkoutTemplate copyWithCompanion(WorkoutTemplatesCompanion data) { WorkoutTemplate copyWithCompanion(WorkoutTemplatesCompanion data) {
return WorkoutTemplate( return WorkoutTemplate(
@ -591,6 +622,7 @@ class WorkoutTemplate extends DataClass implements Insertable<WorkoutTemplate> {
? data.lastStartedAt.value ? data.lastStartedAt.value
: this.lastStartedAt, : this.lastStartedAt,
isExample: data.isExample.present ? data.isExample.value : this.isExample, isExample: data.isExample.present ? data.isExample.value : this.isExample,
tagsJson: data.tagsJson.present ? data.tagsJson.value : this.tagsJson,
); );
} }
@ -610,7 +642,8 @@ class WorkoutTemplate extends DataClass implements Insertable<WorkoutTemplate> {
..write('remoteRevision: $remoteRevision, ') ..write('remoteRevision: $remoteRevision, ')
..write('name: $name, ') ..write('name: $name, ')
..write('lastStartedAt: $lastStartedAt, ') ..write('lastStartedAt: $lastStartedAt, ')
..write('isExample: $isExample') ..write('isExample: $isExample, ')
..write('tagsJson: $tagsJson')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@ -631,6 +664,7 @@ class WorkoutTemplate extends DataClass implements Insertable<WorkoutTemplate> {
name, name,
lastStartedAt, lastStartedAt,
isExample, isExample,
tagsJson,
); );
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@ -649,7 +683,8 @@ class WorkoutTemplate extends DataClass implements Insertable<WorkoutTemplate> {
other.remoteRevision == this.remoteRevision && other.remoteRevision == this.remoteRevision &&
other.name == this.name && other.name == this.name &&
other.lastStartedAt == this.lastStartedAt && other.lastStartedAt == this.lastStartedAt &&
other.isExample == this.isExample); other.isExample == this.isExample &&
other.tagsJson == this.tagsJson);
} }
class WorkoutTemplatesCompanion extends UpdateCompanion<WorkoutTemplate> { class WorkoutTemplatesCompanion extends UpdateCompanion<WorkoutTemplate> {
@ -667,6 +702,7 @@ class WorkoutTemplatesCompanion extends UpdateCompanion<WorkoutTemplate> {
final Value<String> name; final Value<String> name;
final Value<DateTime?> lastStartedAt; final Value<DateTime?> lastStartedAt;
final Value<bool> isExample; final Value<bool> isExample;
final Value<String> tagsJson;
final Value<int> rowid; final Value<int> rowid;
const WorkoutTemplatesCompanion({ const WorkoutTemplatesCompanion({
this.id = const Value.absent(), this.id = const Value.absent(),
@ -683,6 +719,7 @@ class WorkoutTemplatesCompanion extends UpdateCompanion<WorkoutTemplate> {
this.name = const Value.absent(), this.name = const Value.absent(),
this.lastStartedAt = const Value.absent(), this.lastStartedAt = const Value.absent(),
this.isExample = const Value.absent(), this.isExample = const Value.absent(),
this.tagsJson = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}); });
WorkoutTemplatesCompanion.insert({ WorkoutTemplatesCompanion.insert({
@ -700,6 +737,7 @@ class WorkoutTemplatesCompanion extends UpdateCompanion<WorkoutTemplate> {
required String name, required String name,
this.lastStartedAt = const Value.absent(), this.lastStartedAt = const Value.absent(),
this.isExample = const Value.absent(), this.isExample = const Value.absent(),
this.tagsJson = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}) : id = Value(id), }) : id = Value(id),
createdAt = Value(createdAt), createdAt = Value(createdAt),
@ -723,6 +761,7 @@ class WorkoutTemplatesCompanion extends UpdateCompanion<WorkoutTemplate> {
Expression<String>? name, Expression<String>? name,
Expression<DateTime>? lastStartedAt, Expression<DateTime>? lastStartedAt,
Expression<bool>? isExample, Expression<bool>? isExample,
Expression<String>? tagsJson,
Expression<int>? rowid, Expression<int>? rowid,
}) { }) {
return RawValuesInsertable({ return RawValuesInsertable({
@ -741,6 +780,7 @@ class WorkoutTemplatesCompanion extends UpdateCompanion<WorkoutTemplate> {
if (name != null) 'name': name, if (name != null) 'name': name,
if (lastStartedAt != null) 'last_started_at': lastStartedAt, if (lastStartedAt != null) 'last_started_at': lastStartedAt,
if (isExample != null) 'is_example': isExample, if (isExample != null) 'is_example': isExample,
if (tagsJson != null) 'tags_json': tagsJson,
if (rowid != null) 'rowid': rowid, if (rowid != null) 'rowid': rowid,
}); });
} }
@ -760,6 +800,7 @@ class WorkoutTemplatesCompanion extends UpdateCompanion<WorkoutTemplate> {
Value<String>? name, Value<String>? name,
Value<DateTime?>? lastStartedAt, Value<DateTime?>? lastStartedAt,
Value<bool>? isExample, Value<bool>? isExample,
Value<String>? tagsJson,
Value<int>? rowid, Value<int>? rowid,
}) { }) {
return WorkoutTemplatesCompanion( return WorkoutTemplatesCompanion(
@ -777,6 +818,7 @@ class WorkoutTemplatesCompanion extends UpdateCompanion<WorkoutTemplate> {
name: name ?? this.name, name: name ?? this.name,
lastStartedAt: lastStartedAt ?? this.lastStartedAt, lastStartedAt: lastStartedAt ?? this.lastStartedAt,
isExample: isExample ?? this.isExample, isExample: isExample ?? this.isExample,
tagsJson: tagsJson ?? this.tagsJson,
rowid: rowid ?? this.rowid, rowid: rowid ?? this.rowid,
); );
} }
@ -828,6 +870,9 @@ class WorkoutTemplatesCompanion extends UpdateCompanion<WorkoutTemplate> {
if (isExample.present) { if (isExample.present) {
map['is_example'] = Variable<bool>(isExample.value); map['is_example'] = Variable<bool>(isExample.value);
} }
if (tagsJson.present) {
map['tags_json'] = Variable<String>(tagsJson.value);
}
if (rowid.present) { if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value); map['rowid'] = Variable<int>(rowid.value);
} }
@ -851,6 +896,7 @@ class WorkoutTemplatesCompanion extends UpdateCompanion<WorkoutTemplate> {
..write('name: $name, ') ..write('name: $name, ')
..write('lastStartedAt: $lastStartedAt, ') ..write('lastStartedAt: $lastStartedAt, ')
..write('isExample: $isExample, ') ..write('isExample: $isExample, ')
..write('tagsJson: $tagsJson, ')
..write('rowid: $rowid') ..write('rowid: $rowid')
..write(')')) ..write(')'))
.toString(); .toString();
@ -12903,6 +12949,18 @@ class $ExercisesTable extends Exercises
), ),
defaultValue: const Constant(false), defaultValue: const Constant(false),
); );
static const VerificationMeta _tagsJsonMeta = const VerificationMeta(
'tagsJson',
);
@override
late final GeneratedColumn<String> tagsJson = GeneratedColumn<String>(
'tags_json',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant('[]'),
);
static const VerificationMeta _archivedAtMeta = const VerificationMeta( static const VerificationMeta _archivedAtMeta = const VerificationMeta(
'archivedAt', 'archivedAt',
); );
@ -12944,6 +13002,7 @@ class $ExercisesTable extends Exercises
autoStartNextTimedStep, autoStartNextTimedStep,
category, category,
isExample, isExample,
tagsJson,
archivedAt, archivedAt,
]; ];
@override @override
@ -13197,6 +13256,12 @@ class $ExercisesTable extends Exercises
isExample.isAcceptableOrUnknown(data['is_example']!, _isExampleMeta), isExample.isAcceptableOrUnknown(data['is_example']!, _isExampleMeta),
); );
} }
if (data.containsKey('tags_json')) {
context.handle(
_tagsJsonMeta,
tagsJson.isAcceptableOrUnknown(data['tags_json']!, _tagsJsonMeta),
);
}
if (data.containsKey('archived_at')) { if (data.containsKey('archived_at')) {
context.handle( context.handle(
_archivedAtMeta, _archivedAtMeta,
@ -13324,6 +13389,10 @@ class $ExercisesTable extends Exercises
DriftSqlType.bool, DriftSqlType.bool,
data['${effectivePrefix}is_example'], data['${effectivePrefix}is_example'],
)!, )!,
tagsJson: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}tags_json'],
)!,
archivedAt: attachedDatabase.typeMapping.read( archivedAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime, DriftSqlType.dateTime,
data['${effectivePrefix}archived_at'], data['${effectivePrefix}archived_at'],
@ -13366,6 +13435,7 @@ class Exercise extends DataClass implements Insertable<Exercise> {
final bool autoStartNextTimedStep; final bool autoStartNextTimedStep;
final String category; final String category;
final bool isExample; final bool isExample;
final String tagsJson;
final DateTime? archivedAt; final DateTime? archivedAt;
const Exercise({ const Exercise({
required this.id, required this.id,
@ -13396,6 +13466,7 @@ class Exercise extends DataClass implements Insertable<Exercise> {
required this.autoStartNextTimedStep, required this.autoStartNextTimedStep,
required this.category, required this.category,
required this.isExample, required this.isExample,
required this.tagsJson,
this.archivedAt, this.archivedAt,
}); });
@override @override
@ -13459,6 +13530,7 @@ class Exercise extends DataClass implements Insertable<Exercise> {
map['auto_start_next_timed_step'] = Variable<bool>(autoStartNextTimedStep); map['auto_start_next_timed_step'] = Variable<bool>(autoStartNextTimedStep);
map['category'] = Variable<String>(category); map['category'] = Variable<String>(category);
map['is_example'] = Variable<bool>(isExample); map['is_example'] = Variable<bool>(isExample);
map['tags_json'] = Variable<String>(tagsJson);
if (!nullToAbsent || archivedAt != null) { if (!nullToAbsent || archivedAt != null) {
map['archived_at'] = Variable<DateTime>(archivedAt); map['archived_at'] = Variable<DateTime>(archivedAt);
} }
@ -13521,6 +13593,7 @@ class Exercise extends DataClass implements Insertable<Exercise> {
autoStartNextTimedStep: Value(autoStartNextTimedStep), autoStartNextTimedStep: Value(autoStartNextTimedStep),
category: Value(category), category: Value(category),
isExample: Value(isExample), isExample: Value(isExample),
tagsJson: Value(tagsJson),
archivedAt: archivedAt == null && nullToAbsent archivedAt: archivedAt == null && nullToAbsent
? const Value.absent() ? const Value.absent()
: Value(archivedAt), : Value(archivedAt),
@ -13571,6 +13644,7 @@ class Exercise extends DataClass implements Insertable<Exercise> {
), ),
category: serializer.fromJson<String>(json['category']), category: serializer.fromJson<String>(json['category']),
isExample: serializer.fromJson<bool>(json['isExample']), isExample: serializer.fromJson<bool>(json['isExample']),
tagsJson: serializer.fromJson<String>(json['tagsJson']),
archivedAt: serializer.fromJson<DateTime?>(json['archivedAt']), archivedAt: serializer.fromJson<DateTime?>(json['archivedAt']),
); );
} }
@ -13610,6 +13684,7 @@ class Exercise extends DataClass implements Insertable<Exercise> {
'autoStartNextTimedStep': serializer.toJson<bool>(autoStartNextTimedStep), 'autoStartNextTimedStep': serializer.toJson<bool>(autoStartNextTimedStep),
'category': serializer.toJson<String>(category), 'category': serializer.toJson<String>(category),
'isExample': serializer.toJson<bool>(isExample), 'isExample': serializer.toJson<bool>(isExample),
'tagsJson': serializer.toJson<String>(tagsJson),
'archivedAt': serializer.toJson<DateTime?>(archivedAt), 'archivedAt': serializer.toJson<DateTime?>(archivedAt),
}; };
} }
@ -13643,6 +13718,7 @@ class Exercise extends DataClass implements Insertable<Exercise> {
bool? autoStartNextTimedStep, bool? autoStartNextTimedStep,
String? category, String? category,
bool? isExample, bool? isExample,
String? tagsJson,
Value<DateTime?> archivedAt = const Value.absent(), Value<DateTime?> archivedAt = const Value.absent(),
}) => Exercise( }) => Exercise(
id: id ?? this.id, id: id ?? this.id,
@ -13686,6 +13762,7 @@ class Exercise extends DataClass implements Insertable<Exercise> {
autoStartNextTimedStep ?? this.autoStartNextTimedStep, autoStartNextTimedStep ?? this.autoStartNextTimedStep,
category: category ?? this.category, category: category ?? this.category,
isExample: isExample ?? this.isExample, isExample: isExample ?? this.isExample,
tagsJson: tagsJson ?? this.tagsJson,
archivedAt: archivedAt.present ? archivedAt.value : this.archivedAt, archivedAt: archivedAt.present ? archivedAt.value : this.archivedAt,
); );
Exercise copyWithCompanion(ExercisesCompanion data) { Exercise copyWithCompanion(ExercisesCompanion data) {
@ -13756,6 +13833,7 @@ class Exercise extends DataClass implements Insertable<Exercise> {
: this.autoStartNextTimedStep, : this.autoStartNextTimedStep,
category: data.category.present ? data.category.value : this.category, category: data.category.present ? data.category.value : this.category,
isExample: data.isExample.present ? data.isExample.value : this.isExample, isExample: data.isExample.present ? data.isExample.value : this.isExample,
tagsJson: data.tagsJson.present ? data.tagsJson.value : this.tagsJson,
archivedAt: data.archivedAt.present archivedAt: data.archivedAt.present
? data.archivedAt.value ? data.archivedAt.value
: this.archivedAt, : this.archivedAt,
@ -13793,6 +13871,7 @@ class Exercise extends DataClass implements Insertable<Exercise> {
..write('autoStartNextTimedStep: $autoStartNextTimedStep, ') ..write('autoStartNextTimedStep: $autoStartNextTimedStep, ')
..write('category: $category, ') ..write('category: $category, ')
..write('isExample: $isExample, ') ..write('isExample: $isExample, ')
..write('tagsJson: $tagsJson, ')
..write('archivedAt: $archivedAt') ..write('archivedAt: $archivedAt')
..write(')')) ..write(')'))
.toString(); .toString();
@ -13828,6 +13907,7 @@ class Exercise extends DataClass implements Insertable<Exercise> {
autoStartNextTimedStep, autoStartNextTimedStep,
category, category,
isExample, isExample,
tagsJson,
archivedAt, archivedAt,
]); ]);
@override @override
@ -13862,6 +13942,7 @@ class Exercise extends DataClass implements Insertable<Exercise> {
other.autoStartNextTimedStep == this.autoStartNextTimedStep && other.autoStartNextTimedStep == this.autoStartNextTimedStep &&
other.category == this.category && other.category == this.category &&
other.isExample == this.isExample && other.isExample == this.isExample &&
other.tagsJson == this.tagsJson &&
other.archivedAt == this.archivedAt); other.archivedAt == this.archivedAt);
} }
@ -13894,6 +13975,7 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
final Value<bool> autoStartNextTimedStep; final Value<bool> autoStartNextTimedStep;
final Value<String> category; final Value<String> category;
final Value<bool> isExample; final Value<bool> isExample;
final Value<String> tagsJson;
final Value<DateTime?> archivedAt; final Value<DateTime?> archivedAt;
final Value<int> rowid; final Value<int> rowid;
const ExercisesCompanion({ const ExercisesCompanion({
@ -13925,6 +14007,7 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
this.autoStartNextTimedStep = const Value.absent(), this.autoStartNextTimedStep = const Value.absent(),
this.category = const Value.absent(), this.category = const Value.absent(),
this.isExample = const Value.absent(), this.isExample = const Value.absent(),
this.tagsJson = const Value.absent(),
this.archivedAt = const Value.absent(), this.archivedAt = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}); });
@ -13957,6 +14040,7 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
this.autoStartNextTimedStep = const Value.absent(), this.autoStartNextTimedStep = const Value.absent(),
this.category = const Value.absent(), this.category = const Value.absent(),
this.isExample = const Value.absent(), this.isExample = const Value.absent(),
this.tagsJson = 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),
@ -13998,6 +14082,7 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
Expression<bool>? autoStartNextTimedStep, Expression<bool>? autoStartNextTimedStep,
Expression<String>? category, Expression<String>? category,
Expression<bool>? isExample, Expression<bool>? isExample,
Expression<String>? tagsJson,
Expression<DateTime>? archivedAt, Expression<DateTime>? archivedAt,
Expression<int>? rowid, Expression<int>? rowid,
}) { }) {
@ -14035,6 +14120,7 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
'auto_start_next_timed_step': autoStartNextTimedStep, 'auto_start_next_timed_step': autoStartNextTimedStep,
if (category != null) 'category': category, if (category != null) 'category': category,
if (isExample != null) 'is_example': isExample, if (isExample != null) 'is_example': isExample,
if (tagsJson != null) 'tags_json': tagsJson,
if (archivedAt != null) 'archived_at': archivedAt, if (archivedAt != null) 'archived_at': archivedAt,
if (rowid != null) 'rowid': rowid, if (rowid != null) 'rowid': rowid,
}); });
@ -14069,6 +14155,7 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
Value<bool>? autoStartNextTimedStep, Value<bool>? autoStartNextTimedStep,
Value<String>? category, Value<String>? category,
Value<bool>? isExample, Value<bool>? isExample,
Value<String>? tagsJson,
Value<DateTime?>? archivedAt, Value<DateTime?>? archivedAt,
Value<int>? rowid, Value<int>? rowid,
}) { }) {
@ -14104,6 +14191,7 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
autoStartNextTimedStep ?? this.autoStartNextTimedStep, autoStartNextTimedStep ?? this.autoStartNextTimedStep,
category: category ?? this.category, category: category ?? this.category,
isExample: isExample ?? this.isExample, isExample: isExample ?? this.isExample,
tagsJson: tagsJson ?? this.tagsJson,
archivedAt: archivedAt ?? this.archivedAt, archivedAt: archivedAt ?? this.archivedAt,
rowid: rowid ?? this.rowid, rowid: rowid ?? this.rowid,
); );
@ -14204,6 +14292,9 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
if (isExample.present) { if (isExample.present) {
map['is_example'] = Variable<bool>(isExample.value); map['is_example'] = Variable<bool>(isExample.value);
} }
if (tagsJson.present) {
map['tags_json'] = Variable<String>(tagsJson.value);
}
if (archivedAt.present) { if (archivedAt.present) {
map['archived_at'] = Variable<DateTime>(archivedAt.value); map['archived_at'] = Variable<DateTime>(archivedAt.value);
} }
@ -14244,6 +14335,7 @@ class ExercisesCompanion extends UpdateCompanion<Exercise> {
..write('autoStartNextTimedStep: $autoStartNextTimedStep, ') ..write('autoStartNextTimedStep: $autoStartNextTimedStep, ')
..write('category: $category, ') ..write('category: $category, ')
..write('isExample: $isExample, ') ..write('isExample: $isExample, ')
..write('tagsJson: $tagsJson, ')
..write('archivedAt: $archivedAt, ') ..write('archivedAt: $archivedAt, ')
..write('rowid: $rowid') ..write('rowid: $rowid')
..write(')')) ..write(')'))
@ -18019,6 +18111,18 @@ class $ProgramsTable extends Programs with TableInfo<$ProgramsTable, Program> {
), ),
defaultValue: const Constant(false), defaultValue: const Constant(false),
); );
static const VerificationMeta _tagsJsonMeta = const VerificationMeta(
'tagsJson',
);
@override
late final GeneratedColumn<String> tagsJson = GeneratedColumn<String>(
'tags_json',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant('[]'),
);
@override @override
List<GeneratedColumn> get $columns => [ List<GeneratedColumn> get $columns => [
id, id,
@ -18035,6 +18139,7 @@ class $ProgramsTable extends Programs with TableInfo<$ProgramsTable, Program> {
name, name,
defaultRestSeconds, defaultRestSeconds,
isExample, isExample,
tagsJson,
]; ];
@override @override
String get aliasedName => _alias ?? actualTableName; String get aliasedName => _alias ?? actualTableName;
@ -18166,6 +18271,12 @@ class $ProgramsTable extends Programs with TableInfo<$ProgramsTable, Program> {
isExample.isAcceptableOrUnknown(data['is_example']!, _isExampleMeta), isExample.isAcceptableOrUnknown(data['is_example']!, _isExampleMeta),
); );
} }
if (data.containsKey('tags_json')) {
context.handle(
_tagsJsonMeta,
tagsJson.isAcceptableOrUnknown(data['tags_json']!, _tagsJsonMeta),
);
}
return context; return context;
} }
@ -18231,6 +18342,10 @@ class $ProgramsTable extends Programs with TableInfo<$ProgramsTable, Program> {
DriftSqlType.bool, DriftSqlType.bool,
data['${effectivePrefix}is_example'], data['${effectivePrefix}is_example'],
)!, )!,
tagsJson: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}tags_json'],
)!,
); );
} }
@ -18255,6 +18370,7 @@ class Program extends DataClass implements Insertable<Program> {
final String name; final String name;
final int defaultRestSeconds; final int defaultRestSeconds;
final bool isExample; final bool isExample;
final String tagsJson;
const Program({ const Program({
required this.id, required this.id,
required this.createdAt, required this.createdAt,
@ -18270,6 +18386,7 @@ class Program extends DataClass implements Insertable<Program> {
required this.name, required this.name,
required this.defaultRestSeconds, required this.defaultRestSeconds,
required this.isExample, required this.isExample,
required this.tagsJson,
}); });
@override @override
Map<String, Expression> toColumns(bool nullToAbsent) { Map<String, Expression> toColumns(bool nullToAbsent) {
@ -18296,6 +18413,7 @@ class Program extends DataClass implements Insertable<Program> {
map['name'] = Variable<String>(name); map['name'] = Variable<String>(name);
map['default_rest_seconds'] = Variable<int>(defaultRestSeconds); map['default_rest_seconds'] = Variable<int>(defaultRestSeconds);
map['is_example'] = Variable<bool>(isExample); map['is_example'] = Variable<bool>(isExample);
map['tags_json'] = Variable<String>(tagsJson);
return map; return map;
} }
@ -18323,6 +18441,7 @@ class Program extends DataClass implements Insertable<Program> {
name: Value(name), name: Value(name),
defaultRestSeconds: Value(defaultRestSeconds), defaultRestSeconds: Value(defaultRestSeconds),
isExample: Value(isExample), isExample: Value(isExample),
tagsJson: Value(tagsJson),
); );
} }
@ -18348,6 +18467,7 @@ class Program extends DataClass implements Insertable<Program> {
name: serializer.fromJson<String>(json['name']), name: serializer.fromJson<String>(json['name']),
defaultRestSeconds: serializer.fromJson<int>(json['defaultRestSeconds']), defaultRestSeconds: serializer.fromJson<int>(json['defaultRestSeconds']),
isExample: serializer.fromJson<bool>(json['isExample']), isExample: serializer.fromJson<bool>(json['isExample']),
tagsJson: serializer.fromJson<String>(json['tagsJson']),
); );
} }
@override @override
@ -18368,6 +18488,7 @@ class Program extends DataClass implements Insertable<Program> {
'name': serializer.toJson<String>(name), 'name': serializer.toJson<String>(name),
'defaultRestSeconds': serializer.toJson<int>(defaultRestSeconds), 'defaultRestSeconds': serializer.toJson<int>(defaultRestSeconds),
'isExample': serializer.toJson<bool>(isExample), 'isExample': serializer.toJson<bool>(isExample),
'tagsJson': serializer.toJson<String>(tagsJson),
}; };
} }
@ -18386,6 +18507,7 @@ class Program extends DataClass implements Insertable<Program> {
String? name, String? name,
int? defaultRestSeconds, int? defaultRestSeconds,
bool? isExample, bool? isExample,
String? tagsJson,
}) => Program( }) => Program(
id: id ?? this.id, id: id ?? this.id,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
@ -18405,6 +18527,7 @@ class Program extends DataClass implements Insertable<Program> {
name: name ?? this.name, name: name ?? this.name,
defaultRestSeconds: defaultRestSeconds ?? this.defaultRestSeconds, defaultRestSeconds: defaultRestSeconds ?? this.defaultRestSeconds,
isExample: isExample ?? this.isExample, isExample: isExample ?? this.isExample,
tagsJson: tagsJson ?? this.tagsJson,
); );
Program copyWithCompanion(ProgramsCompanion data) { Program copyWithCompanion(ProgramsCompanion data) {
return Program( return Program(
@ -18436,6 +18559,7 @@ class Program extends DataClass implements Insertable<Program> {
? data.defaultRestSeconds.value ? data.defaultRestSeconds.value
: this.defaultRestSeconds, : this.defaultRestSeconds,
isExample: data.isExample.present ? data.isExample.value : this.isExample, isExample: data.isExample.present ? data.isExample.value : this.isExample,
tagsJson: data.tagsJson.present ? data.tagsJson.value : this.tagsJson,
); );
} }
@ -18455,7 +18579,8 @@ class Program extends DataClass implements Insertable<Program> {
..write('remoteRevision: $remoteRevision, ') ..write('remoteRevision: $remoteRevision, ')
..write('name: $name, ') ..write('name: $name, ')
..write('defaultRestSeconds: $defaultRestSeconds, ') ..write('defaultRestSeconds: $defaultRestSeconds, ')
..write('isExample: $isExample') ..write('isExample: $isExample, ')
..write('tagsJson: $tagsJson')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@ -18476,6 +18601,7 @@ class Program extends DataClass implements Insertable<Program> {
name, name,
defaultRestSeconds, defaultRestSeconds,
isExample, isExample,
tagsJson,
); );
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@ -18494,7 +18620,8 @@ class Program extends DataClass implements Insertable<Program> {
other.remoteRevision == this.remoteRevision && other.remoteRevision == this.remoteRevision &&
other.name == this.name && other.name == this.name &&
other.defaultRestSeconds == this.defaultRestSeconds && other.defaultRestSeconds == this.defaultRestSeconds &&
other.isExample == this.isExample); other.isExample == this.isExample &&
other.tagsJson == this.tagsJson);
} }
class ProgramsCompanion extends UpdateCompanion<Program> { class ProgramsCompanion extends UpdateCompanion<Program> {
@ -18512,6 +18639,7 @@ class ProgramsCompanion extends UpdateCompanion<Program> {
final Value<String> name; final Value<String> name;
final Value<int> defaultRestSeconds; final Value<int> defaultRestSeconds;
final Value<bool> isExample; final Value<bool> isExample;
final Value<String> tagsJson;
final Value<int> rowid; final Value<int> rowid;
const ProgramsCompanion({ const ProgramsCompanion({
this.id = const Value.absent(), this.id = const Value.absent(),
@ -18528,6 +18656,7 @@ class ProgramsCompanion extends UpdateCompanion<Program> {
this.name = const Value.absent(), this.name = const Value.absent(),
this.defaultRestSeconds = const Value.absent(), this.defaultRestSeconds = const Value.absent(),
this.isExample = const Value.absent(), this.isExample = const Value.absent(),
this.tagsJson = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}); });
ProgramsCompanion.insert({ ProgramsCompanion.insert({
@ -18545,6 +18674,7 @@ class ProgramsCompanion extends UpdateCompanion<Program> {
required String name, required String name,
required int defaultRestSeconds, required int defaultRestSeconds,
this.isExample = const Value.absent(), this.isExample = const Value.absent(),
this.tagsJson = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}) : id = Value(id), }) : id = Value(id),
createdAt = Value(createdAt), createdAt = Value(createdAt),
@ -18569,6 +18699,7 @@ class ProgramsCompanion extends UpdateCompanion<Program> {
Expression<String>? name, Expression<String>? name,
Expression<int>? defaultRestSeconds, Expression<int>? defaultRestSeconds,
Expression<bool>? isExample, Expression<bool>? isExample,
Expression<String>? tagsJson,
Expression<int>? rowid, Expression<int>? rowid,
}) { }) {
return RawValuesInsertable({ return RawValuesInsertable({
@ -18588,6 +18719,7 @@ class ProgramsCompanion extends UpdateCompanion<Program> {
if (defaultRestSeconds != null) if (defaultRestSeconds != null)
'default_rest_seconds': defaultRestSeconds, 'default_rest_seconds': defaultRestSeconds,
if (isExample != null) 'is_example': isExample, if (isExample != null) 'is_example': isExample,
if (tagsJson != null) 'tags_json': tagsJson,
if (rowid != null) 'rowid': rowid, if (rowid != null) 'rowid': rowid,
}); });
} }
@ -18607,6 +18739,7 @@ class ProgramsCompanion extends UpdateCompanion<Program> {
Value<String>? name, Value<String>? name,
Value<int>? defaultRestSeconds, Value<int>? defaultRestSeconds,
Value<bool>? isExample, Value<bool>? isExample,
Value<String>? tagsJson,
Value<int>? rowid, Value<int>? rowid,
}) { }) {
return ProgramsCompanion( return ProgramsCompanion(
@ -18624,6 +18757,7 @@ class ProgramsCompanion extends UpdateCompanion<Program> {
name: name ?? this.name, name: name ?? this.name,
defaultRestSeconds: defaultRestSeconds ?? this.defaultRestSeconds, defaultRestSeconds: defaultRestSeconds ?? this.defaultRestSeconds,
isExample: isExample ?? this.isExample, isExample: isExample ?? this.isExample,
tagsJson: tagsJson ?? this.tagsJson,
rowid: rowid ?? this.rowid, rowid: rowid ?? this.rowid,
); );
} }
@ -18675,6 +18809,9 @@ class ProgramsCompanion extends UpdateCompanion<Program> {
if (isExample.present) { if (isExample.present) {
map['is_example'] = Variable<bool>(isExample.value); map['is_example'] = Variable<bool>(isExample.value);
} }
if (tagsJson.present) {
map['tags_json'] = Variable<String>(tagsJson.value);
}
if (rowid.present) { if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value); map['rowid'] = Variable<int>(rowid.value);
} }
@ -18698,6 +18835,7 @@ class ProgramsCompanion extends UpdateCompanion<Program> {
..write('name: $name, ') ..write('name: $name, ')
..write('defaultRestSeconds: $defaultRestSeconds, ') ..write('defaultRestSeconds: $defaultRestSeconds, ')
..write('isExample: $isExample, ') ..write('isExample: $isExample, ')
..write('tagsJson: $tagsJson, ')
..write('rowid: $rowid') ..write('rowid: $rowid')
..write(')')) ..write(')'))
.toString(); .toString();
@ -30394,6 +30532,7 @@ typedef $$WorkoutTemplatesTableCreateCompanionBuilder =
required String name, required String name,
Value<DateTime?> lastStartedAt, Value<DateTime?> lastStartedAt,
Value<bool> isExample, Value<bool> isExample,
Value<String> tagsJson,
Value<int> rowid, Value<int> rowid,
}); });
typedef $$WorkoutTemplatesTableUpdateCompanionBuilder = typedef $$WorkoutTemplatesTableUpdateCompanionBuilder =
@ -30412,6 +30551,7 @@ typedef $$WorkoutTemplatesTableUpdateCompanionBuilder =
Value<String> name, Value<String> name,
Value<DateTime?> lastStartedAt, Value<DateTime?> lastStartedAt,
Value<bool> isExample, Value<bool> isExample,
Value<String> tagsJson,
Value<int> rowid, Value<int> rowid,
}); });
@ -30589,6 +30729,11 @@ class $$WorkoutTemplatesTableFilterComposer
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
ColumnFilters<String> get tagsJson => $composableBuilder(
column: $table.tagsJson,
builder: (column) => ColumnFilters(column),
);
Expression<bool> activeWorkoutSessionsRefs( Expression<bool> activeWorkoutSessionsRefs(
Expression<bool> Function($$ActiveWorkoutSessionsTableFilterComposer f) f, Expression<bool> Function($$ActiveWorkoutSessionsTableFilterComposer f) f,
) { ) {
@ -30745,6 +30890,11 @@ class $$WorkoutTemplatesTableOrderingComposer
column: $table.isExample, column: $table.isExample,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<String> get tagsJson => $composableBuilder(
column: $table.tagsJson,
builder: (column) => ColumnOrderings(column),
);
} }
class $$WorkoutTemplatesTableAnnotationComposer class $$WorkoutTemplatesTableAnnotationComposer
@ -30812,6 +30962,9 @@ class $$WorkoutTemplatesTableAnnotationComposer
GeneratedColumn<bool> get isExample => GeneratedColumn<bool> get isExample =>
$composableBuilder(column: $table.isExample, builder: (column) => column); $composableBuilder(column: $table.isExample, builder: (column) => column);
GeneratedColumn<String> get tagsJson =>
$composableBuilder(column: $table.tagsJson, builder: (column) => column);
Expression<T> activeWorkoutSessionsRefs<T extends Object>( Expression<T> activeWorkoutSessionsRefs<T extends Object>(
Expression<T> Function($$ActiveWorkoutSessionsTableAnnotationComposer a) f, Expression<T> Function($$ActiveWorkoutSessionsTableAnnotationComposer a) f,
) { ) {
@ -30939,6 +31092,7 @@ class $$WorkoutTemplatesTableTableManager
Value<String> name = const Value.absent(), Value<String> name = const Value.absent(),
Value<DateTime?> lastStartedAt = const Value.absent(), Value<DateTime?> lastStartedAt = const Value.absent(),
Value<bool> isExample = const Value.absent(), Value<bool> isExample = const Value.absent(),
Value<String> tagsJson = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => WorkoutTemplatesCompanion( }) => WorkoutTemplatesCompanion(
id: id, id: id,
@ -30955,6 +31109,7 @@ class $$WorkoutTemplatesTableTableManager
name: name, name: name,
lastStartedAt: lastStartedAt, lastStartedAt: lastStartedAt,
isExample: isExample, isExample: isExample,
tagsJson: tagsJson,
rowid: rowid, rowid: rowid,
), ),
createCompanionCallback: createCompanionCallback:
@ -30973,6 +31128,7 @@ class $$WorkoutTemplatesTableTableManager
required String name, required String name,
Value<DateTime?> lastStartedAt = const Value.absent(), Value<DateTime?> lastStartedAt = const Value.absent(),
Value<bool> isExample = const Value.absent(), Value<bool> isExample = const Value.absent(),
Value<String> tagsJson = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => WorkoutTemplatesCompanion.insert( }) => WorkoutTemplatesCompanion.insert(
id: id, id: id,
@ -30989,6 +31145,7 @@ class $$WorkoutTemplatesTableTableManager
name: name, name: name,
lastStartedAt: lastStartedAt, lastStartedAt: lastStartedAt,
isExample: isExample, isExample: isExample,
tagsJson: tagsJson,
rowid: rowid, rowid: rowid,
), ),
withReferenceMapper: (p0) => p0 withReferenceMapper: (p0) => p0
@ -38397,6 +38554,7 @@ typedef $$ExercisesTableCreateCompanionBuilder =
Value<bool> autoStartNextTimedStep, Value<bool> autoStartNextTimedStep,
Value<String> category, Value<String> category,
Value<bool> isExample, Value<bool> isExample,
Value<String> tagsJson,
Value<DateTime?> archivedAt, Value<DateTime?> archivedAt,
Value<int> rowid, Value<int> rowid,
}); });
@ -38430,6 +38588,7 @@ typedef $$ExercisesTableUpdateCompanionBuilder =
Value<bool> autoStartNextTimedStep, Value<bool> autoStartNextTimedStep,
Value<String> category, Value<String> category,
Value<bool> isExample, Value<bool> isExample,
Value<String> tagsJson,
Value<DateTime?> archivedAt, Value<DateTime?> archivedAt,
Value<int> rowid, Value<int> rowid,
}); });
@ -38668,6 +38827,11 @@ class $$ExercisesTableFilterComposer
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
ColumnFilters<String> get tagsJson => $composableBuilder(
column: $table.tagsJson,
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),
@ -38934,6 +39098,11 @@ class $$ExercisesTableOrderingComposer
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<String> get tagsJson => $composableBuilder(
column: $table.tagsJson,
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),
@ -39107,6 +39276,9 @@ class $$ExercisesTableAnnotationComposer
GeneratedColumn<bool> get isExample => GeneratedColumn<bool> get isExample =>
$composableBuilder(column: $table.isExample, builder: (column) => column); $composableBuilder(column: $table.isExample, builder: (column) => column);
GeneratedColumn<String> get tagsJson =>
$composableBuilder(column: $table.tagsJson, 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,
@ -39296,6 +39468,7 @@ class $$ExercisesTableTableManager
Value<bool> autoStartNextTimedStep = const Value.absent(), Value<bool> autoStartNextTimedStep = const Value.absent(),
Value<String> category = const Value.absent(), Value<String> category = const Value.absent(),
Value<bool> isExample = const Value.absent(), Value<bool> isExample = const Value.absent(),
Value<String> tagsJson = 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(
@ -39327,6 +39500,7 @@ class $$ExercisesTableTableManager
autoStartNextTimedStep: autoStartNextTimedStep, autoStartNextTimedStep: autoStartNextTimedStep,
category: category, category: category,
isExample: isExample, isExample: isExample,
tagsJson: tagsJson,
archivedAt: archivedAt, archivedAt: archivedAt,
rowid: rowid, rowid: rowid,
), ),
@ -39360,6 +39534,7 @@ class $$ExercisesTableTableManager
Value<bool> autoStartNextTimedStep = const Value.absent(), Value<bool> autoStartNextTimedStep = const Value.absent(),
Value<String> category = const Value.absent(), Value<String> category = const Value.absent(),
Value<bool> isExample = const Value.absent(), Value<bool> isExample = const Value.absent(),
Value<String> tagsJson = 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(
@ -39391,6 +39566,7 @@ class $$ExercisesTableTableManager
autoStartNextTimedStep: autoStartNextTimedStep, autoStartNextTimedStep: autoStartNextTimedStep,
category: category, category: category,
isExample: isExample, isExample: isExample,
tagsJson: tagsJson,
archivedAt: archivedAt, archivedAt: archivedAt,
rowid: rowid, rowid: rowid,
), ),
@ -41614,6 +41790,7 @@ typedef $$ProgramsTableCreateCompanionBuilder =
required String name, required String name,
required int defaultRestSeconds, required int defaultRestSeconds,
Value<bool> isExample, Value<bool> isExample,
Value<String> tagsJson,
Value<int> rowid, Value<int> rowid,
}); });
typedef $$ProgramsTableUpdateCompanionBuilder = typedef $$ProgramsTableUpdateCompanionBuilder =
@ -41632,6 +41809,7 @@ typedef $$ProgramsTableUpdateCompanionBuilder =
Value<String> name, Value<String> name,
Value<int> defaultRestSeconds, Value<int> defaultRestSeconds,
Value<bool> isExample, Value<bool> isExample,
Value<String> tagsJson,
Value<int> rowid, Value<int> rowid,
}); });
@ -41767,6 +41945,11 @@ class $$ProgramsTableFilterComposer
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
ColumnFilters<String> get tagsJson => $composableBuilder(
column: $table.tagsJson,
builder: (column) => ColumnFilters(column),
);
Expression<bool> programExercisesRefs( Expression<bool> programExercisesRefs(
Expression<bool> Function($$ProgramExercisesTableFilterComposer f) f, Expression<bool> Function($$ProgramExercisesTableFilterComposer f) f,
) { ) {
@ -41897,6 +42080,11 @@ class $$ProgramsTableOrderingComposer
column: $table.isExample, column: $table.isExample,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<String> get tagsJson => $composableBuilder(
column: $table.tagsJson,
builder: (column) => ColumnOrderings(column),
);
} }
class $$ProgramsTableAnnotationComposer class $$ProgramsTableAnnotationComposer
@ -41964,6 +42152,9 @@ class $$ProgramsTableAnnotationComposer
GeneratedColumn<bool> get isExample => GeneratedColumn<bool> get isExample =>
$composableBuilder(column: $table.isExample, builder: (column) => column); $composableBuilder(column: $table.isExample, builder: (column) => column);
GeneratedColumn<String> get tagsJson =>
$composableBuilder(column: $table.tagsJson, builder: (column) => column);
Expression<T> programExercisesRefs<T extends Object>( Expression<T> programExercisesRefs<T extends Object>(
Expression<T> Function($$ProgramExercisesTableAnnotationComposer a) f, Expression<T> Function($$ProgramExercisesTableAnnotationComposer a) f,
) { ) {
@ -42062,6 +42253,7 @@ class $$ProgramsTableTableManager
Value<String> name = const Value.absent(), Value<String> name = const Value.absent(),
Value<int> defaultRestSeconds = const Value.absent(), Value<int> defaultRestSeconds = const Value.absent(),
Value<bool> isExample = const Value.absent(), Value<bool> isExample = const Value.absent(),
Value<String> tagsJson = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => ProgramsCompanion( }) => ProgramsCompanion(
id: id, id: id,
@ -42078,6 +42270,7 @@ class $$ProgramsTableTableManager
name: name, name: name,
defaultRestSeconds: defaultRestSeconds, defaultRestSeconds: defaultRestSeconds,
isExample: isExample, isExample: isExample,
tagsJson: tagsJson,
rowid: rowid, rowid: rowid,
), ),
createCompanionCallback: createCompanionCallback:
@ -42096,6 +42289,7 @@ class $$ProgramsTableTableManager
required String name, required String name,
required int defaultRestSeconds, required int defaultRestSeconds,
Value<bool> isExample = const Value.absent(), Value<bool> isExample = const Value.absent(),
Value<String> tagsJson = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => ProgramsCompanion.insert( }) => ProgramsCompanion.insert(
id: id, id: id,
@ -42112,6 +42306,7 @@ class $$ProgramsTableTableManager
name: name, name: name,
defaultRestSeconds: defaultRestSeconds, defaultRestSeconds: defaultRestSeconds,
isExample: isExample, isExample: isExample,
tagsJson: tagsJson,
rowid: rowid, rowid: rowid,
), ),
withReferenceMapper: (p0) => p0 withReferenceMapper: (p0) => p0

View File

@ -2816,6 +2816,7 @@ db.ExercisesCompanion _exerciseCompanion(domain.Exercise exercise) {
defaultTargetScore: Value(exercise.defaultTargetScore), defaultTargetScore: Value(exercise.defaultTargetScore),
defaultTargetScoreTimeMs: Value(exercise.defaultTargetScoreTimeMs), defaultTargetScoreTimeMs: Value(exercise.defaultTargetScoreTimeMs),
autoStartNextTimedStep: Value(exercise.autoStartNextTimedStep), autoStartNextTimedStep: Value(exercise.autoStartNextTimedStep),
tagsJson: Value(_encodeTags(exercise.tags)),
archivedAt: Value(_utcOrNull(exercise.archivedAt)), archivedAt: Value(_utcOrNull(exercise.archivedAt)),
); );
} }
@ -2846,6 +2847,7 @@ domain.Exercise _exerciseFromRow(
autoStartNextTimedStep: row.autoStartNextTimedStep, autoStartNextTimedStep: row.autoStartNextTimedStep,
category: starterMetadata.category, category: starterMetadata.category,
isExample: starterMetadata.isExample, isExample: starterMetadata.isExample,
tags: _decodeTags(row.tagsJson),
steps: steps, steps: steps,
archivedAt: _utcOrNull(row.archivedAt), archivedAt: _utcOrNull(row.archivedAt),
); );
@ -3028,6 +3030,7 @@ db.ProgramsCompanion _programCompanion(domain.Program program) {
remoteRevision: values[10] as Value<String?>, remoteRevision: values[10] as Value<String?>,
name: Value(program.name), name: Value(program.name),
defaultRestSeconds: Value(program.defaultRestSeconds), defaultRestSeconds: Value(program.defaultRestSeconds),
tagsJson: Value(_encodeTags(program.tags)),
); );
} }
@ -3041,6 +3044,7 @@ domain.Program _programFromRow(
name: row.name, name: row.name,
defaultRestSeconds: row.defaultRestSeconds, defaultRestSeconds: row.defaultRestSeconds,
isExample: isExample, isExample: isExample,
tags: _decodeTags(row.tagsJson),
exercises: exercises, exercises: exercises,
); );
} }
@ -3157,6 +3161,7 @@ db.WorkoutTemplatesCompanion _workoutTemplateCompanion(
remoteRevision: values[10] as Value<String?>, remoteRevision: values[10] as Value<String?>,
name: Value(template.name), name: Value(template.name),
lastStartedAt: Value(_utcOrNull(template.lastStartedAt)), lastStartedAt: Value(_utcOrNull(template.lastStartedAt)),
tagsJson: Value(_encodeTags(template.tags)),
); );
} }
@ -3171,6 +3176,7 @@ domain.WorkoutTemplate _workoutTemplateFromRow(
name: row.name, name: row.name,
lastStartedAt: _utcOrNull(row.lastStartedAt), lastStartedAt: _utcOrNull(row.lastStartedAt),
isExample: isExample, isExample: isExample,
tags: _decodeTags(row.tagsJson),
programs: programs, programs: programs,
overrides: overrides, overrides: overrides,
); );
@ -4077,6 +4083,7 @@ Map<String, Object?> _exercisePayload(domain.Exercise exercise) => {
'autoStartNextTimedStep': exercise.autoStartNextTimedStep, 'autoStartNextTimedStep': exercise.autoStartNextTimedStep,
'category': exercise.category.name, 'category': exercise.category.name,
'isExample': exercise.isExample, 'isExample': exercise.isExample,
'tags': exercise.tags,
'steps': exercise.steps.map((step) => step.toSnapshotJson()).toList(), 'steps': exercise.steps.map((step) => step.toSnapshotJson()).toList(),
'archivedAt': exercise.archivedAt?.toUtc().toIso8601String(), 'archivedAt': exercise.archivedAt?.toUtc().toIso8601String(),
}; };
@ -4102,6 +4109,7 @@ Map<String, Object?> _programPayload(domain.Program program) => {
'name': program.name, 'name': program.name,
'defaultRestSeconds': program.defaultRestSeconds, 'defaultRestSeconds': program.defaultRestSeconds,
'isExample': program.isExample, 'isExample': program.isExample,
'tags': program.tags,
'exercises': program.exercises 'exercises': program.exercises
.map((exercise) => exercise.toSnapshotJson()) .map((exercise) => exercise.toSnapshotJson())
.toList(), .toList(),
@ -4114,6 +4122,7 @@ Map<String, Object?> _workoutTemplatePayload(domain.WorkoutTemplate template) =>
'name': template.name, 'name': template.name,
'lastStartedAt': template.lastStartedAt?.toUtc().toIso8601String(), 'lastStartedAt': template.lastStartedAt?.toUtc().toIso8601String(),
'isExample': template.isExample, 'isExample': template.isExample,
'tags': template.tags,
'programs': template.programs 'programs': template.programs
.map( .map(
(program) => { (program) => {
@ -4183,6 +4192,7 @@ domain.Exercise _exerciseFromPayload(RemoteSyncedItem item) {
payload['category'] as String? ?? 'uncategorized', payload['category'] as String? ?? 'uncategorized',
), ),
isExample: payload['isExample'] as bool? ?? false, isExample: payload['isExample'] as bool? ?? false,
tags: _stringListFromPayload(payload['tags']),
steps: _stepsFromPayload(payload['steps']), steps: _stepsFromPayload(payload['steps']),
archivedAt: _dateTimeFromPayload(payload['archivedAt']), archivedAt: _dateTimeFromPayload(payload['archivedAt']),
); );
@ -4213,6 +4223,7 @@ domain.Program _programFromPayload(RemoteSyncedItem item) {
name: _stringFromPayload(payload, 'name', item.clientId), name: _stringFromPayload(payload, 'name', item.clientId),
defaultRestSeconds: payload['defaultRestSeconds'] as int? ?? 0, defaultRestSeconds: payload['defaultRestSeconds'] as int? ?? 0,
isExample: payload['isExample'] as bool? ?? false, isExample: payload['isExample'] as bool? ?? false,
tags: _stringListFromPayload(payload['tags']),
exercises: _programExercisesFromPayload(payload['exercises'], metadata), exercises: _programExercisesFromPayload(payload['exercises'], metadata),
); );
} }
@ -4225,6 +4236,7 @@ domain.WorkoutTemplate _workoutTemplateFromPayload(RemoteSyncedItem item) {
name: _stringFromPayload(payload, 'name', item.clientId), name: _stringFromPayload(payload, 'name', item.clientId),
lastStartedAt: _dateTimeFromPayload(payload['lastStartedAt']), lastStartedAt: _dateTimeFromPayload(payload['lastStartedAt']),
isExample: payload['isExample'] as bool? ?? false, isExample: payload['isExample'] as bool? ?? false,
tags: _stringListFromPayload(payload['tags']),
programs: _workoutTemplateProgramsFromPayload( programs: _workoutTemplateProgramsFromPayload(
payload['programs'], payload['programs'],
metadata, metadata,
@ -4422,6 +4434,23 @@ List<String> _stringListFromPayload(Object? value) {
return value.whereType<String>().toList(growable: false); return value.whereType<String>().toList(growable: false);
} }
String _encodeTags(List<String> tags) => jsonEncode(domain.normalizeTags(tags));
List<String> _decodeTags(String? encoded) {
if (encoded == null || encoded.trim().isEmpty) {
return const [];
}
try {
final decoded = jsonDecode(encoded);
if (decoded is! List) {
return const [];
}
return domain.normalizeTags(decoded.whereType<String>());
} on Object {
return const [];
}
}
List<domain.ExerciseStep> _stepsFromPayload(Object? value) { List<domain.ExerciseStep> _stepsFromPayload(Object? value) {
if (value is! List) { if (value is! List) {
return const []; return const [];

View File

@ -196,6 +196,7 @@ class Exercises extends SyncableTable {
TextColumn get category => TextColumn get category =>
text().withDefault(const Constant('uncategorized'))(); text().withDefault(const Constant('uncategorized'))();
BoolColumn get isExample => boolean().withDefault(const Constant(false))(); BoolColumn get isExample => boolean().withDefault(const Constant(false))();
TextColumn get tagsJson => text().withDefault(const Constant('[]'))();
DateTimeColumn get archivedAt => dateTime().nullable()(); DateTimeColumn get archivedAt => dateTime().nullable()();
@override @override
@ -214,6 +215,7 @@ class Exercises extends SyncableTable {
'default_target_score_time_ms > 0)', 'default_target_score_time_ms > 0)',
"CHECK (category IN ('shoot', 'freeThrows', 'dribble', 'finishing', " "CHECK (category IN ('shoot', 'freeThrows', 'dribble', 'finishing', "
"'conditioning', 'defense', 'mobility', 'uncategorized'))", "'conditioning', 'defense', 'mobility', 'uncategorized'))",
'CHECK (json_valid(tags_json))',
]; ];
} }
@ -284,9 +286,13 @@ class Programs extends SyncableTable {
TextColumn get name => text().withLength(min: 1)(); TextColumn get name => text().withLength(min: 1)();
IntColumn get defaultRestSeconds => integer()(); IntColumn get defaultRestSeconds => integer()();
BoolColumn get isExample => boolean().withDefault(const Constant(false))(); BoolColumn get isExample => boolean().withDefault(const Constant(false))();
TextColumn get tagsJson => text().withDefault(const Constant('[]'))();
@override @override
List<String> get customConstraints => ['CHECK (default_rest_seconds >= 0)']; List<String> get customConstraints => [
'CHECK (default_rest_seconds >= 0)',
'CHECK (json_valid(tags_json))',
];
} }
class ProgramExercises extends SyncableTable { class ProgramExercises extends SyncableTable {
@ -367,6 +373,10 @@ class WorkoutTemplates extends SyncableTable {
TextColumn get name => text().withLength(min: 1)(); TextColumn get name => text().withLength(min: 1)();
DateTimeColumn get lastStartedAt => dateTime().nullable()(); DateTimeColumn get lastStartedAt => dateTime().nullable()();
BoolColumn get isExample => boolean().withDefault(const Constant(false))(); BoolColumn get isExample => boolean().withDefault(const Constant(false))();
TextColumn get tagsJson => text().withDefault(const Constant('[]'))();
@override
List<String> get customConstraints => ['CHECK (json_valid(tags_json))'];
} }
class LocalSeedMetadata extends Table { class LocalSeedMetadata extends Table {

View File

@ -2256,6 +2256,221 @@ void main() {
); );
}); });
test('Tags are normalized and validated on taggable entities', () {
final exercise = Exercise(
metadata: _metadata('exercise-tags'),
name: 'Shoot',
hasTimeMeasure: false,
hasRepsMeasure: true,
hasScoreMeasure: false,
tags: const [' Match Prep ', 'INTENSE'],
);
expect(exercise.tags, ['match prep', 'intense']);
expect(
() => Program(
metadata: _metadata('program-tags'),
name: 'Program',
defaultRestSeconds: 30,
tags: const ['match', 'MATCH'],
),
throwsA(isA<DomainException>()),
);
expect(
() => WorkoutTemplate(
metadata: _metadata('template-tags'),
name: 'Template',
tags: List.generate(9, (index) => 'tag$index'),
),
throwsA(isA<DomainException>()),
);
expect(
() => Exercise(
metadata: _metadata('exercise-long-tag'),
name: 'Shoot',
hasTimeMeasure: false,
hasRepsMeasure: true,
hasScoreMeasure: false,
tags: const ['tag beaucoup trop long pour le mvp'],
),
throwsA(isA<DomainException>()),
);
});
test('Tag helpers filter by all selected tags and sort suggestions', () {
final programs = [
Program(
metadata: _metadata('program-1'),
name: 'A',
defaultRestSeconds: 30,
tags: const ['match', 'intense'],
),
Program(
metadata: _metadata('program-2'),
name: 'B',
defaultRestSeconds: 30,
tags: const ['match', 'extérieur'],
),
Program(
metadata: _metadata('program-3'),
name: 'C',
defaultRestSeconds: 30,
tags: const ['intense'],
),
];
final filtered = filterByRequiredTags(programs, {
' MATCH ',
'intense',
}, (program) => program.tags);
final suggestions = tagSuggestionsFor(programs, (program) => program.tags);
expect(filtered.map((program) => program.metadata.id), ['program-1']);
expect(suggestions.map((usage) => '${usage.tag}:${usage.count}'), [
'intense:2',
'match:2',
'extérieur:1',
]);
});
test('Program use case duplicates deeply with copy name conflicts', () async {
final programRepository = _FakeProgramRepository();
final templateRepository = _FakeWorkoutTemplateRepository();
final source = Program(
metadata: _metadata('program-source'),
name: 'Programme tirs',
defaultRestSeconds: 45,
isExample: true,
tags: const ['match', 'extérieur'],
exercises: [
_programExercise(
id: 'program-exercise-source',
programId: 'program-source',
sourceExerciseId: 'exercise-source',
position: 0,
),
],
);
programRepository.programs.addAll([
source,
Program(
metadata: _metadata('program-copy-1'),
name: 'Copie de Programme tirs',
defaultRestSeconds: 45,
),
]);
final useCase = ProgramUseCases(
programRepository: programRepository,
exerciseRepository: _FakeExerciseRepository(),
templateRepository: templateRepository,
clock: _FakeClock(DateTime.utc(2026, 7, 22, 12)),
ids: _FakeIds(),
originDeviceId: 'device-1',
);
final copy = await useCase.duplicate('program-source');
expect(copy.metadata.id, isNot('program-source'));
expect(copy.name, 'Copie 2 de Programme tirs');
expect(copy.isExample, isFalse);
expect(copy.tags, source.tags);
expect(copy.exercises.single.metadata.id, isNot('program-exercise-source'));
expect(copy.exercises.single.programId, copy.metadata.id);
expect(copy.exercises.single.sourceExerciseId, 'exercise-source');
expect(copy.exercises.single.targetReps, 10);
expect(
programRepository.programs
.singleWhere((program) => program.metadata.id == 'program-source')
.exercises
.single
.metadata
.id,
'program-exercise-source',
);
});
test(
'Workout template use case duplicates deeply and remaps overrides',
() async {
final templateRepository = _FakeWorkoutTemplateRepository();
final source = WorkoutTemplate(
metadata: _metadata('template-source'),
name: 'Prépa match',
lastStartedAt: DateTime.utc(2026, 7, 20, 12),
isExample: true,
tags: const ['match', 'intense'],
programs: [
_templateProgram(
id: 'template-program-source',
workoutTemplateId: 'template-source',
sourceProgramId: 'program-source',
position: 0,
),
],
overrides: [
WorkoutTemplateExerciseOverride(
metadata: _metadata('override-source'),
workoutTemplateProgramId: 'template-program-source',
snapshotProgramExerciseId: 'program-exercise-source',
setsCountOverride: 4,
targetRepsOverride: 12,
autoStartNextTimedStepOverride: false,
),
],
);
templateRepository.templates.addAll([
source,
WorkoutTemplate(
metadata: _metadata('template-copy-1'),
name: 'Copie de Prépa match',
),
]);
final useCase = WorkoutTemplateUseCases(
templateRepository: templateRepository,
programRepository: _FakeProgramRepository(),
clock: _FakeClock(DateTime.utc(2026, 7, 22, 12)),
ids: _FakeIds(),
originDeviceId: 'device-1',
);
final copy = await useCase.duplicate('template-source');
expect(copy.metadata.id, isNot('template-source'));
expect(copy.name, 'Copie 2 de Prépa match');
expect(copy.lastStartedAt, isNull);
expect(copy.isExample, isFalse);
expect(copy.tags, source.tags);
expect(
copy.programs.single.metadata.id,
isNot('template-program-source'),
);
expect(copy.programs.single.workoutTemplateId, copy.metadata.id);
expect(copy.overrides.single.metadata.id, isNot('override-source'));
expect(
copy.overrides.single.workoutTemplateProgramId,
copy.programs.single.metadata.id,
);
expect(
templateRepository.templates
.singleWhere(
(template) => template.metadata.id == 'template-source',
)
.programs
.single
.metadata
.id,
'template-program-source',
);
expect(
copy.overrides.single.snapshotProgramExerciseId,
'program-exercise-source',
);
expect(copy.overrides.single.setsCountOverride, 4);
expect(copy.overrides.single.targetRepsOverride, 12);
expect(copy.overrides.single.autoStartNextTimedStepOverride, isFalse);
},
);
test('Progression stats use case resolves four week overview', () async { test('Progression stats use case resolves four week overview', () async {
final repository = _FakeProgressionStatsRepository(); final repository = _FakeProgressionStatsRepository();
final now = DateTime.utc(2026, 7, 22, 12); final now = DateTime.utc(2026, 7, 22, 12);

View File

@ -15,6 +15,7 @@ void main() {
late local.DriftWorkoutTemplateRepository templateRepository; late local.DriftWorkoutTemplateRepository templateRepository;
late local.DriftWorkoutHistoryRepository historyRepository; late local.DriftWorkoutHistoryRepository historyRepository;
late local.DriftProgressionStatsRepository progressionStatsRepository; late local.DriftProgressionStatsRepository progressionStatsRepository;
late local.DriftLocalSyncChangeRepository syncChangeRepository;
late local.DriftExercisePerformanceReferenceRepository late local.DriftExercisePerformanceReferenceRepository
performanceReferenceRepository; performanceReferenceRepository;
@ -28,6 +29,7 @@ void main() {
progressionStatsRepository = local.DriftProgressionStatsRepository( progressionStatsRepository = local.DriftProgressionStatsRepository(
database, database,
); );
syncChangeRepository = local.DriftLocalSyncChangeRepository(database);
performanceReferenceRepository = performanceReferenceRepository =
local.DriftExercisePerformanceReferenceRepository(database); local.DriftExercisePerformanceReferenceRepository(database);
}); });
@ -106,6 +108,290 @@ void main() {
expect(restored!.defaultTargetScore, 0); expect(restored!.defaultTargetScore, 0);
}); });
test('taggable tables expose tags json columns on fresh schema', () async {
Future<List<String>> columnNames(String tableName) async {
final rows = await database
.customSelect('PRAGMA table_info($tableName)')
.get();
return rows.map((row) => row.data['name'] as String).toList();
}
expect(await columnNames('exercises'), contains('tags_json'));
expect(await columnNames('programs'), contains('tags_json'));
expect(await columnNames('workout_templates'), contains('tags_json'));
expect(database.schemaVersion, 19);
});
test('repositories save and load normalized tags', () async {
final now = DateTime.utc(2026, 7, 22, 10);
await exerciseRepository.save(
Exercise(
metadata: _metadata('exercise-tags', now),
name: 'Shoot',
hasTimeMeasure: false,
hasRepsMeasure: true,
hasScoreMeasure: false,
tags: const [' Match ', 'Extérieur'],
),
);
await programRepository.save(
Program(
metadata: _metadata('program-tags', now),
name: 'Program',
defaultRestSeconds: 45,
tags: const ['Intense'],
),
);
await templateRepository.save(
WorkoutTemplate(
metadata: _metadata('template-tags', now),
name: 'Template',
tags: const ['Routine'],
),
);
final exercise = await exerciseRepository.findById('exercise-tags');
final program = await programRepository.findById('program-tags');
final template = await templateRepository.findById('template-tags');
expect(exercise!.tags, ['match', 'extérieur']);
expect(program!.tags, ['intense']);
expect(template!.tags, ['routine']);
});
test('local sync payload includes tags for taggable resources', () async {
final now = DateTime.utc(2026, 7, 22, 10, 30);
await exerciseRepository.save(
Exercise(
metadata: _metadata('exercise-sync-tags', now),
name: 'Shoot',
hasTimeMeasure: false,
hasRepsMeasure: true,
hasScoreMeasure: false,
tags: const ['match'],
),
);
await programRepository.save(
Program(
metadata: _metadata('program-sync-tags', now),
name: 'Program',
defaultRestSeconds: 45,
tags: const ['intense'],
),
);
await templateRepository.save(
WorkoutTemplate(
metadata: _metadata('template-sync-tags', now),
name: 'Template',
tags: const ['routine'],
),
);
final changes = await syncChangeRepository.listPendingChanges();
final payloadsById = {
for (final change in changes) change.item.clientId: change.item.payload,
};
expect(payloadsById['exercise-sync-tags']!['tags'], ['match']);
expect(payloadsById['program-sync-tags']!['tags'], ['intense']);
expect(payloadsById['template-sync-tags']!['tags'], ['routine']);
});
test('local sync pull defaults missing tags to empty lists', () async {
final now = DateTime.utc(2026, 7, 22, 11);
await syncChangeRepository.applyRemoteItem(
RemoteSyncedItem(
resourceType: SyncResourceType.exercise,
clientId: 'remote-exercise-no-tags',
serverId: 'server-exercise-no-tags',
schemaVersion: 1,
clientUpdatedAt: now,
serverUpdatedAt: now,
deletedAt: null,
payload: const {
'id': 'remote-exercise-no-tags',
'name': 'Remote exercise',
'hasTimeMeasure': false,
'hasRepsMeasure': true,
'hasScoreMeasure': false,
},
),
);
await syncChangeRepository.applyRemoteItem(
RemoteSyncedItem(
resourceType: SyncResourceType.program,
clientId: 'remote-program-no-tags',
serverId: 'server-program-no-tags',
schemaVersion: 1,
clientUpdatedAt: now,
serverUpdatedAt: now,
deletedAt: null,
payload: const {
'id': 'remote-program-no-tags',
'name': 'Remote program',
'defaultRestSeconds': 30,
},
),
);
await syncChangeRepository.applyRemoteItem(
RemoteSyncedItem(
resourceType: SyncResourceType.workoutTemplate,
clientId: 'remote-template-no-tags',
serverId: 'server-template-no-tags',
schemaVersion: 1,
clientUpdatedAt: now,
serverUpdatedAt: now,
deletedAt: null,
payload: const {
'id': 'remote-template-no-tags',
'name': 'Remote template',
},
),
);
final exercise = await exerciseRepository.findById(
'remote-exercise-no-tags',
);
final program = await programRepository.findById('remote-program-no-tags');
final template = await templateRepository.findById(
'remote-template-no-tags',
);
expect(exercise!.tags, isEmpty);
expect(program!.tags, isEmpty);
expect(template!.tags, isEmpty);
});
test('program duplication persists copied children through Drift', () async {
final now = DateTime.utc(2026, 7, 22, 11, 15);
await exerciseRepository.save(
Exercise(
metadata: _metadata('exercise-dup-source', now),
name: 'Shoot',
hasTimeMeasure: false,
hasRepsMeasure: true,
hasScoreMeasure: false,
defaultTargetReps: 10,
),
);
await programRepository.replaceExercises(
Program(
metadata: _metadata('program-dup-source', now),
name: 'Programme tirs',
defaultRestSeconds: 30,
isExample: true,
tags: const ['match'],
exercises: [
_programExercise(
'program-exercise-dup-source',
now,
programId: 'program-dup-source',
position: 0,
),
],
),
now,
);
final useCase = ProgramUseCases(
programRepository: programRepository,
exerciseRepository: exerciseRepository,
templateRepository: templateRepository,
clock: _FakeClock(now.add(const Duration(minutes: 1))),
ids: _FakeIds(),
originDeviceId: 'device-1',
);
final copy = await useCase.duplicate('program-dup-source');
final restored = await programRepository.findById(copy.metadata.id);
expect(restored, isNotNull);
expect(restored!.name, 'Copie de Programme tirs');
expect(restored.isExample, isFalse);
expect(restored.tags, ['match']);
expect(
restored.exercises.single.metadata.id,
isNot('program-exercise-dup-source'),
);
expect(restored.exercises.single.programId, restored.metadata.id);
expect(restored.exercises.single.exerciseNameSnapshot, 'Exercise 0');
});
test(
'workout template duplication persists remapped overrides through Drift',
() async {
final now = DateTime.utc(2026, 7, 22, 11, 30);
await programRepository.save(
Program(
metadata: _metadata('program-template-source', now),
name: 'Program source',
defaultRestSeconds: 30,
),
);
await templateRepository.replaceComposition(
WorkoutTemplate(
metadata: _metadata('template-dup-source', now),
name: 'Prépa match',
lastStartedAt: now,
isExample: true,
tags: const ['intense'],
programs: [
WorkoutTemplateProgram(
metadata: _metadata('template-program-dup-source', now),
workoutTemplateId: 'template-dup-source',
sourceProgramId: 'program-template-source',
position: 0,
programNameSnapshot: 'Program source',
defaultRestSecondsSnapshot: 30,
programSnapshotJson: '{"exercises":[]}',
),
],
overrides: [
WorkoutTemplateExerciseOverride(
metadata: _metadata('template-override-dup-source', now),
workoutTemplateProgramId: 'template-program-dup-source',
snapshotProgramExerciseId: 'snapshot-exercise',
setsCountOverride: 4,
),
],
),
now,
);
final useCase = WorkoutTemplateUseCases(
templateRepository: templateRepository,
programRepository: programRepository,
clock: _FakeClock(now.add(const Duration(minutes: 1))),
ids: _FakeIds(),
originDeviceId: 'device-1',
);
final copy = await useCase.duplicate('template-dup-source');
final restored = await templateRepository.findById(copy.metadata.id);
expect(restored, isNotNull);
expect(restored!.name, 'Copie de Prépa match');
expect(restored.lastStartedAt, isNull);
expect(restored.isExample, isFalse);
expect(restored.tags, ['intense']);
expect(
restored.programs.single.metadata.id,
isNot('template-program-dup-source'),
);
expect(restored.programs.single.workoutTemplateId, restored.metadata.id);
expect(
restored.overrides.single.metadata.id,
isNot('template-override-dup-source'),
);
expect(
restored.overrides.single.workoutTemplateProgramId,
restored.programs.single.metadata.id,
);
expect(
restored.overrides.single.snapshotProgramExerciseId,
'snapshot-exercise',
);
},
);
test('starter seed populates a fresh database once', () async { test('starter seed populates a fresh database once', () async {
final seedRepository = local.DriftStarterSeedRepository(database); final seedRepository = local.DriftStarterSeedRepository(database);
final result = await SeedStarterContentUseCase( final result = await SeedStarterContentUseCase(