Ajoute l'adapter API distant (infrastructure/remote/auth_api.dart, http_api_client.dart) et le stockage sécurisé du token d'auth (infrastructure/security/secure_storage_auth_token_store.dart), câblés dans app_bootstrap.dart. Étend le modèle Drift (migration schemaVersion 9→10) et la couche application (ports, use cases, entités) pour la session de compte. flutter pub get OK (ajout http, flutter_secure_storage), build_runner OK, dart format appliqué, analyze propre (mêmes infos préexistantes), 109/109 tests verts, build APK debug validé. Premier ticket du chantier "Ajouter les features serveur au client" (#63). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2534 lines
88 KiB
Dart
2534 lines
88 KiB
Dart
import 'dart:convert';
|
|
|
|
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<domain.Exercise?> findById(String id) async {
|
|
final row = await (database.select(
|
|
database.exercises,
|
|
)..where((table) => table.id.equals(id))).getSingleOrNull();
|
|
return row == null
|
|
? null
|
|
: _exerciseFromRow(
|
|
row,
|
|
await _imageMediaIdsForExercise(id),
|
|
await _stepsForExercise(id),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<List<domain.Exercise>> listActive() async {
|
|
final rows =
|
|
await (database.select(database.exercises)
|
|
..where(
|
|
(table) => table.deletedAt.isNull() & table.archivedAt.isNull(),
|
|
)
|
|
..orderBy([(table) => OrderingTerm.asc(table.name)]))
|
|
.get();
|
|
final exercises = <domain.Exercise>[];
|
|
for (final row in rows) {
|
|
exercises.add(
|
|
_exerciseFromRow(
|
|
row,
|
|
await _imageMediaIdsForExercise(row.id),
|
|
await _stepsForExercise(row.id),
|
|
),
|
|
);
|
|
}
|
|
return exercises;
|
|
}
|
|
|
|
@override
|
|
Future<bool> 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<void> save(domain.Exercise exercise) async {
|
|
await database.transaction(() async {
|
|
await _upsertWithChangeLog(
|
|
database: database,
|
|
tableName: 'exercises',
|
|
entityType: 'Exercise',
|
|
metadata: exercise.metadata,
|
|
write: () => database
|
|
.into(database.exercises)
|
|
.insertOnConflictUpdate(_exerciseCompanion(exercise)),
|
|
);
|
|
await _replaceExerciseImages(database, exercise);
|
|
await _replaceExerciseSteps(database, exercise);
|
|
});
|
|
}
|
|
|
|
Future<List<String>> _imageMediaIdsForExercise(String exerciseId) async {
|
|
final rows =
|
|
await (database.select(database.exerciseImages)
|
|
..where(
|
|
(table) =>
|
|
table.exerciseId.equals(exerciseId) &
|
|
table.deletedAt.isNull(),
|
|
)
|
|
..orderBy([(table) => OrderingTerm.asc(table.position)]))
|
|
.get();
|
|
return rows.map((row) => row.mediaAssetId).toList();
|
|
}
|
|
|
|
Future<List<domain.ExerciseStep>> _stepsForExercise(String exerciseId) async {
|
|
final rows =
|
|
await (database.select(database.exerciseSteps)
|
|
..where(
|
|
(table) =>
|
|
table.exerciseId.equals(exerciseId) &
|
|
table.deletedAt.isNull(),
|
|
)
|
|
..orderBy([(table) => OrderingTerm.asc(table.position)]))
|
|
.get();
|
|
return rows.map(_exerciseStepFromRow).toList();
|
|
}
|
|
}
|
|
|
|
final class DriftMediaAssetRepository implements MediaAssetRepository {
|
|
const DriftMediaAssetRepository(this.database);
|
|
|
|
final db.AppDatabase database;
|
|
|
|
@override
|
|
Future<domain.MediaAsset?> 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<List<domain.MediaAsset>> listActive() async {
|
|
final rows = await (database.select(
|
|
database.mediaAssets,
|
|
)..where((table) => table.deletedAt.isNull())).get();
|
|
return rows.map(_mediaAssetFromRow).toList();
|
|
}
|
|
|
|
@override
|
|
Future<void> 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 DriftOnlineAccountRepository implements OnlineAccountRepository {
|
|
const DriftOnlineAccountRepository(this.database);
|
|
|
|
final db.AppDatabase database;
|
|
|
|
@override
|
|
Future<domain.UserAccountSession?> currentSession() async {
|
|
final row =
|
|
await (database.select(database.onlineAccountSessions)
|
|
..orderBy([(table) => OrderingTerm.desc(table.updatedAt)])
|
|
..limit(1))
|
|
.getSingleOrNull();
|
|
return row == null ? null : _userAccountSessionFromRow(row);
|
|
}
|
|
|
|
@override
|
|
Future<void> saveSession(domain.UserAccountSession session) async {
|
|
await database
|
|
.into(database.onlineAccountSessions)
|
|
.insertOnConflictUpdate(_userAccountSessionCompanion(session));
|
|
}
|
|
|
|
@override
|
|
Future<void> clearSession() async {
|
|
await database.delete(database.onlineAccountSessions).go();
|
|
}
|
|
}
|
|
|
|
final class DriftProgramRepository implements ProgramRepository {
|
|
const DriftProgramRepository(this.database);
|
|
|
|
final db.AppDatabase database;
|
|
|
|
@override
|
|
Future<domain.Program?> 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<List<domain.Program>> listActive() async {
|
|
final rows =
|
|
await (database.select(database.programs)
|
|
..where((table) => table.deletedAt.isNull())
|
|
..orderBy([(table) => OrderingTerm.asc(table.name)]))
|
|
.get();
|
|
final programs = <domain.Program>[];
|
|
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<void> 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<void> 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<void> 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<domain.WorkoutTemplate?> 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
|
|
? <db.WorkoutTemplateExerciseOverride>[]
|
|
: 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<List<domain.WorkoutTemplate>> listActive() async {
|
|
final rows =
|
|
await (database.select(database.workoutTemplates)
|
|
..where((table) => table.deletedAt.isNull())
|
|
..orderBy([(table) => OrderingTerm.asc(table.name)]))
|
|
.get();
|
|
final templates = <domain.WorkoutTemplate>[];
|
|
for (final row in rows) {
|
|
final template = await findById(row.id);
|
|
if (template != null) {
|
|
templates.add(template);
|
|
}
|
|
}
|
|
return templates;
|
|
}
|
|
|
|
@override
|
|
Future<void> 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<void> 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<void> 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<void> 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<domain.ActiveWorkoutSession?> 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<domain.ActiveWorkoutSession?> 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<void> 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<void> 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<void> 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<domain.ActiveRestState?> 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<void> saveScoreStopwatchState(
|
|
domain.ActiveScoreStopwatchState state,
|
|
) async {
|
|
await _upsertWithChangeLog(
|
|
database: database,
|
|
tableName: 'active_score_stopwatch_states',
|
|
entityType: 'ActiveScoreStopwatchState',
|
|
metadata: state.metadata,
|
|
write: () => database
|
|
.into(database.activeScoreStopwatchStates)
|
|
.insertOnConflictUpdate(_activeScoreStopwatchStateCompanion(state)),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> saveExerciseStepProgressState(
|
|
domain.ActiveExerciseStepProgressState state,
|
|
) async {
|
|
await _upsertWithChangeLog(
|
|
database: database,
|
|
tableName: 'active_exercise_step_progress_states',
|
|
entityType: 'ActiveExerciseStepProgressState',
|
|
metadata: state.metadata,
|
|
write: () => database
|
|
.into(database.activeExerciseStepProgressStates)
|
|
.insertOnConflictUpdate(
|
|
_activeExerciseStepProgressStateCompanion(state),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> saveExerciseStepResult(
|
|
domain.ActiveExerciseStepResult result,
|
|
) async {
|
|
await _upsertWithChangeLog(
|
|
database: database,
|
|
tableName: 'active_exercise_step_results',
|
|
entityType: 'ActiveExerciseStepResult',
|
|
metadata: result.metadata,
|
|
write: () => database
|
|
.into(database.activeExerciseStepResults)
|
|
.insertOnConflictUpdate(_activeExerciseStepResultCompanion(result)),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteScoreStopwatchState({
|
|
required String sessionId,
|
|
required int programIndex,
|
|
required int exerciseIndex,
|
|
required int setIndex,
|
|
required DateTime deletedAt,
|
|
}) async {
|
|
final row =
|
|
await (database.select(database.activeScoreStopwatchStates)..where(
|
|
(table) =>
|
|
table.activeWorkoutSessionId.equals(sessionId) &
|
|
table.programIndex.equals(programIndex) &
|
|
table.exerciseIndex.equals(exerciseIndex) &
|
|
table.setIndex.equals(setIndex) &
|
|
table.deletedAt.isNull(),
|
|
))
|
|
.getSingleOrNull();
|
|
if (row == null) {
|
|
return;
|
|
}
|
|
final revision = row.localRevision + 1;
|
|
await (database.delete(
|
|
database.activeScoreStopwatchStates,
|
|
)..where((table) => table.id.equals(row.id))).go();
|
|
await _writeChangeLog(
|
|
database: database,
|
|
entityType: 'ActiveScoreStopwatchState',
|
|
entityId: row.id,
|
|
operation: 'delete',
|
|
localRevision: revision,
|
|
originDeviceId: row.originDeviceId,
|
|
createdAt: deletedAt,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<domain.ActiveScoreStopwatchState?> findScoreStopwatchState({
|
|
required String sessionId,
|
|
required int programIndex,
|
|
required int exerciseIndex,
|
|
required int setIndex,
|
|
}) async {
|
|
final row =
|
|
await (database.select(database.activeScoreStopwatchStates)..where(
|
|
(table) =>
|
|
table.activeWorkoutSessionId.equals(sessionId) &
|
|
table.programIndex.equals(programIndex) &
|
|
table.exerciseIndex.equals(exerciseIndex) &
|
|
table.setIndex.equals(setIndex) &
|
|
table.deletedAt.isNull(),
|
|
))
|
|
.getSingleOrNull();
|
|
return row == null ? null : _activeScoreStopwatchStateFromRow(row);
|
|
}
|
|
|
|
@override
|
|
Future<domain.ActiveExerciseStepProgressState?>
|
|
findExerciseStepProgressState({
|
|
required String sessionId,
|
|
required int programIndex,
|
|
required int exerciseIndex,
|
|
required int setIndex,
|
|
}) async {
|
|
final row =
|
|
await (database.select(database.activeExerciseStepProgressStates)
|
|
..where(
|
|
(table) =>
|
|
table.activeWorkoutSessionId.equals(sessionId) &
|
|
table.programIndex.equals(programIndex) &
|
|
table.exerciseIndex.equals(exerciseIndex) &
|
|
table.setIndex.equals(setIndex) &
|
|
table.deletedAt.isNull(),
|
|
))
|
|
.getSingleOrNull();
|
|
return row == null ? null : _activeExerciseStepProgressStateFromRow(row);
|
|
}
|
|
|
|
@override
|
|
Future<List<domain.ActiveSetResult>> 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<List<domain.ActiveExerciseStepProgressState>>
|
|
listExerciseStepProgressStates(String sessionId) async {
|
|
final rows =
|
|
await (database.select(database.activeExerciseStepProgressStates)
|
|
..where(
|
|
(table) =>
|
|
table.activeWorkoutSessionId.equals(sessionId) &
|
|
table.deletedAt.isNull(),
|
|
)
|
|
..orderBy([
|
|
(table) => OrderingTerm.asc(table.programIndex),
|
|
(table) => OrderingTerm.asc(table.exerciseIndex),
|
|
(table) => OrderingTerm.asc(table.setIndex),
|
|
]))
|
|
.get();
|
|
return rows.map(_activeExerciseStepProgressStateFromRow).toList();
|
|
}
|
|
|
|
@override
|
|
Future<List<domain.ActiveExerciseStepResult>> listExerciseStepResults(
|
|
String sessionId,
|
|
) async {
|
|
final rows =
|
|
await (database.select(database.activeExerciseStepResults)
|
|
..where(
|
|
(table) =>
|
|
table.activeWorkoutSessionId.equals(sessionId) &
|
|
table.deletedAt.isNull(),
|
|
)
|
|
..orderBy([
|
|
(table) => OrderingTerm.asc(table.programIndex),
|
|
(table) => OrderingTerm.asc(table.exerciseIndex),
|
|
(table) => OrderingTerm.asc(table.setIndex),
|
|
(table) => OrderingTerm.asc(table.passageIndex),
|
|
(table) => OrderingTerm.asc(table.stepIndex),
|
|
]))
|
|
.get();
|
|
return rows.map(_activeExerciseStepResultFromRow).toList();
|
|
}
|
|
|
|
@override
|
|
Future<List<domain.ActiveRestState>> 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();
|
|
}
|
|
|
|
@override
|
|
Future<List<domain.ActiveScoreStopwatchState>> listScoreStopwatchStates(
|
|
String sessionId,
|
|
) async {
|
|
final rows =
|
|
await (database.select(database.activeScoreStopwatchStates)
|
|
..where(
|
|
(table) =>
|
|
table.activeWorkoutSessionId.equals(sessionId) &
|
|
table.deletedAt.isNull(),
|
|
)
|
|
..orderBy([
|
|
(table) => OrderingTerm.asc(table.programIndex),
|
|
(table) => OrderingTerm.asc(table.exerciseIndex),
|
|
(table) => OrderingTerm.asc(table.setIndex),
|
|
]))
|
|
.get();
|
|
return rows.map(_activeScoreStopwatchStateFromRow).toList();
|
|
}
|
|
}
|
|
|
|
final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository {
|
|
const DriftWorkoutHistoryRepository(this.database);
|
|
|
|
final db.AppDatabase database;
|
|
|
|
@override
|
|
Future<domain.WorkoutHistory?> 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();
|
|
final stepResults =
|
|
await (database.select(database.workoutHistoryStepResults)
|
|
..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),
|
|
(table) => OrderingTerm.asc(table.passageIndex),
|
|
(table) => OrderingTerm.asc(table.stepIndex),
|
|
]))
|
|
.get();
|
|
return _workoutHistoryFromRow(
|
|
row,
|
|
results.map(_workoutHistorySetResultFromRow).toList(),
|
|
stepResults.map(_workoutHistoryStepResultFromRow).toList(),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<List<domain.WorkoutHistory>> listActive() async {
|
|
final rows =
|
|
await (database.select(database.workoutHistories)
|
|
..where((table) => table.deletedAt.isNull())
|
|
..orderBy([(table) => OrderingTerm.desc(table.startedAt)]))
|
|
.get();
|
|
final histories = <domain.WorkoutHistory>[];
|
|
for (final row in rows) {
|
|
final history = await findById(row.id);
|
|
if (history != null) {
|
|
histories.add(history);
|
|
}
|
|
}
|
|
return histories;
|
|
}
|
|
|
|
@override
|
|
Future<void> 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);
|
|
}
|
|
for (final result in history.stepResults) {
|
|
await saveStepResult(result);
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<void> 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<void> saveStepResult(domain.WorkoutHistoryStepResult result) async {
|
|
await _upsertWithChangeLog(
|
|
database: database,
|
|
tableName: 'workout_history_step_results',
|
|
entityType: 'WorkoutHistoryStepResult',
|
|
metadata: result.metadata,
|
|
write: () => database
|
|
.into(database.workoutHistoryStepResults)
|
|
.insertOnConflictUpdate(_workoutHistoryStepResultCompanion(result)),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> 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,
|
|
);
|
|
final stepResultRows =
|
|
await (database.select(database.workoutHistoryStepResults)..where(
|
|
(table) =>
|
|
table.workoutHistoryId.equals(id) &
|
|
table.deletedAt.isNull(),
|
|
))
|
|
.get();
|
|
await _softDeleteWorkoutHistoryStepResultRows(
|
|
database,
|
|
stepResultRows,
|
|
deletedAt,
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _upsertWithChangeLog({
|
|
required db.AppDatabase database,
|
|
required String tableName,
|
|
required String entityType,
|
|
required domain.EntityMetadata metadata,
|
|
required Future<void> 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<void> _replaceExerciseImages(
|
|
db.AppDatabase database,
|
|
domain.Exercise exercise,
|
|
) async {
|
|
final rows = await (database.select(
|
|
database.exerciseImages,
|
|
)..where((table) => table.exerciseId.equals(exercise.metadata.id))).get();
|
|
final activeRowsByMediaId = {
|
|
for (final row in rows)
|
|
if (row.deletedAt == null) row.mediaAssetId: row,
|
|
};
|
|
final desiredIds = exercise.imageMediaIds.toSet();
|
|
final removedRows = activeRowsByMediaId.values
|
|
.where((row) => !desiredIds.contains(row.mediaAssetId))
|
|
.toList();
|
|
await _softDeleteExerciseImageRows(
|
|
database,
|
|
removedRows,
|
|
exercise.metadata.updatedAt,
|
|
);
|
|
|
|
for (var index = 0; index < exercise.imageMediaIds.length; index++) {
|
|
final mediaId = exercise.imageMediaIds[index];
|
|
final existing = activeRowsByMediaId[mediaId];
|
|
final metadata = existing == null
|
|
? domain.EntityMetadata(
|
|
id: _exerciseImageId(exercise.metadata.id, mediaId),
|
|
createdAt: exercise.metadata.updatedAt,
|
|
updatedAt: exercise.metadata.updatedAt,
|
|
originDeviceId: exercise.metadata.originDeviceId,
|
|
)
|
|
: _metadataFromRow(existing).touch(exercise.metadata.updatedAt);
|
|
await _upsertWithChangeLog(
|
|
database: database,
|
|
tableName: 'exercise_images',
|
|
entityType: 'ExerciseImage',
|
|
metadata: metadata,
|
|
write: () => database
|
|
.into(database.exerciseImages)
|
|
.insertOnConflictUpdate(
|
|
db.ExerciseImagesCompanion(
|
|
id: Value(metadata.id),
|
|
createdAt: Value(metadata.createdAt.toUtc()),
|
|
updatedAt: Value(metadata.updatedAt.toUtc()),
|
|
deletedAt: Value(_utcOrNull(metadata.deletedAt)),
|
|
schemaVersion: Value(metadata.schemaVersion),
|
|
syncState: Value(_syncStateToDb(metadata.syncState)),
|
|
localRevision: Value(metadata.localRevision),
|
|
originDeviceId: Value(metadata.originDeviceId),
|
|
futureOwnerProfileId: Value(metadata.futureOwnerProfileId),
|
|
lastSyncedAt: Value(_utcOrNull(metadata.lastSyncedAt)),
|
|
remoteRevision: Value(metadata.remoteRevision),
|
|
exerciseId: Value(exercise.metadata.id),
|
|
mediaAssetId: Value(mediaId),
|
|
position: Value(index),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _replaceExerciseSteps(
|
|
db.AppDatabase database,
|
|
domain.Exercise exercise,
|
|
) async {
|
|
final rows = await (database.select(
|
|
database.exerciseSteps,
|
|
)..where((table) => table.exerciseId.equals(exercise.metadata.id))).get();
|
|
final activeRowsByStepId = {
|
|
for (final row in rows)
|
|
if (row.deletedAt == null) row.id: row,
|
|
};
|
|
final desiredIds = exercise.steps.map((step) => step.id).toSet();
|
|
final removedRows = activeRowsByStepId.values
|
|
.where((row) => !desiredIds.contains(row.id))
|
|
.toList();
|
|
await _softDeleteExerciseStepRows(
|
|
database,
|
|
removedRows,
|
|
exercise.metadata.updatedAt,
|
|
);
|
|
|
|
for (final step in exercise.steps) {
|
|
final existing = activeRowsByStepId[step.id];
|
|
final metadata = existing == null
|
|
? domain.EntityMetadata(
|
|
id: step.id,
|
|
createdAt: exercise.metadata.updatedAt,
|
|
updatedAt: exercise.metadata.updatedAt,
|
|
originDeviceId: exercise.metadata.originDeviceId,
|
|
)
|
|
: _metadataFromRow(existing).touch(exercise.metadata.updatedAt);
|
|
await _upsertWithChangeLog(
|
|
database: database,
|
|
tableName: 'exercise_steps',
|
|
entityType: 'ExerciseStep',
|
|
metadata: metadata,
|
|
write: () => database
|
|
.into(database.exerciseSteps)
|
|
.insertOnConflictUpdate(
|
|
db.ExerciseStepsCompanion(
|
|
id: Value(metadata.id),
|
|
createdAt: Value(metadata.createdAt.toUtc()),
|
|
updatedAt: Value(metadata.updatedAt.toUtc()),
|
|
deletedAt: Value(_utcOrNull(metadata.deletedAt)),
|
|
schemaVersion: Value(metadata.schemaVersion),
|
|
syncState: Value(_syncStateToDb(metadata.syncState)),
|
|
localRevision: Value(metadata.localRevision),
|
|
originDeviceId: Value(metadata.originDeviceId),
|
|
futureOwnerProfileId: Value(metadata.futureOwnerProfileId),
|
|
lastSyncedAt: Value(_utcOrNull(metadata.lastSyncedAt)),
|
|
remoteRevision: Value(metadata.remoteRevision),
|
|
exerciseId: Value(exercise.metadata.id),
|
|
position: Value(step.position),
|
|
name: Value(step.name),
|
|
type: Value(_exerciseStepTypeToDb(step.type)),
|
|
defaultTargetValue: Value(step.defaultTargetValue),
|
|
hasScore: Value(step.hasScore),
|
|
scoreInputMode: Value(
|
|
step.hasScore ? _scoreInputModeToDb(step.scoreInputMode) : null,
|
|
),
|
|
scoreLabel: Value(step.scoreLabel),
|
|
scoreUnit: Value(step.scoreUnit),
|
|
defaultTargetScore: Value(step.defaultTargetScore),
|
|
defaultTargetScoreTimeMs: Value(step.defaultTargetScoreTimeMs),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _softDeleteExerciseImageRows(
|
|
db.AppDatabase database,
|
|
List<db.ExerciseImage> rows,
|
|
DateTime deletedAt,
|
|
) async {
|
|
for (final row in rows) {
|
|
final revision = row.localRevision + 1;
|
|
await (database.update(
|
|
database.exerciseImages,
|
|
)..where((table) => table.id.equals(row.id))).write(
|
|
db.ExerciseImagesCompanion(
|
|
deletedAt: Value(deletedAt.toUtc()),
|
|
updatedAt: Value(deletedAt.toUtc()),
|
|
localRevision: Value(revision),
|
|
syncState: const Value('deleted'),
|
|
),
|
|
);
|
|
await _writeChangeLog(
|
|
database: database,
|
|
entityType: 'ExerciseImage',
|
|
entityId: row.id,
|
|
operation: 'softDelete',
|
|
localRevision: revision,
|
|
originDeviceId: row.originDeviceId,
|
|
createdAt: deletedAt,
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _softDeleteExerciseStepRows(
|
|
db.AppDatabase database,
|
|
List<db.ExerciseStep> rows,
|
|
DateTime deletedAt,
|
|
) async {
|
|
for (final row in rows) {
|
|
final revision = row.localRevision + 1;
|
|
await (database.update(
|
|
database.exerciseSteps,
|
|
)..where((table) => table.id.equals(row.id))).write(
|
|
db.ExerciseStepsCompanion(
|
|
deletedAt: Value(deletedAt.toUtc()),
|
|
updatedAt: Value(deletedAt.toUtc()),
|
|
localRevision: Value(revision),
|
|
syncState: const Value('deleted'),
|
|
),
|
|
);
|
|
await _writeChangeLog(
|
|
database: database,
|
|
entityType: 'ExerciseStep',
|
|
entityId: row.id,
|
|
operation: 'softDelete',
|
|
localRevision: revision,
|
|
originDeviceId: row.originDeviceId,
|
|
createdAt: deletedAt,
|
|
);
|
|
}
|
|
}
|
|
|
|
String _exerciseImageId(String exerciseId, String mediaId) {
|
|
return 'exercise-image:$exerciseId:$mediaId';
|
|
}
|
|
|
|
Future<String> _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<String>(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<void> _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<void> _softDeleteProgramExerciseRows(
|
|
db.AppDatabase database,
|
|
List<db.ProgramExercise> 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<void> _softDeleteWorkoutTemplateProgramRows(
|
|
db.AppDatabase database,
|
|
List<db.WorkoutTemplateProgram> 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<void> _softDeleteWorkoutTemplateExerciseOverrideRows(
|
|
db.AppDatabase database,
|
|
List<db.WorkoutTemplateExerciseOverride> 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<void> _softDeleteWorkoutHistorySetResultRows(
|
|
db.AppDatabase database,
|
|
List<db.WorkoutHistorySetResult> 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,
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _softDeleteWorkoutHistoryStepResultRows(
|
|
db.AppDatabase database,
|
|
List<db.WorkoutHistoryStepResult> rows,
|
|
DateTime deletedAt,
|
|
) async {
|
|
for (final row in rows) {
|
|
final revision = row.localRevision + 1;
|
|
await (database.update(
|
|
database.workoutHistoryStepResults,
|
|
)..where((table) => table.id.equals(row.id))).write(
|
|
db.WorkoutHistoryStepResultsCompanion(
|
|
deletedAt: Value(deletedAt.toUtc()),
|
|
updatedAt: Value(deletedAt.toUtc()),
|
|
localRevision: Value(revision),
|
|
syncState: const Value('deleted'),
|
|
),
|
|
);
|
|
await _writeChangeLog(
|
|
database: database,
|
|
entityType: 'WorkoutHistoryStepResult',
|
|
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<dynamic> _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<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
name: Value(exercise.name),
|
|
description: Value(exercise.description),
|
|
iconMediaId: Value(exercise.iconMediaId),
|
|
videoMediaId: Value(exercise.videoMediaId),
|
|
hasTimeMeasure: Value(exercise.hasTimeMeasure),
|
|
hasRepsMeasure: Value(exercise.hasRepsMeasure),
|
|
hasScoreMeasure: Value(exercise.hasScoreMeasure),
|
|
scoreInputMode: Value(_scoreInputModeToDb(exercise.scoreInputMode)),
|
|
scoreLabel: Value(exercise.scoreLabel),
|
|
scoreUnit: Value(exercise.scoreUnit),
|
|
defaultTargetTimeSeconds: Value(exercise.defaultTargetTimeSeconds),
|
|
defaultTargetReps: Value(exercise.defaultTargetReps),
|
|
defaultTargetScore: Value(exercise.defaultTargetScore),
|
|
defaultTargetScoreTimeMs: Value(exercise.defaultTargetScoreTimeMs),
|
|
archivedAt: Value(_utcOrNull(exercise.archivedAt)),
|
|
);
|
|
}
|
|
|
|
domain.Exercise _exerciseFromRow(
|
|
db.Exercise row,
|
|
List<String> imageMediaIds,
|
|
List<domain.ExerciseStep> steps,
|
|
) {
|
|
return domain.Exercise(
|
|
metadata: _metadataFromRow(row),
|
|
name: row.name,
|
|
description: row.description,
|
|
imageMediaIds: imageMediaIds,
|
|
iconMediaId: row.iconMediaId,
|
|
videoMediaId: row.videoMediaId,
|
|
hasTimeMeasure: row.hasTimeMeasure,
|
|
hasRepsMeasure: row.hasRepsMeasure,
|
|
hasScoreMeasure: row.hasScoreMeasure,
|
|
scoreInputMode: _scoreInputModeFromDb(row.scoreInputMode),
|
|
scoreLabel: row.scoreLabel,
|
|
scoreUnit: row.scoreUnit,
|
|
defaultTargetTimeSeconds: row.defaultTargetTimeSeconds,
|
|
defaultTargetReps: row.defaultTargetReps,
|
|
defaultTargetScore: row.defaultTargetScore,
|
|
defaultTargetScoreTimeMs: row.defaultTargetScoreTimeMs,
|
|
steps: steps,
|
|
archivedAt: _utcOrNull(row.archivedAt),
|
|
);
|
|
}
|
|
|
|
domain.ExerciseStep _exerciseStepFromRow(db.ExerciseStep row) {
|
|
return domain.ExerciseStep(
|
|
id: row.id,
|
|
position: row.position,
|
|
name: row.name,
|
|
type: _exerciseStepTypeFromDb(row.type),
|
|
defaultTargetValue: row.defaultTargetValue,
|
|
hasScore: row.hasScore,
|
|
scoreInputMode: row.scoreInputMode == null
|
|
? domain.ScoreInputMode.manual
|
|
: _scoreInputModeFromDb(row.scoreInputMode!),
|
|
scoreLabel: row.scoreLabel,
|
|
scoreUnit: row.scoreUnit,
|
|
defaultTargetScore: row.defaultTargetScore,
|
|
defaultTargetScoreTimeMs: row.defaultTargetScoreTimeMs,
|
|
);
|
|
}
|
|
|
|
db.MediaAssetsCompanion _mediaAssetCompanion(domain.MediaAsset asset) {
|
|
final values = _metadataValues(asset.metadata);
|
|
return db.MediaAssetsCompanion(
|
|
id: values[0] as Value<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
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.OnlineAccountSessionsCompanion _userAccountSessionCompanion(
|
|
domain.UserAccountSession session,
|
|
) {
|
|
return db.OnlineAccountSessionsCompanion(
|
|
id: Value(session.id),
|
|
serverUserId: Value(session.serverUserId),
|
|
email: Value(session.email),
|
|
displayName: Value<String?>(session.displayName),
|
|
isLoggedIn: Value(session.isLoggedIn),
|
|
createdAt: Value(session.createdAt.toUtc()),
|
|
updatedAt: Value(session.updatedAt.toUtc()),
|
|
lastAuthenticatedAt: Value<DateTime?>(
|
|
_utcOrNull(session.lastAuthenticatedAt),
|
|
),
|
|
);
|
|
}
|
|
|
|
domain.UserAccountSession _userAccountSessionFromRow(
|
|
db.OnlineAccountSession row,
|
|
) {
|
|
return domain.UserAccountSession(
|
|
id: row.id,
|
|
serverUserId: row.serverUserId,
|
|
email: row.email,
|
|
displayName: row.displayName,
|
|
isLoggedIn: row.isLoggedIn,
|
|
createdAt: _utc(row.createdAt),
|
|
updatedAt: _utc(row.updatedAt),
|
|
lastAuthenticatedAt: _utcOrNull(row.lastAuthenticatedAt),
|
|
);
|
|
}
|
|
|
|
db.ProgramsCompanion _programCompanion(domain.Program program) {
|
|
final values = _metadataValues(program.metadata);
|
|
return db.ProgramsCompanion(
|
|
id: values[0] as Value<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
name: Value(program.name),
|
|
defaultRestSeconds: Value(program.defaultRestSeconds),
|
|
);
|
|
}
|
|
|
|
domain.Program _programFromRow(
|
|
db.Program row,
|
|
List<domain.ProgramExercise> 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<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
programId: Value(exercise.programId),
|
|
sourceExerciseId: Value(exercise.sourceExerciseId),
|
|
position: Value(exercise.position),
|
|
exerciseNameSnapshot: Value(exercise.exerciseNameSnapshot),
|
|
exerciseDescriptionSnapshot: Value(exercise.exerciseDescriptionSnapshot),
|
|
exerciseImageMediaIdSnapshot: Value(exercise.exerciseImageMediaIdSnapshot),
|
|
exerciseImageMediaIdsSnapshotJson: Value(
|
|
_encodeImageMediaIdsSnapshot(exercise.exerciseImageMediaIdsSnapshot),
|
|
),
|
|
exerciseStepsSnapshotJson: Value(
|
|
_encodeExerciseStepsSnapshot(exercise.exerciseStepsSnapshot),
|
|
),
|
|
exerciseVideoMediaIdSnapshot: Value(exercise.exerciseVideoMediaIdSnapshot),
|
|
exerciseArchivedSnapshot: Value(exercise.exerciseArchivedSnapshot),
|
|
availableTimeSnapshot: Value(exercise.availableTimeSnapshot),
|
|
availableRepsSnapshot: Value(exercise.availableRepsSnapshot),
|
|
availableScoreSnapshot: Value(exercise.availableScoreSnapshot),
|
|
scoreInputModeSnapshot: Value(
|
|
_scoreInputModeToDb(exercise.scoreInputModeSnapshot),
|
|
),
|
|
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),
|
|
targetScoreTimeMs: Value(exercise.targetScoreTimeMs),
|
|
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,
|
|
exerciseImageMediaIdsSnapshot: _decodeImageMediaIdsSnapshot(
|
|
row.exerciseImageMediaIdsSnapshotJson,
|
|
row.exerciseImageMediaIdSnapshot,
|
|
),
|
|
exerciseStepsSnapshot: _decodeExerciseStepsSnapshot(
|
|
row.exerciseStepsSnapshotJson,
|
|
),
|
|
exerciseVideoMediaIdSnapshot: row.exerciseVideoMediaIdSnapshot,
|
|
exerciseArchivedSnapshot: row.exerciseArchivedSnapshot,
|
|
availableTimeSnapshot: row.availableTimeSnapshot,
|
|
availableRepsSnapshot: row.availableRepsSnapshot,
|
|
availableScoreSnapshot: row.availableScoreSnapshot,
|
|
scoreInputModeSnapshot: _scoreInputModeFromDb(row.scoreInputModeSnapshot),
|
|
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,
|
|
targetScoreTimeMs: row.targetScoreTimeMs,
|
|
restSecondsOverride: row.restSecondsOverride,
|
|
);
|
|
}
|
|
|
|
db.WorkoutTemplatesCompanion _workoutTemplateCompanion(
|
|
domain.WorkoutTemplate template,
|
|
) {
|
|
final values = _metadataValues(template.metadata);
|
|
return db.WorkoutTemplatesCompanion(
|
|
id: values[0] as Value<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
name: Value(template.name),
|
|
lastStartedAt: Value(_utcOrNull(template.lastStartedAt)),
|
|
);
|
|
}
|
|
|
|
domain.WorkoutTemplate _workoutTemplateFromRow(
|
|
db.WorkoutTemplate row,
|
|
List<domain.WorkoutTemplateProgram> programs,
|
|
List<domain.WorkoutTemplateExerciseOverride> 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<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
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<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
workoutTemplateProgramId: Value(override.workoutTemplateProgramId),
|
|
snapshotProgramExerciseId: Value(override.snapshotProgramExerciseId),
|
|
setsCountOverride: Value(override.setsCountOverride),
|
|
targetTimeSecondsOverride: Value(override.targetTimeSecondsOverride),
|
|
targetRepsOverride: Value(override.targetRepsOverride),
|
|
targetScoreOverride: Value(override.targetScoreOverride),
|
|
targetScoreTimeMsOverride: Value(override.targetScoreTimeMsOverride),
|
|
);
|
|
}
|
|
|
|
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,
|
|
targetScoreTimeMsOverride: row.targetScoreTimeMsOverride,
|
|
);
|
|
}
|
|
|
|
db.ActiveWorkoutSessionsCompanion _activeWorkoutSessionCompanion(
|
|
domain.ActiveWorkoutSession session,
|
|
) {
|
|
final values = _metadataValues(session.metadata);
|
|
return db.ActiveWorkoutSessionsCompanion(
|
|
id: values[0] as Value<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
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<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
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),
|
|
actualScoreTimeMs: Value(result.actualScoreTimeMs),
|
|
scoreInputModeSnapshot: Value(
|
|
_scoreInputModeToDb(result.scoreInputModeSnapshot),
|
|
),
|
|
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,
|
|
actualScoreTimeMs: row.actualScoreTimeMs,
|
|
scoreInputModeSnapshot: _scoreInputModeFromDb(row.scoreInputModeSnapshot),
|
|
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<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
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.ActiveScoreStopwatchStatesCompanion _activeScoreStopwatchStateCompanion(
|
|
domain.ActiveScoreStopwatchState state,
|
|
) {
|
|
final values = _metadataValues(state.metadata);
|
|
return db.ActiveScoreStopwatchStatesCompanion(
|
|
id: values[0] as Value<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
activeWorkoutSessionId: Value(state.activeWorkoutSessionId),
|
|
programIndex: Value(state.programIndex),
|
|
exerciseIndex: Value(state.exerciseIndex),
|
|
setIndex: Value(state.setIndex),
|
|
status: Value(_scoreStopwatchStatusToDb(state.status)),
|
|
startedAt: Value(state.startedAt.toUtc()),
|
|
accumulatedMs: Value(state.accumulatedMs),
|
|
stoppedAt: Value(_utcOrNull(state.stoppedAt)),
|
|
);
|
|
}
|
|
|
|
domain.ActiveScoreStopwatchState _activeScoreStopwatchStateFromRow(
|
|
db.ActiveScoreStopwatchState row,
|
|
) {
|
|
return domain.ActiveScoreStopwatchState(
|
|
metadata: _metadataFromRow(row),
|
|
activeWorkoutSessionId: row.activeWorkoutSessionId,
|
|
programIndex: row.programIndex,
|
|
exerciseIndex: row.exerciseIndex,
|
|
setIndex: row.setIndex,
|
|
status: _scoreStopwatchStatusFromDb(row.status),
|
|
startedAt: _utc(row.startedAt),
|
|
accumulatedMs: row.accumulatedMs,
|
|
stoppedAt: _utcOrNull(row.stoppedAt),
|
|
);
|
|
}
|
|
|
|
db.ActiveExerciseStepProgressStatesCompanion
|
|
_activeExerciseStepProgressStateCompanion(
|
|
domain.ActiveExerciseStepProgressState state,
|
|
) {
|
|
final values = _metadataValues(state.metadata);
|
|
return db.ActiveExerciseStepProgressStatesCompanion(
|
|
id: values[0] as Value<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
activeWorkoutSessionId: Value(state.activeWorkoutSessionId),
|
|
programIndex: Value(state.programIndex),
|
|
exerciseIndex: Value(state.exerciseIndex),
|
|
setIndex: Value(state.setIndex),
|
|
currentPassageIndex: Value(state.currentPassageIndex),
|
|
currentStepIndex: Value(state.currentStepIndex),
|
|
currentStepSnapshotId: Value(state.currentStepSnapshotId),
|
|
status: Value(_exerciseStepProgressStatusToDb(state.status)),
|
|
startedAt: Value<DateTime?>(_utcOrNull(state.startedAt)),
|
|
accumulatedMs: Value(state.accumulatedMs),
|
|
lastTransitionAt: Value(state.lastTransitionAt.toUtc()),
|
|
);
|
|
}
|
|
|
|
domain.ActiveExerciseStepProgressState _activeExerciseStepProgressStateFromRow(
|
|
db.ActiveExerciseStepProgressState row,
|
|
) {
|
|
return domain.ActiveExerciseStepProgressState(
|
|
metadata: _metadataFromRow(row),
|
|
activeWorkoutSessionId: row.activeWorkoutSessionId,
|
|
programIndex: row.programIndex,
|
|
exerciseIndex: row.exerciseIndex,
|
|
setIndex: row.setIndex,
|
|
currentPassageIndex: row.currentPassageIndex,
|
|
currentStepIndex: row.currentStepIndex,
|
|
currentStepSnapshotId: row.currentStepSnapshotId,
|
|
status: _exerciseStepProgressStatusFromDb(row.status),
|
|
startedAt: _utcOrNull(row.startedAt),
|
|
accumulatedMs: row.accumulatedMs,
|
|
lastTransitionAt: _utc(row.lastTransitionAt),
|
|
);
|
|
}
|
|
|
|
db.ActiveExerciseStepResultsCompanion _activeExerciseStepResultCompanion(
|
|
domain.ActiveExerciseStepResult result,
|
|
) {
|
|
final values = _metadataValues(result.metadata);
|
|
return db.ActiveExerciseStepResultsCompanion(
|
|
id: values[0] as Value<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
activeWorkoutSessionId: Value(result.activeWorkoutSessionId),
|
|
programSnapshotId: Value(result.programSnapshotId),
|
|
exerciseSnapshotId: Value(result.exerciseSnapshotId),
|
|
programIndex: Value(result.programIndex),
|
|
exerciseIndex: Value(result.exerciseIndex),
|
|
setIndex: Value(result.setIndex),
|
|
passageIndex: Value(result.passageIndex),
|
|
stepIndex: Value(result.stepIndex),
|
|
stepSnapshotId: Value(result.stepSnapshotId),
|
|
stepNameSnapshot: Value(result.stepNameSnapshot),
|
|
stepTypeSnapshot: Value(_exerciseStepTypeToDb(result.stepTypeSnapshot)),
|
|
targetValueSnapshot: Value(result.targetValueSnapshot),
|
|
hasScoreSnapshot: Value(result.hasScoreSnapshot),
|
|
scoreInputModeSnapshot: Value<String?>(
|
|
result.scoreInputModeSnapshot == null
|
|
? null
|
|
: _scoreInputModeToDb(result.scoreInputModeSnapshot!),
|
|
),
|
|
scoreLabelSnapshot: Value<String?>(result.scoreLabelSnapshot),
|
|
scoreUnitSnapshot: Value<String?>(result.scoreUnitSnapshot),
|
|
targetScoreSnapshot: Value<double?>(result.targetScoreSnapshot),
|
|
targetScoreTimeMsSnapshot: Value<int?>(result.targetScoreTimeMsSnapshot),
|
|
status: Value(_setResultStatusToDb(result.status)),
|
|
startedAt: Value<DateTime?>(_utcOrNull(result.startedAt)),
|
|
completedAt: Value<DateTime?>(_utcOrNull(result.completedAt)),
|
|
actualTimeMs: Value<int?>(result.actualTimeMs),
|
|
actualReps: Value<int?>(result.actualReps),
|
|
actualScore: Value<double?>(result.actualScore),
|
|
actualScoreTimeMs: Value<int?>(result.actualScoreTimeMs),
|
|
note: Value<String?>(result.note),
|
|
);
|
|
}
|
|
|
|
domain.ActiveExerciseStepResult _activeExerciseStepResultFromRow(
|
|
db.ActiveExerciseStepResult row,
|
|
) {
|
|
return domain.ActiveExerciseStepResult(
|
|
metadata: _metadataFromRow(row),
|
|
activeWorkoutSessionId: row.activeWorkoutSessionId,
|
|
programSnapshotId: row.programSnapshotId,
|
|
exerciseSnapshotId: row.exerciseSnapshotId,
|
|
programIndex: row.programIndex,
|
|
exerciseIndex: row.exerciseIndex,
|
|
setIndex: row.setIndex,
|
|
passageIndex: row.passageIndex,
|
|
stepIndex: row.stepIndex,
|
|
stepSnapshotId: row.stepSnapshotId,
|
|
stepNameSnapshot: row.stepNameSnapshot,
|
|
stepTypeSnapshot: _exerciseStepTypeFromDb(row.stepTypeSnapshot),
|
|
targetValueSnapshot: row.targetValueSnapshot,
|
|
hasScoreSnapshot: row.hasScoreSnapshot,
|
|
scoreInputModeSnapshot: row.scoreInputModeSnapshot == null
|
|
? null
|
|
: _scoreInputModeFromDb(row.scoreInputModeSnapshot!),
|
|
scoreLabelSnapshot: row.scoreLabelSnapshot,
|
|
scoreUnitSnapshot: row.scoreUnitSnapshot,
|
|
targetScoreSnapshot: row.targetScoreSnapshot,
|
|
targetScoreTimeMsSnapshot: row.targetScoreTimeMsSnapshot,
|
|
status: _setResultStatusFromDb(row.status),
|
|
startedAt: _utcOrNull(row.startedAt),
|
|
completedAt: _utcOrNull(row.completedAt),
|
|
actualTimeMs: row.actualTimeMs,
|
|
actualReps: row.actualReps,
|
|
actualScore: row.actualScore,
|
|
actualScoreTimeMs: row.actualScoreTimeMs,
|
|
note: row.note,
|
|
);
|
|
}
|
|
|
|
db.WorkoutHistoriesCompanion _workoutHistoryCompanion(
|
|
domain.WorkoutHistory history,
|
|
) {
|
|
final values = _metadataValues(history.metadata);
|
|
return db.WorkoutHistoriesCompanion(
|
|
id: values[0] as Value<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
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<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
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),
|
|
scoreInputModeSnapshot: Value(
|
|
_scoreInputModeToDb(result.scoreInputModeSnapshot),
|
|
),
|
|
targetTimeSecondsSnapshot: Value(result.targetTimeSecondsSnapshot),
|
|
targetRepsSnapshot: Value(result.targetRepsSnapshot),
|
|
targetScoreSnapshot: Value(result.targetScoreSnapshot),
|
|
targetScoreTimeMsSnapshot: Value(result.targetScoreTimeMsSnapshot),
|
|
actualTimeMs: Value(result.actualTimeMs),
|
|
actualReps: Value(result.actualReps),
|
|
actualScore: Value(result.actualScore),
|
|
actualScoreTimeMs: Value(result.actualScoreTimeMs),
|
|
scoreLabelSnapshot: Value(result.scoreLabelSnapshot),
|
|
scoreUnitSnapshot: Value(result.scoreUnitSnapshot),
|
|
startedAt: Value(_utcOrNull(result.startedAt)),
|
|
completedAt: Value(_utcOrNull(result.completedAt)),
|
|
status: Value(_setResultStatusToDb(result.status)),
|
|
);
|
|
}
|
|
|
|
db.WorkoutHistoryStepResultsCompanion _workoutHistoryStepResultCompanion(
|
|
domain.WorkoutHistoryStepResult result,
|
|
) {
|
|
final values = _metadataValues(result.metadata);
|
|
return db.WorkoutHistoryStepResultsCompanion(
|
|
id: values[0] as Value<String>,
|
|
createdAt: values[1] as Value<DateTime>,
|
|
updatedAt: values[2] as Value<DateTime>,
|
|
deletedAt: values[3] as Value<DateTime?>,
|
|
schemaVersion: values[4] as Value<int>,
|
|
syncState: values[5] as Value<String>,
|
|
localRevision: values[6] as Value<int>,
|
|
originDeviceId: values[7] as Value<String>,
|
|
futureOwnerProfileId: values[8] as Value<String?>,
|
|
lastSyncedAt: values[9] as Value<DateTime?>,
|
|
remoteRevision: values[10] as Value<String?>,
|
|
workoutHistoryId: Value(result.workoutHistoryId),
|
|
programSnapshotId: Value(result.programSnapshotId),
|
|
exerciseSnapshotId: Value(result.exerciseSnapshotId),
|
|
programIndex: Value(result.programIndex),
|
|
exerciseIndex: Value(result.exerciseIndex),
|
|
setIndex: Value(result.setIndex),
|
|
passageIndex: Value(result.passageIndex),
|
|
stepIndex: Value(result.stepIndex),
|
|
stepSnapshotId: Value(result.stepSnapshotId),
|
|
stepNameSnapshot: Value(result.stepNameSnapshot),
|
|
stepTypeSnapshot: Value(_exerciseStepTypeToDb(result.stepTypeSnapshot)),
|
|
targetValueSnapshot: Value(result.targetValueSnapshot),
|
|
hasScoreSnapshot: Value(result.hasScoreSnapshot),
|
|
scoreInputModeSnapshot: Value<String?>(
|
|
result.scoreInputModeSnapshot == null
|
|
? null
|
|
: _scoreInputModeToDb(result.scoreInputModeSnapshot!),
|
|
),
|
|
scoreLabelSnapshot: Value<String?>(result.scoreLabelSnapshot),
|
|
scoreUnitSnapshot: Value<String?>(result.scoreUnitSnapshot),
|
|
targetScoreSnapshot: Value<double?>(result.targetScoreSnapshot),
|
|
targetScoreTimeMsSnapshot: Value<int?>(result.targetScoreTimeMsSnapshot),
|
|
status: Value(_setResultStatusToDb(result.status)),
|
|
startedAt: Value<DateTime?>(_utcOrNull(result.startedAt)),
|
|
completedAt: Value<DateTime?>(_utcOrNull(result.completedAt)),
|
|
actualTimeMs: Value<int?>(result.actualTimeMs),
|
|
actualReps: Value<int?>(result.actualReps),
|
|
actualScore: Value<double?>(result.actualScore),
|
|
actualScoreTimeMs: Value<int?>(result.actualScoreTimeMs),
|
|
note: Value<String?>(result.note),
|
|
);
|
|
}
|
|
|
|
domain.WorkoutHistory _workoutHistoryFromRow(
|
|
db.WorkoutHistory row,
|
|
List<domain.WorkoutHistorySetResult> results,
|
|
List<domain.WorkoutHistoryStepResult> stepResults,
|
|
) {
|
|
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,
|
|
stepResults: stepResults,
|
|
);
|
|
}
|
|
|
|
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,
|
|
scoreInputModeSnapshot: _scoreInputModeFromDb(row.scoreInputModeSnapshot),
|
|
targetTimeSecondsSnapshot: row.targetTimeSecondsSnapshot,
|
|
targetRepsSnapshot: row.targetRepsSnapshot,
|
|
targetScoreSnapshot: row.targetScoreSnapshot,
|
|
targetScoreTimeMsSnapshot: row.targetScoreTimeMsSnapshot,
|
|
actualTimeMs: row.actualTimeMs,
|
|
actualReps: row.actualReps,
|
|
actualScore: row.actualScore,
|
|
actualScoreTimeMs: row.actualScoreTimeMs,
|
|
scoreLabelSnapshot: row.scoreLabelSnapshot,
|
|
scoreUnitSnapshot: row.scoreUnitSnapshot,
|
|
startedAt: _utcOrNull(row.startedAt),
|
|
completedAt: _utcOrNull(row.completedAt),
|
|
status: _setResultStatusFromDb(row.status),
|
|
);
|
|
}
|
|
|
|
domain.WorkoutHistoryStepResult _workoutHistoryStepResultFromRow(
|
|
db.WorkoutHistoryStepResult row,
|
|
) {
|
|
return domain.WorkoutHistoryStepResult(
|
|
metadata: _metadataFromRow(row),
|
|
workoutHistoryId: row.workoutHistoryId,
|
|
programSnapshotId: row.programSnapshotId,
|
|
exerciseSnapshotId: row.exerciseSnapshotId,
|
|
programIndex: row.programIndex,
|
|
exerciseIndex: row.exerciseIndex,
|
|
setIndex: row.setIndex,
|
|
passageIndex: row.passageIndex,
|
|
stepIndex: row.stepIndex,
|
|
stepSnapshotId: row.stepSnapshotId,
|
|
stepNameSnapshot: row.stepNameSnapshot,
|
|
stepTypeSnapshot: _exerciseStepTypeFromDb(row.stepTypeSnapshot),
|
|
targetValueSnapshot: row.targetValueSnapshot,
|
|
hasScoreSnapshot: row.hasScoreSnapshot,
|
|
scoreInputModeSnapshot: row.scoreInputModeSnapshot == null
|
|
? null
|
|
: _scoreInputModeFromDb(row.scoreInputModeSnapshot!),
|
|
scoreLabelSnapshot: row.scoreLabelSnapshot,
|
|
scoreUnitSnapshot: row.scoreUnitSnapshot,
|
|
targetScoreSnapshot: row.targetScoreSnapshot,
|
|
targetScoreTimeMsSnapshot: row.targetScoreTimeMsSnapshot,
|
|
status: _setResultStatusFromDb(row.status),
|
|
startedAt: _utcOrNull(row.startedAt),
|
|
completedAt: _utcOrNull(row.completedAt),
|
|
actualTimeMs: row.actualTimeMs,
|
|
actualReps: row.actualReps,
|
|
actualScore: row.actualScore,
|
|
actualScoreTimeMs: row.actualScoreTimeMs,
|
|
note: row.note,
|
|
);
|
|
}
|
|
|
|
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 _scoreInputModeToDb(domain.ScoreInputMode mode) => switch (mode) {
|
|
domain.ScoreInputMode.manual => 'manual',
|
|
domain.ScoreInputMode.stopwatch => 'stopwatch',
|
|
};
|
|
|
|
domain.ScoreInputMode _scoreInputModeFromDb(String value) => switch (value) {
|
|
'manual' => domain.ScoreInputMode.manual,
|
|
'stopwatch' => domain.ScoreInputMode.stopwatch,
|
|
_ => throw domain.DomainException('Unknown score input mode: $value'),
|
|
};
|
|
|
|
String _exerciseStepTypeToDb(domain.ExerciseStepType type) => switch (type) {
|
|
domain.ExerciseStepType.time => 'time',
|
|
domain.ExerciseStepType.reps => 'reps',
|
|
};
|
|
|
|
domain.ExerciseStepType _exerciseStepTypeFromDb(String value) =>
|
|
switch (value) {
|
|
'time' => domain.ExerciseStepType.time,
|
|
'reps' => domain.ExerciseStepType.reps,
|
|
_ => throw domain.DomainException('Unknown exercise step type: $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'),
|
|
};
|
|
|
|
String _scoreStopwatchStatusToDb(domain.ActiveScoreStopwatchStatus status) =>
|
|
switch (status) {
|
|
domain.ActiveScoreStopwatchStatus.running => 'running',
|
|
domain.ActiveScoreStopwatchStatus.stopped => 'stopped',
|
|
};
|
|
|
|
domain.ActiveScoreStopwatchStatus _scoreStopwatchStatusFromDb(String value) =>
|
|
switch (value) {
|
|
'running' => domain.ActiveScoreStopwatchStatus.running,
|
|
'stopped' => domain.ActiveScoreStopwatchStatus.stopped,
|
|
_ => throw domain.DomainException(
|
|
'Unknown active score stopwatch status: $value',
|
|
),
|
|
};
|
|
|
|
String _exerciseStepProgressStatusToDb(
|
|
domain.ActiveExerciseStepProgressStatus status,
|
|
) => switch (status) {
|
|
domain.ActiveExerciseStepProgressStatus.notStarted => 'notStarted',
|
|
domain.ActiveExerciseStepProgressStatus.waitingManual => 'waitingManual',
|
|
domain.ActiveExerciseStepProgressStatus.runningTimer => 'runningTimer',
|
|
domain.ActiveExerciseStepProgressStatus.pausedTimer => 'pausedTimer',
|
|
domain.ActiveExerciseStepProgressStatus.stoppedTimer => 'stoppedTimer',
|
|
domain.ActiveExerciseStepProgressStatus.sequenceComplete =>
|
|
'sequenceComplete',
|
|
};
|
|
|
|
domain.ActiveExerciseStepProgressStatus _exerciseStepProgressStatusFromDb(
|
|
String value,
|
|
) => switch (value) {
|
|
'notStarted' => domain.ActiveExerciseStepProgressStatus.notStarted,
|
|
'waitingManual' => domain.ActiveExerciseStepProgressStatus.waitingManual,
|
|
'runningTimer' => domain.ActiveExerciseStepProgressStatus.runningTimer,
|
|
'pausedTimer' => domain.ActiveExerciseStepProgressStatus.pausedTimer,
|
|
'stoppedTimer' => domain.ActiveExerciseStepProgressStatus.stoppedTimer,
|
|
'sequenceComplete' =>
|
|
domain.ActiveExerciseStepProgressStatus.sequenceComplete,
|
|
_ => throw domain.DomainException(
|
|
'Unknown active exercise step progress status: $value',
|
|
),
|
|
};
|
|
|
|
String? _encodeImageMediaIdsSnapshot(List<String> imageMediaIds) {
|
|
return imageMediaIds.isEmpty ? null : jsonEncode(imageMediaIds);
|
|
}
|
|
|
|
List<String> _decodeImageMediaIdsSnapshot(
|
|
String? encoded,
|
|
String? fallbackImageMediaId,
|
|
) {
|
|
if (encoded == null || encoded.trim().isEmpty) {
|
|
return [if (fallbackImageMediaId != null) fallbackImageMediaId];
|
|
}
|
|
final decoded = jsonDecode(encoded);
|
|
if (decoded is! List) {
|
|
throw const domain.DomainException(
|
|
'Invalid exercise image media ids snapshot.',
|
|
);
|
|
}
|
|
return decoded
|
|
.whereType<String>()
|
|
.where((id) => id.trim().isNotEmpty)
|
|
.toList(growable: false);
|
|
}
|
|
|
|
String? _encodeExerciseStepsSnapshot(List<domain.ExerciseStep> steps) {
|
|
return steps.isEmpty
|
|
? null
|
|
: jsonEncode(steps.map((step) => step.toSnapshotJson()).toList());
|
|
}
|
|
|
|
List<domain.ExerciseStep> _decodeExerciseStepsSnapshot(String? encoded) {
|
|
if (encoded == null || encoded.trim().isEmpty) {
|
|
return const [];
|
|
}
|
|
final decoded = jsonDecode(encoded);
|
|
if (decoded is! List) {
|
|
throw const domain.DomainException('Invalid exercise steps snapshot.');
|
|
}
|
|
return decoded
|
|
.map((item) {
|
|
if (item is! Map) {
|
|
throw const domain.DomainException('Invalid exercise step snapshot.');
|
|
}
|
|
final json = Map<String, Object?>.from(item);
|
|
return domain.ExerciseStep(
|
|
id: _requiredString(json, 'id'),
|
|
position: _requiredInt(json, 'position'),
|
|
name: _requiredString(json, 'name'),
|
|
type: _exerciseStepTypeFromDb(_requiredString(json, 'type')),
|
|
defaultTargetValue: _requiredInt(json, 'defaultTargetValue'),
|
|
hasScore: _requiredBool(json, 'hasScore'),
|
|
scoreInputMode: _scoreInputModeFromDb(
|
|
_optionalString(json, 'scoreInputMode') ?? 'manual',
|
|
),
|
|
scoreLabel: _optionalString(json, 'scoreLabel'),
|
|
scoreUnit: _optionalString(json, 'scoreUnit'),
|
|
defaultTargetScore: _optionalDouble(json, 'defaultTargetScore'),
|
|
defaultTargetScoreTimeMs: _optionalInt(
|
|
json,
|
|
'defaultTargetScoreTimeMs',
|
|
),
|
|
);
|
|
})
|
|
.toList(growable: false);
|
|
}
|
|
|
|
String _requiredString(Map<String, Object?> json, String key) {
|
|
final value = json[key];
|
|
if (value is String && value.trim().isNotEmpty) {
|
|
return value;
|
|
}
|
|
throw domain.DomainException('$key must be a non-empty string.');
|
|
}
|
|
|
|
String? _optionalString(Map<String, Object?> json, String key) {
|
|
final value = json[key];
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
return value is String ? value : null;
|
|
}
|
|
|
|
int _requiredInt(Map<String, Object?> json, String key) {
|
|
final value = json[key];
|
|
if (value is int) {
|
|
return value;
|
|
}
|
|
throw domain.DomainException('$key must be an integer.');
|
|
}
|
|
|
|
int? _optionalInt(Map<String, Object?> json, String key) {
|
|
final value = json[key];
|
|
return value is int ? value : null;
|
|
}
|
|
|
|
double? _optionalDouble(Map<String, Object?> json, String key) {
|
|
final value = json[key];
|
|
return (value as num?)?.toDouble();
|
|
}
|
|
|
|
bool _requiredBool(Map<String, Object?> json, String key) {
|
|
final value = json[key];
|
|
if (value is bool) {
|
|
return value;
|
|
}
|
|
throw domain.DomainException('$key must be a boolean.');
|
|
}
|
|
|
|
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'),
|
|
};
|