Ajoute la persistance de l'exécution par étapes avec résultats par passage (ports, use cases, entités, modèle Drift avec migration) et l'éditeur d'exercice avec séquence d'étapes sur exercise_library_screen.dart. Développés dans le même worktree partagé par DevBackend et DevFrontend ; commit combiné plutôt que séparé car les fakes de repository de test corrigés (nouvelles interfaces #58) touchent plusieurs écrans sans lien avec l'éditeur lui-même (historique, accueil, exécution, séance-modèle). Corrige au passage un ListTile sans ancêtre Material dans le nouvel éditeur d'étapes (Material(type: MaterialType.transparency)). flutter pub get OK, build_runner OK, analyze propre (mêmes infos préexistantes), 93/93 tests verts, build APK debug validé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -7,6 +7,8 @@ part 'app_database.g.dart';
|
||||
|
||||
@DriftDatabase(
|
||||
tables: [
|
||||
ActiveExerciseStepProgressStates,
|
||||
ActiveExerciseStepResults,
|
||||
ActiveRestStates,
|
||||
ActiveScoreStopwatchStates,
|
||||
ActiveSetResults,
|
||||
@ -20,6 +22,7 @@ part 'app_database.g.dart';
|
||||
Programs,
|
||||
WorkoutHistories,
|
||||
WorkoutHistorySetResults,
|
||||
WorkoutHistoryStepResults,
|
||||
WorkoutTemplateExerciseOverrides,
|
||||
WorkoutTemplatePrograms,
|
||||
WorkoutTemplates,
|
||||
@ -38,7 +41,7 @@ final class AppDatabase extends _$AppDatabase {
|
||||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 8;
|
||||
int get schemaVersion => 9;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration {
|
||||
@ -77,6 +80,9 @@ final class AppDatabase extends _$AppDatabase {
|
||||
if (from < 8) {
|
||||
await _migrateToSchema8(migrator);
|
||||
}
|
||||
if (from < 9) {
|
||||
await _migrateToSchema9(migrator);
|
||||
}
|
||||
await _createIndexes();
|
||||
},
|
||||
beforeOpen: (details) async {
|
||||
@ -133,6 +139,15 @@ final class AppDatabase extends _$AppDatabase {
|
||||
'CREATE INDEX IF NOT EXISTS idx_active_rest_states_session_id '
|
||||
'ON active_rest_states (active_workout_session_id)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS '
|
||||
'idx_active_exercise_step_progress_states_session_id '
|
||||
'ON active_exercise_step_progress_states (active_workout_session_id)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_active_exercise_step_results_session_id '
|
||||
'ON active_exercise_step_results (active_workout_session_id)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS '
|
||||
'idx_active_workout_sessions_single_open '
|
||||
@ -148,6 +163,10 @@ final class AppDatabase extends _$AppDatabase {
|
||||
'CREATE INDEX IF NOT EXISTS idx_workout_history_set_results_history_id '
|
||||
'ON workout_history_set_results (workout_history_id)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_workout_history_step_results_history_id '
|
||||
'ON workout_history_step_results (workout_history_id)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_change_log_entity '
|
||||
'ON change_log (entity_type, entity_id)',
|
||||
@ -164,6 +183,8 @@ final class AppDatabase extends _$AppDatabase {
|
||||
}
|
||||
|
||||
const _syncableTableNames = [
|
||||
'active_exercise_step_progress_states',
|
||||
'active_exercise_step_results',
|
||||
'active_rest_states',
|
||||
'active_score_stopwatch_states',
|
||||
'active_set_results',
|
||||
@ -176,6 +197,7 @@ const _syncableTableNames = [
|
||||
'programs',
|
||||
'workout_history',
|
||||
'workout_history_set_results',
|
||||
'workout_history_step_results',
|
||||
'workout_template_exercise_overrides',
|
||||
'workout_template_programs',
|
||||
'workout_templates',
|
||||
@ -287,4 +309,10 @@ extension on AppDatabase {
|
||||
'exercise_steps_snapshot_json TEXT',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _migrateToSchema9(Migrator migrator) async {
|
||||
await migrator.createTable(activeExerciseStepProgressStates);
|
||||
await migrator.createTable(activeExerciseStepResults);
|
||||
await migrator.createTable(workoutHistoryStepResults);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -532,6 +532,38 @@ final class DriftActiveSessionRepository implements ActiveSessionRepository {
|
||||
);
|
||||
}
|
||||
|
||||
@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,
|
||||
@ -588,6 +620,28 @@ final class DriftActiveSessionRepository implements ActiveSessionRepository {
|
||||
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 =
|
||||
@ -602,6 +656,47 @@ final class DriftActiveSessionRepository implements ActiveSessionRepository {
|
||||
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 =
|
||||
@ -661,9 +756,25 @@ final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository {
|
||||
(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(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -699,6 +810,9 @@ final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository {
|
||||
for (final result in history.results) {
|
||||
await saveSetResult(result);
|
||||
}
|
||||
for (final result in history.stepResults) {
|
||||
await saveStepResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -715,6 +829,19 @@ final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository {
|
||||
);
|
||||
}
|
||||
|
||||
@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 {
|
||||
@ -753,6 +880,18 @@ final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository {
|
||||
resultRows,
|
||||
deletedAt,
|
||||
);
|
||||
final stepResultRows =
|
||||
await (database.select(database.workoutHistoryStepResults)..where(
|
||||
(table) =>
|
||||
table.workoutHistoryId.equals(id) &
|
||||
table.deletedAt.isNull(),
|
||||
))
|
||||
.get();
|
||||
await _softDeleteWorkoutHistoryStepResultRows(
|
||||
database,
|
||||
stepResultRows,
|
||||
deletedAt,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1149,6 +1288,35 @@ Future<void> _softDeleteWorkoutHistorySetResultRows(
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@ -1732,6 +1900,142 @@ domain.ActiveScoreStopwatchState _activeScoreStopwatchStateFromRow(
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
) {
|
||||
@ -1805,9 +2109,59 @@ db.WorkoutHistorySetResultsCompanion _workoutHistorySetResultCompanion(
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
@ -1820,6 +2174,7 @@ domain.WorkoutHistory _workoutHistoryFromRow(
|
||||
completed: row.completed,
|
||||
historySnapshotJson: row.historySnapshotJson,
|
||||
results: results,
|
||||
stepResults: stepResults,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1856,6 +2211,42 @@ domain.WorkoutHistorySetResult _workoutHistorySetResultFromRow(
|
||||
);
|
||||
}
|
||||
|
||||
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',
|
||||
@ -1939,6 +2330,33 @@ domain.ActiveScoreStopwatchStatus _scoreStopwatchStatusFromDb(String 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);
|
||||
}
|
||||
|
||||
@ -419,6 +419,108 @@ class ActiveRestStates extends SyncableTable {
|
||||
];
|
||||
}
|
||||
|
||||
class ActiveExerciseStepProgressStates extends SyncableTable {
|
||||
@override
|
||||
String get tableName => 'active_exercise_step_progress_states';
|
||||
|
||||
TextColumn get activeWorkoutSessionId =>
|
||||
text().references(ActiveWorkoutSessions, #id)();
|
||||
IntColumn get programIndex => integer()();
|
||||
IntColumn get exerciseIndex => integer()();
|
||||
IntColumn get setIndex => integer()();
|
||||
IntColumn get currentPassageIndex => integer()();
|
||||
IntColumn get currentStepIndex => integer()();
|
||||
TextColumn get currentStepSnapshotId => text().withLength(min: 1)();
|
||||
TextColumn get status => text()();
|
||||
DateTimeColumn get startedAt => dateTime().nullable()();
|
||||
IntColumn get accumulatedMs => integer()();
|
||||
DateTimeColumn get lastTransitionAt => dateTime()();
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => [
|
||||
'UNIQUE (active_workout_session_id, program_index, exercise_index, '
|
||||
'set_index)',
|
||||
'CHECK (program_index >= 0)',
|
||||
'CHECK (exercise_index >= 0)',
|
||||
'CHECK (set_index >= 0)',
|
||||
'CHECK (current_passage_index >= 0)',
|
||||
'CHECK (current_step_index >= 0)',
|
||||
"CHECK (status IN ('notStarted', 'waitingManual', 'runningTimer', "
|
||||
"'pausedTimer', 'stoppedTimer', 'sequenceComplete'))",
|
||||
'CHECK (status != \'runningTimer\' OR started_at IS NOT NULL)',
|
||||
'CHECK (accumulated_ms >= 0)',
|
||||
];
|
||||
}
|
||||
|
||||
class ActiveExerciseStepResults extends SyncableTable {
|
||||
@override
|
||||
String get tableName => 'active_exercise_step_results';
|
||||
|
||||
TextColumn get activeWorkoutSessionId =>
|
||||
text().references(ActiveWorkoutSessions, #id)();
|
||||
TextColumn get programSnapshotId => text().withLength(min: 1)();
|
||||
TextColumn get exerciseSnapshotId => text().withLength(min: 1)();
|
||||
IntColumn get programIndex => integer()();
|
||||
IntColumn get exerciseIndex => integer()();
|
||||
IntColumn get setIndex => integer()();
|
||||
IntColumn get passageIndex => integer()();
|
||||
IntColumn get stepIndex => integer()();
|
||||
TextColumn get stepSnapshotId => text().withLength(min: 1)();
|
||||
TextColumn get stepNameSnapshot => text().withLength(min: 1)();
|
||||
TextColumn get stepTypeSnapshot => text()();
|
||||
IntColumn get targetValueSnapshot => integer()();
|
||||
BoolColumn get hasScoreSnapshot => boolean()();
|
||||
TextColumn get scoreInputModeSnapshot => text().nullable()();
|
||||
TextColumn get scoreLabelSnapshot => text().nullable()();
|
||||
TextColumn get scoreUnitSnapshot => text().nullable()();
|
||||
RealColumn get targetScoreSnapshot => real().nullable()();
|
||||
IntColumn get targetScoreTimeMsSnapshot => integer().nullable()();
|
||||
TextColumn get status => text()();
|
||||
DateTimeColumn get startedAt => dateTime().nullable()();
|
||||
DateTimeColumn get completedAt => dateTime().nullable()();
|
||||
IntColumn get actualTimeMs => integer().nullable()();
|
||||
IntColumn get actualReps => integer().nullable()();
|
||||
RealColumn get actualScore => real().nullable()();
|
||||
IntColumn get actualScoreTimeMs => integer().nullable()();
|
||||
TextColumn get note => text().nullable()();
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => [
|
||||
'UNIQUE (active_workout_session_id, program_index, exercise_index, '
|
||||
'set_index, passage_index, step_index)',
|
||||
'CHECK (program_index >= 0)',
|
||||
'CHECK (exercise_index >= 0)',
|
||||
'CHECK (set_index >= 0)',
|
||||
'CHECK (passage_index >= 0)',
|
||||
'CHECK (step_index >= 0)',
|
||||
"CHECK (step_type_snapshot IN ('time', 'reps'))",
|
||||
'CHECK (target_value_snapshot > 0)',
|
||||
"CHECK (score_input_mode_snapshot IS NULL OR "
|
||||
"score_input_mode_snapshot IN ('manual', 'stopwatch'))",
|
||||
"CHECK (status IN ('completed', 'skipped'))",
|
||||
'CHECK (actual_time_ms IS NULL OR actual_time_ms >= 0)',
|
||||
'CHECK (actual_reps IS NULL OR actual_reps >= 0)',
|
||||
'CHECK (actual_score IS NULL OR actual_score >= 0)',
|
||||
'CHECK (actual_score_time_ms IS NULL OR actual_score_time_ms >= 0)',
|
||||
'CHECK (actual_time_ms IS NULL OR step_type_snapshot = \'time\')',
|
||||
'CHECK (actual_reps IS NULL OR step_type_snapshot = \'reps\')',
|
||||
'CHECK (status != \'skipped\' OR (actual_time_ms IS NULL '
|
||||
'AND actual_reps IS NULL AND actual_score IS NULL '
|
||||
'AND actual_score_time_ms IS NULL))',
|
||||
'CHECK (target_score_snapshot IS NULL OR '
|
||||
"(has_score_snapshot AND score_input_mode_snapshot = 'manual'))",
|
||||
'CHECK (target_score_time_ms_snapshot IS NULL OR '
|
||||
"(has_score_snapshot AND score_input_mode_snapshot = 'stopwatch'))",
|
||||
'CHECK (actual_score IS NULL OR '
|
||||
"(has_score_snapshot AND score_input_mode_snapshot = 'manual'))",
|
||||
'CHECK (actual_score_time_ms IS NULL OR '
|
||||
"(has_score_snapshot AND score_input_mode_snapshot = 'stopwatch'))",
|
||||
'CHECK (actual_score IS NULL OR actual_score_time_ms IS NULL)',
|
||||
'CHECK (target_score_snapshot IS NULL OR '
|
||||
'target_score_time_ms_snapshot IS NULL)',
|
||||
];
|
||||
}
|
||||
|
||||
class WorkoutHistories extends SyncableTable {
|
||||
@override
|
||||
String get tableName => 'workout_history';
|
||||
@ -511,6 +613,74 @@ class WorkoutHistorySetResults extends SyncableTable {
|
||||
];
|
||||
}
|
||||
|
||||
class WorkoutHistoryStepResults extends SyncableTable {
|
||||
@override
|
||||
String get tableName => 'workout_history_step_results';
|
||||
|
||||
TextColumn get workoutHistoryId => text().references(WorkoutHistories, #id)();
|
||||
TextColumn get programSnapshotId => text().withLength(min: 1)();
|
||||
TextColumn get exerciseSnapshotId => text().withLength(min: 1)();
|
||||
IntColumn get programIndex => integer()();
|
||||
IntColumn get exerciseIndex => integer()();
|
||||
IntColumn get setIndex => integer()();
|
||||
IntColumn get passageIndex => integer()();
|
||||
IntColumn get stepIndex => integer()();
|
||||
TextColumn get stepSnapshotId => text().withLength(min: 1)();
|
||||
TextColumn get stepNameSnapshot => text().withLength(min: 1)();
|
||||
TextColumn get stepTypeSnapshot => text()();
|
||||
IntColumn get targetValueSnapshot => integer()();
|
||||
BoolColumn get hasScoreSnapshot => boolean()();
|
||||
TextColumn get scoreInputModeSnapshot => text().nullable()();
|
||||
TextColumn get scoreLabelSnapshot => text().nullable()();
|
||||
TextColumn get scoreUnitSnapshot => text().nullable()();
|
||||
RealColumn get targetScoreSnapshot => real().nullable()();
|
||||
IntColumn get targetScoreTimeMsSnapshot => integer().nullable()();
|
||||
TextColumn get status => text()();
|
||||
DateTimeColumn get startedAt => dateTime().nullable()();
|
||||
DateTimeColumn get completedAt => dateTime().nullable()();
|
||||
IntColumn get actualTimeMs => integer().nullable()();
|
||||
IntColumn get actualReps => integer().nullable()();
|
||||
RealColumn get actualScore => real().nullable()();
|
||||
IntColumn get actualScoreTimeMs => integer().nullable()();
|
||||
TextColumn get note => text().nullable()();
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => [
|
||||
'UNIQUE (workout_history_id, program_index, exercise_index, set_index, '
|
||||
'passage_index, step_index)',
|
||||
'CHECK (program_index >= 0)',
|
||||
'CHECK (exercise_index >= 0)',
|
||||
'CHECK (set_index >= 0)',
|
||||
'CHECK (passage_index >= 0)',
|
||||
'CHECK (step_index >= 0)',
|
||||
"CHECK (step_type_snapshot IN ('time', 'reps'))",
|
||||
'CHECK (target_value_snapshot > 0)',
|
||||
"CHECK (score_input_mode_snapshot IS NULL OR "
|
||||
"score_input_mode_snapshot IN ('manual', 'stopwatch'))",
|
||||
"CHECK (status IN ('completed', 'skipped'))",
|
||||
'CHECK (actual_time_ms IS NULL OR actual_time_ms >= 0)',
|
||||
'CHECK (actual_reps IS NULL OR actual_reps >= 0)',
|
||||
'CHECK (actual_score IS NULL OR actual_score >= 0)',
|
||||
'CHECK (actual_score_time_ms IS NULL OR actual_score_time_ms >= 0)',
|
||||
'CHECK (actual_time_ms IS NULL OR step_type_snapshot = \'time\')',
|
||||
'CHECK (actual_reps IS NULL OR step_type_snapshot = \'reps\')',
|
||||
'CHECK (status != \'skipped\' OR (actual_time_ms IS NULL '
|
||||
'AND actual_reps IS NULL AND actual_score IS NULL '
|
||||
'AND actual_score_time_ms IS NULL))',
|
||||
'CHECK (target_score_snapshot IS NULL OR '
|
||||
"(has_score_snapshot AND score_input_mode_snapshot = 'manual'))",
|
||||
'CHECK (target_score_time_ms_snapshot IS NULL OR '
|
||||
"(has_score_snapshot AND score_input_mode_snapshot = 'stopwatch'))",
|
||||
'CHECK (actual_score IS NULL OR '
|
||||
"(has_score_snapshot AND score_input_mode_snapshot = 'manual'))",
|
||||
'CHECK (actual_score_time_ms IS NULL OR '
|
||||
"(has_score_snapshot AND score_input_mode_snapshot = 'stopwatch'))",
|
||||
'CHECK (actual_score IS NULL OR actual_score_time_ms IS NULL)',
|
||||
'CHECK (target_score_snapshot IS NULL OR '
|
||||
'target_score_time_ms_snapshot IS NULL)',
|
||||
];
|
||||
}
|
||||
|
||||
class ChangeLogEntries extends Table {
|
||||
@override
|
||||
String get tableName => 'change_log';
|
||||
|
||||
Reference in New Issue
Block a user