import 'package:drift/drift.dart'; import '../../application/application.dart'; import '../../domain/domain.dart' as domain; import 'app_database.dart' as db; final class DriftExerciseRepository implements ExerciseRepository { const DriftExerciseRepository(this.database); final db.AppDatabase database; @override Future findById(String id) async { final row = await (database.select( database.exercises, )..where((table) => table.id.equals(id))).getSingleOrNull(); return row == null ? null : _exerciseFromRow(row); } @override Future> listActive() async { final rows = await (database.select(database.exercises) ..where( (table) => table.deletedAt.isNull() & table.archivedAt.isNull(), ) ..orderBy([(table) => OrderingTerm.asc(table.name)])) .get(); return rows.map(_exerciseFromRow).toList(); } @override Future isReferencedByProgram(String id) async { final row = await (database.select(database.programExercises) ..where( (table) => table.deletedAt.isNull() & table.sourceExerciseId.equals(id), ) ..limit(1)) .getSingleOrNull(); return row != null; } @override Future save(domain.Exercise exercise) async { await _upsertWithChangeLog( database: database, tableName: 'exercises', entityType: 'Exercise', metadata: exercise.metadata, write: () => database .into(database.exercises) .insertOnConflictUpdate(_exerciseCompanion(exercise)), ); } } final class DriftMediaAssetRepository implements MediaAssetRepository { const DriftMediaAssetRepository(this.database); final db.AppDatabase database; @override Future findById(String id) async { final row = await (database.select( database.mediaAssets, )..where((table) => table.id.equals(id))).getSingleOrNull(); return row == null ? null : _mediaAssetFromRow(row); } @override Future> listActive() async { final rows = await (database.select( database.mediaAssets, )..where((table) => table.deletedAt.isNull())).get(); return rows.map(_mediaAssetFromRow).toList(); } @override Future save(domain.MediaAsset mediaAsset) async { await _upsertWithChangeLog( database: database, tableName: 'media_assets', entityType: 'MediaAsset', metadata: mediaAsset.metadata, write: () => database .into(database.mediaAssets) .insertOnConflictUpdate(_mediaAssetCompanion(mediaAsset)), ); } } final class DriftProgramRepository implements ProgramRepository { const DriftProgramRepository(this.database); final db.AppDatabase database; @override Future findById(String id) async { final query = database.select(database.programs) ..where((table) => table.id.equals(id) & table.deletedAt.isNull()); final row = await query.getSingleOrNull(); if (row == null) { return null; } final exercises = await (database.select(database.programExercises) ..where( (table) => table.programId.equals(id) & table.deletedAt.isNull(), ) ..orderBy([(table) => OrderingTerm.asc(table.position)])) .get(); return _programFromRow( row, exercises.map(_programExerciseFromRow).toList(), ); } @override Future> listActive() async { final rows = await (database.select(database.programs) ..where((table) => table.deletedAt.isNull()) ..orderBy([(table) => OrderingTerm.asc(table.name)])) .get(); final programs = []; for (final row in rows) { final exercises = await (database.select(database.programExercises) ..where( (table) => table.programId.equals(row.id) & table.deletedAt.isNull(), ) ..orderBy([(table) => OrderingTerm.asc(table.position)])) .get(); programs.add( _programFromRow(row, exercises.map(_programExerciseFromRow).toList()), ); } return programs; } @override Future save(domain.Program program) async { await database.transaction(() async { await _upsertWithChangeLog( database: database, tableName: 'programs', entityType: 'Program', metadata: program.metadata, write: () => database .into(database.programs) .insertOnConflictUpdate(_programCompanion(program)), ); for (final exercise in program.exercises) { await saveExercise(exercise); } }); } @override Future saveExercise(domain.ProgramExercise exercise) async { await _upsertWithChangeLog( database: database, tableName: 'program_exercises', entityType: 'ProgramExercise', metadata: exercise.metadata, write: () => database .into(database.programExercises) .insertOnConflictUpdate(_programExerciseCompanion(exercise)), ); } @override Future replaceExercises( domain.Program program, DateTime deletedAt, ) async { await database.transaction(() async { await save(program); final activeIds = program.exercises .map((exercise) => exercise.metadata.id) .toSet(); final staleQuery = database.select(database.programExercises); if (activeIds.isEmpty) { staleQuery.where( (table) => table.programId.equals(program.metadata.id) & table.deletedAt.isNull(), ); } else { staleQuery.where( (table) => table.programId.equals(program.metadata.id) & table.deletedAt.isNull() & table.id.isNotIn(activeIds), ); } await _softDeleteProgramExerciseRows( database, await staleQuery.get(), deletedAt, ); }); } } final class DriftWorkoutTemplateRepository implements WorkoutTemplateRepository { const DriftWorkoutTemplateRepository(this.database); final db.AppDatabase database; @override Future findById(String id) async { final row = await (database.select( database.workoutTemplates, )..where((table) => table.id.equals(id) & table.deletedAt.isNull())) .getSingleOrNull(); if (row == null) { return null; } final programs = await (database.select(database.workoutTemplatePrograms) ..where( (table) => table.workoutTemplateId.equals(id) & table.deletedAt.isNull(), ) ..orderBy([(table) => OrderingTerm.asc(table.position)])) .get(); final programIds = programs.map((program) => program.id).toSet(); final overrides = programIds.isEmpty ? [] : await (database.select(database.workoutTemplateExerciseOverrides) ..where( (table) => table.workoutTemplateProgramId.isIn(programIds) & table.deletedAt.isNull(), )) .get(); return _workoutTemplateFromRow( row, programs.map(_workoutTemplateProgramFromRow).toList(), overrides.map(_workoutTemplateExerciseOverrideFromRow).toList(), ); } @override Future> listActive() async { final rows = await (database.select(database.workoutTemplates) ..where((table) => table.deletedAt.isNull()) ..orderBy([(table) => OrderingTerm.asc(table.name)])) .get(); final templates = []; for (final row in rows) { final template = await findById(row.id); if (template != null) { templates.add(template); } } return templates; } @override Future save(domain.WorkoutTemplate template) async { await database.transaction(() async { await _upsertWithChangeLog( database: database, tableName: 'workout_templates', entityType: 'WorkoutTemplate', metadata: template.metadata, write: () => database .into(database.workoutTemplates) .insertOnConflictUpdate(_workoutTemplateCompanion(template)), ); for (final program in template.programs) { await saveProgram(program); } for (final override in template.overrides) { await saveOverride(override); } }); } @override Future saveProgram(domain.WorkoutTemplateProgram program) async { await _upsertWithChangeLog( database: database, tableName: 'workout_template_programs', entityType: 'WorkoutTemplateProgram', metadata: program.metadata, write: () => database .into(database.workoutTemplatePrograms) .insertOnConflictUpdate(_workoutTemplateProgramCompanion(program)), ); } @override Future saveOverride( domain.WorkoutTemplateExerciseOverride override, ) async { await _upsertWithChangeLog( database: database, tableName: 'workout_template_exercise_overrides', entityType: 'WorkoutTemplateExerciseOverride', metadata: override.metadata, write: () => database .into(database.workoutTemplateExerciseOverrides) .insertOnConflictUpdate( _workoutTemplateExerciseOverrideCompanion(override), ), ); } @override Future replaceComposition( domain.WorkoutTemplate template, DateTime deletedAt, ) async { await database.transaction(() async { final previousPrograms = await (database.select(database.workoutTemplatePrograms)..where( (table) => table.workoutTemplateId.equals(template.metadata.id) & table.deletedAt.isNull(), )) .get(); final previousProgramIds = previousPrograms .map((program) => program.id) .toSet(); await save(template); final activeProgramIds = template.programs .map((program) => program.metadata.id) .toSet(); final staleProgramsQuery = database.select( database.workoutTemplatePrograms, ); if (activeProgramIds.isEmpty) { staleProgramsQuery.where( (table) => table.workoutTemplateId.equals(template.metadata.id) & table.deletedAt.isNull(), ); } else { staleProgramsQuery.where( (table) => table.workoutTemplateId.equals(template.metadata.id) & table.deletedAt.isNull() & table.id.isNotIn(activeProgramIds), ); } await _softDeleteWorkoutTemplateProgramRows( database, await staleProgramsQuery.get(), deletedAt, ); final activeOverrideIds = template.overrides .map((override) => override.metadata.id) .toSet(); final relevantProgramIds = {...previousProgramIds, ...activeProgramIds}; if (relevantProgramIds.isEmpty) { return; } final staleOverridesQuery = database.select( database.workoutTemplateExerciseOverrides, ); if (activeOverrideIds.isEmpty) { staleOverridesQuery.where( (table) => table.workoutTemplateProgramId.isIn(relevantProgramIds) & table.deletedAt.isNull(), ); } else { staleOverridesQuery.where( (table) => table.workoutTemplateProgramId.isIn(relevantProgramIds) & table.deletedAt.isNull() & table.id.isNotIn(activeOverrideIds), ); } await _softDeleteWorkoutTemplateExerciseOverrideRows( database, await staleOverridesQuery.get(), deletedAt, ); }); } } final class DriftActiveSessionRepository implements ActiveSessionRepository { const DriftActiveSessionRepository(this.database); final db.AppDatabase database; @override Future findById(String id) async { final row = await (database.select( database.activeWorkoutSessions, )..where((table) => table.id.equals(id))).getSingleOrNull(); return row == null ? null : _activeWorkoutSessionFromRow(row); } @override Future findOpen() async { final row = await (database.select(database.activeWorkoutSessions) ..where( (table) => table.deletedAt.isNull() & table.status.isIn(['running', 'paused', 'savedExit']), ) ..orderBy([(table) => OrderingTerm.desc(table.updatedAt)]) ..limit(1)) .getSingleOrNull(); return row == null ? null : _activeWorkoutSessionFromRow(row); } @override Future save(domain.ActiveWorkoutSession session) async { await _upsertWithChangeLog( database: database, tableName: 'active_workout_sessions', entityType: 'ActiveWorkoutSession', metadata: session.metadata, write: () => database .into(database.activeWorkoutSessions) .insertOnConflictUpdate(_activeWorkoutSessionCompanion(session)), ); } @override Future saveSetResult(domain.ActiveSetResult result) async { await _upsertWithChangeLog( database: database, tableName: 'active_set_results', entityType: 'ActiveSetResult', metadata: result.metadata, write: () => database .into(database.activeSetResults) .insertOnConflictUpdate(_activeSetResultCompanion(result)), ); } @override Future saveRestState(domain.ActiveRestState restState) async { await _upsertWithChangeLog( database: database, tableName: 'active_rest_states', entityType: 'ActiveRestState', metadata: restState.metadata, write: () => database .into(database.activeRestStates) .insertOnConflictUpdate(_activeRestStateCompanion(restState)), ); } @override Future findRestStateById(String id) async { final row = await (database.select( database.activeRestStates, )..where((table) => table.id.equals(id))).getSingleOrNull(); return row == null ? null : _activeRestStateFromRow(row); } @override Future> listSetResults(String sessionId) async { final rows = await (database.select(database.activeSetResults) ..where((table) => table.activeWorkoutSessionId.equals(sessionId)) ..orderBy([ (table) => OrderingTerm.asc(table.programIndex), (table) => OrderingTerm.asc(table.exerciseIndex), (table) => OrderingTerm.asc(table.setIndex), ])) .get(); return rows.map(_activeSetResultFromRow).toList(); } @override Future> listRestStates(String sessionId) async { final rows = await (database.select(database.activeRestStates) ..where((table) => table.activeWorkoutSessionId.equals(sessionId)) ..orderBy([(table) => OrderingTerm.asc(table.startedAt)])) .get(); return rows.map(_activeRestStateFromRow).toList(); } } final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository { const DriftWorkoutHistoryRepository(this.database); final db.AppDatabase database; @override Future findById(String id) async { final row = await (database.select( database.workoutHistories, )..where((table) => table.id.equals(id) & table.deletedAt.isNull())) .getSingleOrNull(); if (row == null) { return null; } final results = await (database.select(database.workoutHistorySetResults) ..where( (table) => table.workoutHistoryId.equals(id) & table.deletedAt.isNull(), ) ..orderBy([ (table) => OrderingTerm.asc(table.programIndex), (table) => OrderingTerm.asc(table.exerciseIndex), (table) => OrderingTerm.asc(table.setIndex), ])) .get(); return _workoutHistoryFromRow( row, results.map(_workoutHistorySetResultFromRow).toList(), ); } @override Future> listActive() async { final rows = await (database.select(database.workoutHistories) ..where((table) => table.deletedAt.isNull()) ..orderBy([(table) => OrderingTerm.desc(table.startedAt)])) .get(); final histories = []; for (final row in rows) { final history = await findById(row.id); if (history != null) { histories.add(history); } } return histories; } @override Future save(domain.WorkoutHistory history) async { await database.transaction(() async { await _upsertWithChangeLog( database: database, tableName: 'workout_history', entityType: 'WorkoutHistory', metadata: history.metadata, write: () => database .into(database.workoutHistories) .insertOnConflictUpdate(_workoutHistoryCompanion(history)), ); for (final result in history.results) { await saveSetResult(result); } }); } @override Future saveSetResult(domain.WorkoutHistorySetResult result) async { await _upsertWithChangeLog( database: database, tableName: 'workout_history_set_results', entityType: 'WorkoutHistorySetResult', metadata: result.metadata, write: () => database .into(database.workoutHistorySetResults) .insertOnConflictUpdate(_workoutHistorySetResultCompanion(result)), ); } @override Future delete(String id, DateTime deletedAt) async { await database.transaction(() async { final history = await (database.select( database.workoutHistories, )..where((table) => table.id.equals(id))).getSingle(); final revision = history.localRevision + 1; await (database.update( database.workoutHistories, )..where((table) => table.id.equals(id))).write( db.WorkoutHistoriesCompanion( deletedAt: Value(deletedAt.toUtc()), updatedAt: Value(deletedAt.toUtc()), localRevision: Value(revision), syncState: const Value('deleted'), ), ); await _writeChangeLog( database: database, entityType: 'WorkoutHistory', entityId: id, operation: 'softDelete', localRevision: revision, originDeviceId: history.originDeviceId, createdAt: deletedAt, ); final resultRows = await (database.select(database.workoutHistorySetResults)..where( (table) => table.workoutHistoryId.equals(id) & table.deletedAt.isNull(), )) .get(); await _softDeleteWorkoutHistorySetResultRows( database, resultRows, deletedAt, ); }); } } Future _upsertWithChangeLog({ required db.AppDatabase database, required String tableName, required String entityType, required domain.EntityMetadata metadata, required Future Function() write, }) async { await database.transaction(() async { final operation = await _operationForMutation( database: database, tableName: tableName, entityId: metadata.id, deletedAt: metadata.deletedAt, ); await write(); await _writeChangeLog( database: database, entityType: entityType, entityId: metadata.id, operation: operation, localRevision: metadata.localRevision, originDeviceId: metadata.originDeviceId, createdAt: metadata.updatedAt, ); }); } Future _operationForMutation({ required db.AppDatabase database, required String tableName, required String entityId, required DateTime? deletedAt, }) async { final existing = await database .customSelect( 'SELECT deleted_at FROM $tableName WHERE id = ? LIMIT 1', variables: [Variable(entityId)], ) .getSingleOrNull(); if (existing == null) { return 'insert'; } final wasDeleted = existing.data['deleted_at'] != null; if (deletedAt != null) { return 'softDelete'; } if (wasDeleted) { return 'restore'; } return 'update'; } Future _writeChangeLog({ required db.AppDatabase database, required String entityType, required String entityId, required String operation, required int localRevision, required String originDeviceId, required DateTime createdAt, }) async { final id = [ entityType, entityId, operation, localRevision, createdAt.microsecondsSinceEpoch, ].join(':'); await database .into(database.changeLogEntries) .insertOnConflictUpdate( db.ChangeLogEntriesCompanion.insert( id: id, entityType: entityType, entityId: entityId, operation: operation, localRevision: localRevision, originDeviceId: originDeviceId, createdAt: createdAt.toUtc(), ), ); } Future _softDeleteProgramExerciseRows( db.AppDatabase database, List rows, DateTime deletedAt, ) async { for (final row in rows) { final revision = row.localRevision + 1; await (database.update( database.programExercises, )..where((table) => table.id.equals(row.id))).write( db.ProgramExercisesCompanion( deletedAt: Value(deletedAt.toUtc()), updatedAt: Value(deletedAt.toUtc()), localRevision: Value(revision), syncState: const Value('deleted'), ), ); await _writeChangeLog( database: database, entityType: 'ProgramExercise', entityId: row.id, operation: 'softDelete', localRevision: revision, originDeviceId: row.originDeviceId, createdAt: deletedAt, ); } } Future _softDeleteWorkoutTemplateProgramRows( db.AppDatabase database, List rows, DateTime deletedAt, ) async { for (final row in rows) { final revision = row.localRevision + 1; await (database.update( database.workoutTemplatePrograms, )..where((table) => table.id.equals(row.id))).write( db.WorkoutTemplateProgramsCompanion( deletedAt: Value(deletedAt.toUtc()), updatedAt: Value(deletedAt.toUtc()), localRevision: Value(revision), syncState: const Value('deleted'), ), ); await _writeChangeLog( database: database, entityType: 'WorkoutTemplateProgram', entityId: row.id, operation: 'softDelete', localRevision: revision, originDeviceId: row.originDeviceId, createdAt: deletedAt, ); } } Future _softDeleteWorkoutTemplateExerciseOverrideRows( db.AppDatabase database, List rows, DateTime deletedAt, ) async { for (final row in rows) { final revision = row.localRevision + 1; await (database.update( database.workoutTemplateExerciseOverrides, )..where((table) => table.id.equals(row.id))).write( db.WorkoutTemplateExerciseOverridesCompanion( deletedAt: Value(deletedAt.toUtc()), updatedAt: Value(deletedAt.toUtc()), localRevision: Value(revision), syncState: const Value('deleted'), ), ); await _writeChangeLog( database: database, entityType: 'WorkoutTemplateExerciseOverride', entityId: row.id, operation: 'softDelete', localRevision: revision, originDeviceId: row.originDeviceId, createdAt: deletedAt, ); } } Future _softDeleteWorkoutHistorySetResultRows( db.AppDatabase database, List rows, DateTime deletedAt, ) async { for (final row in rows) { final revision = row.localRevision + 1; await (database.update( database.workoutHistorySetResults, )..where((table) => table.id.equals(row.id))).write( db.WorkoutHistorySetResultsCompanion( deletedAt: Value(deletedAt.toUtc()), updatedAt: Value(deletedAt.toUtc()), localRevision: Value(revision), syncState: const Value('deleted'), ), ); await _writeChangeLog( database: database, entityType: 'WorkoutHistorySetResult', entityId: row.id, operation: 'softDelete', localRevision: revision, originDeviceId: row.originDeviceId, createdAt: deletedAt, ); } } DateTime _utc(DateTime value) => value.toUtc(); DateTime? _utcOrNull(DateTime? value) => value?.toUtc(); domain.EntityMetadata _metadataFromRow(dynamic row) { return domain.EntityMetadata( id: row.id as String, createdAt: _utc(row.createdAt as DateTime), updatedAt: _utc(row.updatedAt as DateTime), deletedAt: _utcOrNull(row.deletedAt as DateTime?), schemaVersion: row.schemaVersion as int, syncState: _syncStateFromDb(row.syncState as String), localRevision: row.localRevision as int, originDeviceId: row.originDeviceId as String, futureOwnerProfileId: row.futureOwnerProfileId as String?, lastSyncedAt: _utcOrNull(row.lastSyncedAt as DateTime?), remoteRevision: row.remoteRevision as String?, ); } List _metadataValues(domain.EntityMetadata metadata) => [ Value(metadata.id), Value(metadata.createdAt.toUtc()), Value(metadata.updatedAt.toUtc()), Value(_utcOrNull(metadata.deletedAt)), Value(metadata.schemaVersion), Value(_syncStateToDb(metadata.syncState)), Value(metadata.localRevision), Value(metadata.originDeviceId), Value(metadata.futureOwnerProfileId), Value(_utcOrNull(metadata.lastSyncedAt)), Value(metadata.remoteRevision), ]; db.ExercisesCompanion _exerciseCompanion(domain.Exercise exercise) { final values = _metadataValues(exercise.metadata); return db.ExercisesCompanion( id: values[0] as Value, createdAt: values[1] as Value, updatedAt: values[2] as Value, deletedAt: values[3] as Value, schemaVersion: values[4] as Value, syncState: values[5] as Value, localRevision: values[6] as Value, originDeviceId: values[7] as Value, futureOwnerProfileId: values[8] as Value, lastSyncedAt: values[9] as Value, remoteRevision: values[10] as Value, name: Value(exercise.name), description: Value(exercise.description), imageMediaId: Value(exercise.imageMediaId), videoMediaId: Value(exercise.videoMediaId), hasTimeMeasure: Value(exercise.hasTimeMeasure), hasRepsMeasure: Value(exercise.hasRepsMeasure), hasScoreMeasure: Value(exercise.hasScoreMeasure), scoreLabel: Value(exercise.scoreLabel), scoreUnit: Value(exercise.scoreUnit), archivedAt: Value(_utcOrNull(exercise.archivedAt)), ); } domain.Exercise _exerciseFromRow(db.Exercise row) { return domain.Exercise( metadata: _metadataFromRow(row), name: row.name, description: row.description, imageMediaId: row.imageMediaId, videoMediaId: row.videoMediaId, hasTimeMeasure: row.hasTimeMeasure, hasRepsMeasure: row.hasRepsMeasure, hasScoreMeasure: row.hasScoreMeasure, scoreLabel: row.scoreLabel, scoreUnit: row.scoreUnit, archivedAt: _utcOrNull(row.archivedAt), ); } db.MediaAssetsCompanion _mediaAssetCompanion(domain.MediaAsset asset) { final values = _metadataValues(asset.metadata); return db.MediaAssetsCompanion( id: values[0] as Value, createdAt: values[1] as Value, updatedAt: values[2] as Value, deletedAt: values[3] as Value, schemaVersion: values[4] as Value, syncState: values[5] as Value, localRevision: values[6] as Value, originDeviceId: values[7] as Value, futureOwnerProfileId: values[8] as Value, lastSyncedAt: values[9] as Value, remoteRevision: values[10] as Value, kind: Value(_mediaKindToDb(asset.kind)), localUri: Value(asset.localUri), mimeType: Value(asset.mimeType), sizeBytes: Value(asset.sizeBytes), width: Value(asset.width), height: Value(asset.height), durationMs: Value(asset.durationMs), checksum: Value(asset.checksum), remoteUri: Value(asset.remoteUri), thumbnailLocalUri: Value(asset.thumbnailLocalUri), ); } domain.MediaAsset _mediaAssetFromRow(db.MediaAsset row) { return domain.MediaAsset( metadata: _metadataFromRow(row), kind: _mediaKindFromDb(row.kind), localUri: row.localUri, mimeType: row.mimeType, sizeBytes: row.sizeBytes, width: row.width, height: row.height, durationMs: row.durationMs, checksum: row.checksum, remoteUri: row.remoteUri, thumbnailLocalUri: row.thumbnailLocalUri, ); } db.ProgramsCompanion _programCompanion(domain.Program program) { final values = _metadataValues(program.metadata); return db.ProgramsCompanion( id: values[0] as Value, createdAt: values[1] as Value, updatedAt: values[2] as Value, deletedAt: values[3] as Value, schemaVersion: values[4] as Value, syncState: values[5] as Value, localRevision: values[6] as Value, originDeviceId: values[7] as Value, futureOwnerProfileId: values[8] as Value, lastSyncedAt: values[9] as Value, remoteRevision: values[10] as Value, name: Value(program.name), defaultRestSeconds: Value(program.defaultRestSeconds), ); } domain.Program _programFromRow( db.Program row, List exercises, ) { return domain.Program( metadata: _metadataFromRow(row), name: row.name, defaultRestSeconds: row.defaultRestSeconds, exercises: exercises, ); } db.ProgramExercisesCompanion _programExerciseCompanion( domain.ProgramExercise exercise, ) { final values = _metadataValues(exercise.metadata); return db.ProgramExercisesCompanion( id: values[0] as Value, createdAt: values[1] as Value, updatedAt: values[2] as Value, deletedAt: values[3] as Value, schemaVersion: values[4] as Value, syncState: values[5] as Value, localRevision: values[6] as Value, originDeviceId: values[7] as Value, futureOwnerProfileId: values[8] as Value, lastSyncedAt: values[9] as Value, remoteRevision: values[10] as Value, programId: Value(exercise.programId), sourceExerciseId: Value(exercise.sourceExerciseId), position: Value(exercise.position), exerciseNameSnapshot: Value(exercise.exerciseNameSnapshot), exerciseDescriptionSnapshot: Value(exercise.exerciseDescriptionSnapshot), exerciseImageMediaIdSnapshot: Value(exercise.exerciseImageMediaIdSnapshot), exerciseVideoMediaIdSnapshot: Value(exercise.exerciseVideoMediaIdSnapshot), exerciseArchivedSnapshot: Value(exercise.exerciseArchivedSnapshot), availableTimeSnapshot: Value(exercise.availableTimeSnapshot), availableRepsSnapshot: Value(exercise.availableRepsSnapshot), availableScoreSnapshot: Value(exercise.availableScoreSnapshot), scoreLabelSnapshot: Value(exercise.scoreLabelSnapshot), scoreUnitSnapshot: Value(exercise.scoreUnitSnapshot), setsCount: Value(exercise.setsCount), timeEnabled: Value(exercise.timeEnabled), repsEnabled: Value(exercise.repsEnabled), scoreEnabled: Value(exercise.scoreEnabled), targetTimeSeconds: Value(exercise.targetTimeSeconds), targetReps: Value(exercise.targetReps), targetScore: Value(exercise.targetScore), restSecondsOverride: Value(exercise.restSecondsOverride), ); } domain.ProgramExercise _programExerciseFromRow(db.ProgramExercise row) { return domain.ProgramExercise( metadata: _metadataFromRow(row), programId: row.programId, sourceExerciseId: row.sourceExerciseId, position: row.position, exerciseNameSnapshot: row.exerciseNameSnapshot, exerciseDescriptionSnapshot: row.exerciseDescriptionSnapshot, exerciseImageMediaIdSnapshot: row.exerciseImageMediaIdSnapshot, exerciseVideoMediaIdSnapshot: row.exerciseVideoMediaIdSnapshot, exerciseArchivedSnapshot: row.exerciseArchivedSnapshot, availableTimeSnapshot: row.availableTimeSnapshot, availableRepsSnapshot: row.availableRepsSnapshot, availableScoreSnapshot: row.availableScoreSnapshot, scoreLabelSnapshot: row.scoreLabelSnapshot, scoreUnitSnapshot: row.scoreUnitSnapshot, setsCount: row.setsCount, timeEnabled: row.timeEnabled, repsEnabled: row.repsEnabled, scoreEnabled: row.scoreEnabled, targetTimeSeconds: row.targetTimeSeconds, targetReps: row.targetReps, targetScore: row.targetScore, restSecondsOverride: row.restSecondsOverride, ); } db.WorkoutTemplatesCompanion _workoutTemplateCompanion( domain.WorkoutTemplate template, ) { final values = _metadataValues(template.metadata); return db.WorkoutTemplatesCompanion( id: values[0] as Value, createdAt: values[1] as Value, updatedAt: values[2] as Value, deletedAt: values[3] as Value, schemaVersion: values[4] as Value, syncState: values[5] as Value, localRevision: values[6] as Value, originDeviceId: values[7] as Value, futureOwnerProfileId: values[8] as Value, lastSyncedAt: values[9] as Value, remoteRevision: values[10] as Value, name: Value(template.name), lastStartedAt: Value(_utcOrNull(template.lastStartedAt)), ); } domain.WorkoutTemplate _workoutTemplateFromRow( db.WorkoutTemplate row, List programs, List overrides, ) { return domain.WorkoutTemplate( metadata: _metadataFromRow(row), name: row.name, lastStartedAt: _utcOrNull(row.lastStartedAt), programs: programs, overrides: overrides, ); } db.WorkoutTemplateProgramsCompanion _workoutTemplateProgramCompanion( domain.WorkoutTemplateProgram program, ) { final values = _metadataValues(program.metadata); return db.WorkoutTemplateProgramsCompanion( id: values[0] as Value, createdAt: values[1] as Value, updatedAt: values[2] as Value, deletedAt: values[3] as Value, schemaVersion: values[4] as Value, syncState: values[5] as Value, localRevision: values[6] as Value, originDeviceId: values[7] as Value, futureOwnerProfileId: values[8] as Value, lastSyncedAt: values[9] as Value, remoteRevision: values[10] as Value, workoutTemplateId: Value(program.workoutTemplateId), sourceProgramId: Value(program.sourceProgramId), position: Value(program.position), programNameSnapshot: Value(program.programNameSnapshot), defaultRestSecondsSnapshot: Value(program.defaultRestSecondsSnapshot), programSnapshotJson: Value(program.programSnapshotJson), ); } domain.WorkoutTemplateProgram _workoutTemplateProgramFromRow( db.WorkoutTemplateProgram row, ) { return domain.WorkoutTemplateProgram( metadata: _metadataFromRow(row), workoutTemplateId: row.workoutTemplateId, sourceProgramId: row.sourceProgramId, position: row.position, programNameSnapshot: row.programNameSnapshot, defaultRestSecondsSnapshot: row.defaultRestSecondsSnapshot, programSnapshotJson: row.programSnapshotJson, ); } db.WorkoutTemplateExerciseOverridesCompanion _workoutTemplateExerciseOverrideCompanion( domain.WorkoutTemplateExerciseOverride override, ) { final values = _metadataValues(override.metadata); return db.WorkoutTemplateExerciseOverridesCompanion( id: values[0] as Value, createdAt: values[1] as Value, updatedAt: values[2] as Value, deletedAt: values[3] as Value, schemaVersion: values[4] as Value, syncState: values[5] as Value, localRevision: values[6] as Value, originDeviceId: values[7] as Value, futureOwnerProfileId: values[8] as Value, lastSyncedAt: values[9] as Value, remoteRevision: values[10] as Value, workoutTemplateProgramId: Value(override.workoutTemplateProgramId), snapshotProgramExerciseId: Value(override.snapshotProgramExerciseId), setsCountOverride: Value(override.setsCountOverride), targetTimeSecondsOverride: Value(override.targetTimeSecondsOverride), targetRepsOverride: Value(override.targetRepsOverride), targetScoreOverride: Value(override.targetScoreOverride), ); } domain.WorkoutTemplateExerciseOverride _workoutTemplateExerciseOverrideFromRow( db.WorkoutTemplateExerciseOverride row, ) { return domain.WorkoutTemplateExerciseOverride( metadata: _metadataFromRow(row), workoutTemplateProgramId: row.workoutTemplateProgramId, snapshotProgramExerciseId: row.snapshotProgramExerciseId, setsCountOverride: row.setsCountOverride, targetTimeSecondsOverride: row.targetTimeSecondsOverride, targetRepsOverride: row.targetRepsOverride, targetScoreOverride: row.targetScoreOverride, ); } db.ActiveWorkoutSessionsCompanion _activeWorkoutSessionCompanion( domain.ActiveWorkoutSession session, ) { final values = _metadataValues(session.metadata); return db.ActiveWorkoutSessionsCompanion( id: values[0] as Value, createdAt: values[1] as Value, updatedAt: values[2] as Value, deletedAt: values[3] as Value, schemaVersion: values[4] as Value, syncState: values[5] as Value, localRevision: values[6] as Value, originDeviceId: values[7] as Value, futureOwnerProfileId: values[8] as Value, lastSyncedAt: values[9] as Value, remoteRevision: values[10] as Value, sourceWorkoutTemplateId: Value(session.sourceWorkoutTemplateId), status: Value(_activeStatusToDb(session.status)), startedAt: Value(session.startedAt.toUtc()), pausedAt: Value(_utcOrNull(session.pausedAt)), endedAt: Value(_utcOrNull(session.endedAt)), lastPersistedAt: Value(session.lastPersistedAt.toUtc()), elapsedActiveMs: Value(session.elapsedActiveMs), currentProgramIndex: Value(session.currentProgramIndex), currentExerciseIndex: Value(session.currentExerciseIndex), currentSetIndex: Value(session.currentSetIndex), resolvedTemplateSnapshotJson: Value(session.resolvedTemplateSnapshotJson), ); } domain.ActiveWorkoutSession _activeWorkoutSessionFromRow( db.ActiveWorkoutSession row, ) { return domain.ActiveWorkoutSession( metadata: _metadataFromRow(row), sourceWorkoutTemplateId: row.sourceWorkoutTemplateId, status: _activeStatusFromDb(row.status), startedAt: _utc(row.startedAt), pausedAt: _utcOrNull(row.pausedAt), endedAt: _utcOrNull(row.endedAt), lastPersistedAt: _utc(row.lastPersistedAt), elapsedActiveMs: row.elapsedActiveMs, currentProgramIndex: row.currentProgramIndex, currentExerciseIndex: row.currentExerciseIndex, currentSetIndex: row.currentSetIndex, resolvedTemplateSnapshotJson: row.resolvedTemplateSnapshotJson, ); } db.ActiveSetResultsCompanion _activeSetResultCompanion( domain.ActiveSetResult result, ) { final values = _metadataValues(result.metadata); return db.ActiveSetResultsCompanion( id: values[0] as Value, createdAt: values[1] as Value, updatedAt: values[2] as Value, deletedAt: values[3] as Value, schemaVersion: values[4] as Value, syncState: values[5] as Value, localRevision: values[6] as Value, originDeviceId: values[7] as Value, futureOwnerProfileId: values[8] as Value, lastSyncedAt: values[9] as Value, remoteRevision: values[10] as Value, activeWorkoutSessionId: Value(result.activeWorkoutSessionId), programSnapshotId: Value(result.programSnapshotId), exerciseSnapshotId: Value(result.exerciseSnapshotId), programIndex: Value(result.programIndex), exerciseIndex: Value(result.exerciseIndex), setIndex: Value(result.setIndex), startedAt: Value(_utcOrNull(result.startedAt)), completedAt: Value(_utcOrNull(result.completedAt)), actualTimeMs: Value(result.actualTimeMs), actualReps: Value(result.actualReps), actualScore: Value(result.actualScore), scoreLabelSnapshot: Value(result.scoreLabelSnapshot), scoreUnitSnapshot: Value(result.scoreUnitSnapshot), note: Value(result.note), status: Value(_setResultStatusToDb(result.status)), ); } domain.ActiveSetResult _activeSetResultFromRow(db.ActiveSetResult row) { return domain.ActiveSetResult( metadata: _metadataFromRow(row), activeWorkoutSessionId: row.activeWorkoutSessionId, programSnapshotId: row.programSnapshotId, exerciseSnapshotId: row.exerciseSnapshotId, programIndex: row.programIndex, exerciseIndex: row.exerciseIndex, setIndex: row.setIndex, startedAt: _utcOrNull(row.startedAt), completedAt: _utcOrNull(row.completedAt), actualTimeMs: row.actualTimeMs, actualReps: row.actualReps, actualScore: row.actualScore, scoreLabelSnapshot: row.scoreLabelSnapshot, scoreUnitSnapshot: row.scoreUnitSnapshot, note: row.note, status: _setResultStatusFromDb(row.status), ); } db.ActiveRestStatesCompanion _activeRestStateCompanion( domain.ActiveRestState restState, ) { final values = _metadataValues(restState.metadata); return db.ActiveRestStatesCompanion( id: values[0] as Value, createdAt: values[1] as Value, updatedAt: values[2] as Value, deletedAt: values[3] as Value, schemaVersion: values[4] as Value, syncState: values[5] as Value, localRevision: values[6] as Value, originDeviceId: values[7] as Value, futureOwnerProfileId: values[8] as Value, lastSyncedAt: values[9] as Value, remoteRevision: values[10] as Value, activeWorkoutSessionId: Value(restState.activeWorkoutSessionId), afterProgramIndex: Value(restState.afterProgramIndex), afterExerciseIndex: Value(restState.afterExerciseIndex), afterSetIndex: Value(restState.afterSetIndex), plannedRestSeconds: Value(restState.plannedRestSeconds), adjustedRestSeconds: Value(restState.adjustedRestSeconds), startedAt: Value(restState.startedAt.toUtc()), endedAt: Value(_utcOrNull(restState.endedAt)), skippedAt: Value(_utcOrNull(restState.skippedAt)), ); } domain.ActiveRestState _activeRestStateFromRow(db.ActiveRestState row) { return domain.ActiveRestState( metadata: _metadataFromRow(row), activeWorkoutSessionId: row.activeWorkoutSessionId, afterProgramIndex: row.afterProgramIndex, afterExerciseIndex: row.afterExerciseIndex, afterSetIndex: row.afterSetIndex, plannedRestSeconds: row.plannedRestSeconds, adjustedRestSeconds: row.adjustedRestSeconds, startedAt: _utc(row.startedAt), endedAt: _utcOrNull(row.endedAt), skippedAt: _utcOrNull(row.skippedAt), ); } db.WorkoutHistoriesCompanion _workoutHistoryCompanion( domain.WorkoutHistory history, ) { final values = _metadataValues(history.metadata); return db.WorkoutHistoriesCompanion( id: values[0] as Value, createdAt: values[1] as Value, updatedAt: values[2] as Value, deletedAt: values[3] as Value, schemaVersion: values[4] as Value, syncState: values[5] as Value, localRevision: values[6] as Value, originDeviceId: values[7] as Value, futureOwnerProfileId: values[8] as Value, lastSyncedAt: values[9] as Value, remoteRevision: values[10] as Value, sourceWorkoutTemplateId: Value(history.sourceWorkoutTemplateId), sourceActiveWorkoutSessionId: Value(history.sourceActiveWorkoutSessionId), nameSnapshot: Value(history.nameSnapshot), startedAt: Value(history.startedAt.toUtc()), endedAt: Value(history.endedAt.toUtc()), totalActiveMs: Value(history.totalActiveMs), completed: Value(history.completed), historySnapshotJson: Value(history.historySnapshotJson), ); } db.WorkoutHistorySetResultsCompanion _workoutHistorySetResultCompanion( domain.WorkoutHistorySetResult result, ) { final values = _metadataValues(result.metadata); return db.WorkoutHistorySetResultsCompanion( id: values[0] as Value, createdAt: values[1] as Value, updatedAt: values[2] as Value, deletedAt: values[3] as Value, schemaVersion: values[4] as Value, syncState: values[5] as Value, localRevision: values[6] as Value, originDeviceId: values[7] as Value, futureOwnerProfileId: values[8] as Value, lastSyncedAt: values[9] as Value, remoteRevision: values[10] as Value, workoutHistoryId: Value(result.workoutHistoryId), programSnapshotId: Value(result.programSnapshotId), exerciseSnapshotId: Value(result.exerciseSnapshotId), programIndex: Value(result.programIndex), exerciseIndex: Value(result.exerciseIndex), setIndex: Value(result.setIndex), programNameSnapshot: Value(result.programNameSnapshot), exerciseNameSnapshot: Value(result.exerciseNameSnapshot), timeEnabledSnapshot: Value(result.timeEnabledSnapshot), repsEnabledSnapshot: Value(result.repsEnabledSnapshot), scoreEnabledSnapshot: Value(result.scoreEnabledSnapshot), targetTimeSecondsSnapshot: Value(result.targetTimeSecondsSnapshot), targetRepsSnapshot: Value(result.targetRepsSnapshot), targetScoreSnapshot: Value(result.targetScoreSnapshot), actualTimeMs: Value(result.actualTimeMs), actualReps: Value(result.actualReps), actualScore: Value(result.actualScore), scoreLabelSnapshot: Value(result.scoreLabelSnapshot), scoreUnitSnapshot: Value(result.scoreUnitSnapshot), startedAt: Value(_utcOrNull(result.startedAt)), completedAt: Value(_utcOrNull(result.completedAt)), status: Value(_setResultStatusToDb(result.status)), ); } domain.WorkoutHistory _workoutHistoryFromRow( db.WorkoutHistory row, List results, ) { return domain.WorkoutHistory( metadata: _metadataFromRow(row), sourceWorkoutTemplateId: row.sourceWorkoutTemplateId, sourceActiveWorkoutSessionId: row.sourceActiveWorkoutSessionId, nameSnapshot: row.nameSnapshot, startedAt: _utc(row.startedAt), endedAt: _utc(row.endedAt), totalActiveMs: row.totalActiveMs, completed: row.completed, historySnapshotJson: row.historySnapshotJson, results: results, ); } domain.WorkoutHistorySetResult _workoutHistorySetResultFromRow( db.WorkoutHistorySetResult row, ) { return domain.WorkoutHistorySetResult( metadata: _metadataFromRow(row), workoutHistoryId: row.workoutHistoryId, programSnapshotId: row.programSnapshotId, exerciseSnapshotId: row.exerciseSnapshotId, programIndex: row.programIndex, exerciseIndex: row.exerciseIndex, setIndex: row.setIndex, programNameSnapshot: row.programNameSnapshot, exerciseNameSnapshot: row.exerciseNameSnapshot, timeEnabledSnapshot: row.timeEnabledSnapshot, repsEnabledSnapshot: row.repsEnabledSnapshot, scoreEnabledSnapshot: row.scoreEnabledSnapshot, targetTimeSecondsSnapshot: row.targetTimeSecondsSnapshot, targetRepsSnapshot: row.targetRepsSnapshot, targetScoreSnapshot: row.targetScoreSnapshot, actualTimeMs: row.actualTimeMs, actualReps: row.actualReps, actualScore: row.actualScore, scoreLabelSnapshot: row.scoreLabelSnapshot, scoreUnitSnapshot: row.scoreUnitSnapshot, startedAt: _utcOrNull(row.startedAt), completedAt: _utcOrNull(row.completedAt), status: _setResultStatusFromDb(row.status), ); } String _syncStateToDb(domain.SyncState state) => switch (state) { domain.SyncState.localOnly => 'localOnly', domain.SyncState.dirty => 'dirty', domain.SyncState.synced => 'synced', domain.SyncState.deleted => 'deleted', }; domain.SyncState _syncStateFromDb(String value) => switch (value) { 'localOnly' => domain.SyncState.localOnly, 'dirty' => domain.SyncState.dirty, 'synced' => domain.SyncState.synced, 'deleted' => domain.SyncState.deleted, _ => throw domain.DomainException('Unknown sync state: $value'), }; String _activeStatusToDb(domain.ActiveWorkoutStatus status) => switch (status) { domain.ActiveWorkoutStatus.running => 'running', domain.ActiveWorkoutStatus.paused => 'paused', domain.ActiveWorkoutStatus.savedExit => 'savedExit', domain.ActiveWorkoutStatus.completed => 'completed', domain.ActiveWorkoutStatus.abandoned => 'abandoned', }; String _mediaKindToDb(domain.MediaKind kind) => switch (kind) { domain.MediaKind.image => 'image', domain.MediaKind.video => 'video', }; domain.MediaKind _mediaKindFromDb(String value) => switch (value) { 'image' => domain.MediaKind.image, 'video' => domain.MediaKind.video, _ => throw domain.DomainException('Unknown media kind: $value'), }; String _setResultStatusToDb(domain.SetResultStatus status) => switch (status) { domain.SetResultStatus.completed => 'completed', domain.SetResultStatus.skipped => 'skipped', }; domain.SetResultStatus _setResultStatusFromDb(String value) => switch (value) { 'completed' => domain.SetResultStatus.completed, 'skipped' => domain.SetResultStatus.skipped, _ => throw domain.DomainException('Unknown set result status: $value'), }; domain.ActiveWorkoutStatus _activeStatusFromDb(String value) => switch (value) { 'running' => domain.ActiveWorkoutStatus.running, 'paused' => domain.ActiveWorkoutStatus.paused, 'savedExit' => domain.ActiveWorkoutStatus.savedExit, 'completed' => domain.ActiveWorkoutStatus.completed, 'abandoned' => domain.ActiveWorkoutStatus.abandoned, _ => throw domain.DomainException('Unknown active workout status: $value'), };