feat(domain): fondation domain du score chronométré (ticket #25)

Étend le modèle Drift (tables.dart, app_database.dart/.g.dart), les
entités du domaine, les ports et use cases pour poser les fondations
du score chronométré. build_runner OK, flutter analyze propre, 39/39
tests verts, build APK debug validé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 01:10:14 +02:00
parent c88c4e876a
commit d521bdffb6
10 changed files with 3628 additions and 15 deletions

View File

@ -8,6 +8,7 @@ part 'app_database.g.dart';
@DriftDatabase(
tables: [
ActiveRestStates,
ActiveScoreStopwatchStates,
ActiveSetResults,
ActiveWorkoutSessions,
ChangeLogEntries,
@ -35,7 +36,7 @@ final class AppDatabase extends _$AppDatabase {
}
@override
int get schemaVersion => 2;
int get schemaVersion => 3;
@override
MigrationStrategy get migration {
@ -56,6 +57,10 @@ final class AppDatabase extends _$AppDatabase {
"'skipped'))",
);
}
if (from < 3) {
await _migrateToSchema3(migrator);
}
await _createIndexes();
},
beforeOpen: (details) async {
await customStatement('PRAGMA foreign_keys = ON');
@ -95,6 +100,10 @@ final class AppDatabase extends _$AppDatabase {
'CREATE INDEX IF NOT EXISTS idx_active_set_results_session_id '
'ON active_set_results (active_workout_session_id)',
);
await customStatement(
'CREATE INDEX IF NOT EXISTS idx_active_score_stopwatch_states_session_id '
'ON active_score_stopwatch_states (active_workout_session_id)',
);
await customStatement(
'CREATE INDEX IF NOT EXISTS idx_active_rest_states_session_id '
'ON active_rest_states (active_workout_session_id)',
@ -131,6 +140,7 @@ final class AppDatabase extends _$AppDatabase {
const _syncableTableNames = [
'active_rest_states',
'active_score_stopwatch_states',
'active_set_results',
'active_workout_sessions',
'exercises',
@ -143,3 +153,53 @@ const _syncableTableNames = [
'workout_template_programs',
'workout_templates',
];
extension on AppDatabase {
Future<void> _migrateToSchema3(Migrator migrator) async {
await customStatement(
'ALTER TABLE exercises ADD COLUMN score_input_mode TEXT NOT NULL '
"DEFAULT 'manual' CHECK (score_input_mode IN ('manual', 'stopwatch'))",
);
await customStatement(
'ALTER TABLE program_exercises ADD COLUMN score_input_mode_snapshot TEXT '
"NOT NULL DEFAULT 'manual' CHECK (score_input_mode_snapshot IN "
"('manual', 'stopwatch'))",
);
await customStatement(
'ALTER TABLE program_exercises ADD COLUMN target_score_time_ms INTEGER '
'CHECK (target_score_time_ms IS NULL OR target_score_time_ms > 0)',
);
await customStatement(
'ALTER TABLE workout_template_exercise_overrides ADD COLUMN '
'target_score_time_ms_override INTEGER CHECK '
'(target_score_time_ms_override IS NULL OR '
'target_score_time_ms_override > 0)',
);
await customStatement(
'ALTER TABLE active_set_results ADD COLUMN actual_score_time_ms INTEGER '
'CHECK (actual_score_time_ms IS NULL OR actual_score_time_ms >= 0)',
);
await customStatement(
'ALTER TABLE active_set_results ADD COLUMN score_input_mode_snapshot '
"TEXT NOT NULL DEFAULT 'manual' CHECK (score_input_mode_snapshot IN "
"('manual', 'stopwatch'))",
);
await customStatement(
'ALTER TABLE workout_history_set_results ADD COLUMN '
'score_input_mode_snapshot TEXT NOT NULL DEFAULT '
"'manual' CHECK (score_input_mode_snapshot IN ('manual', 'stopwatch'))",
);
await customStatement(
'ALTER TABLE workout_history_set_results ADD COLUMN '
'target_score_time_ms_snapshot INTEGER CHECK '
'(target_score_time_ms_snapshot IS NULL OR '
'target_score_time_ms_snapshot > 0)',
);
await customStatement(
'ALTER TABLE workout_history_set_results ADD COLUMN '
'actual_score_time_ms INTEGER CHECK (actual_score_time_ms IS NULL OR '
'actual_score_time_ms >= 0)',
);
await migrator.createTable(activeScoreStopwatchStates);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -469,6 +469,77 @@ final class DriftActiveSessionRepository implements ActiveSessionRepository {
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> 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<List<domain.ActiveSetResult>> listSetResults(String sessionId) async {
final rows =
@ -492,6 +563,26 @@ final class DriftActiveSessionRepository implements ActiveSessionRepository {
.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 {
@ -872,6 +963,7 @@ db.ExercisesCompanion _exerciseCompanion(domain.Exercise exercise) {
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),
archivedAt: Value(_utcOrNull(exercise.archivedAt)),
@ -888,6 +980,7 @@ domain.Exercise _exerciseFromRow(db.Exercise row) {
hasTimeMeasure: row.hasTimeMeasure,
hasRepsMeasure: row.hasRepsMeasure,
hasScoreMeasure: row.hasScoreMeasure,
scoreInputMode: _scoreInputModeFromDb(row.scoreInputMode),
scoreLabel: row.scoreLabel,
scoreUnit: row.scoreUnit,
archivedAt: _utcOrNull(row.archivedAt),
@ -995,6 +1088,9 @@ db.ProgramExercisesCompanion _programExerciseCompanion(
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),
@ -1004,6 +1100,7 @@ db.ProgramExercisesCompanion _programExerciseCompanion(
targetTimeSeconds: Value(exercise.targetTimeSeconds),
targetReps: Value(exercise.targetReps),
targetScore: Value(exercise.targetScore),
targetScoreTimeMs: Value(exercise.targetScoreTimeMs),
restSecondsOverride: Value(exercise.restSecondsOverride),
);
}
@ -1022,6 +1119,7 @@ domain.ProgramExercise _programExerciseFromRow(db.ProgramExercise row) {
availableTimeSnapshot: row.availableTimeSnapshot,
availableRepsSnapshot: row.availableRepsSnapshot,
availableScoreSnapshot: row.availableScoreSnapshot,
scoreInputModeSnapshot: _scoreInputModeFromDb(row.scoreInputModeSnapshot),
scoreLabelSnapshot: row.scoreLabelSnapshot,
scoreUnitSnapshot: row.scoreUnitSnapshot,
setsCount: row.setsCount,
@ -1031,6 +1129,7 @@ domain.ProgramExercise _programExerciseFromRow(db.ProgramExercise row) {
targetTimeSeconds: row.targetTimeSeconds,
targetReps: row.targetReps,
targetScore: row.targetScore,
targetScoreTimeMs: row.targetScoreTimeMs,
restSecondsOverride: row.restSecondsOverride,
);
}
@ -1132,6 +1231,7 @@ _workoutTemplateExerciseOverrideCompanion(
targetTimeSecondsOverride: Value(override.targetTimeSecondsOverride),
targetRepsOverride: Value(override.targetRepsOverride),
targetScoreOverride: Value(override.targetScoreOverride),
targetScoreTimeMsOverride: Value(override.targetScoreTimeMsOverride),
);
}
@ -1146,6 +1246,7 @@ domain.WorkoutTemplateExerciseOverride _workoutTemplateExerciseOverrideFromRow(
targetTimeSecondsOverride: row.targetTimeSecondsOverride,
targetRepsOverride: row.targetRepsOverride,
targetScoreOverride: row.targetScoreOverride,
targetScoreTimeMsOverride: row.targetScoreTimeMsOverride,
);
}
@ -1225,6 +1326,10 @@ db.ActiveSetResultsCompanion _activeSetResultCompanion(
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),
@ -1246,6 +1351,8 @@ domain.ActiveSetResult _activeSetResultFromRow(db.ActiveSetResult row) {
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,
@ -1296,6 +1403,49 @@ domain.ActiveRestState _activeRestStateFromRow(db.ActiveRestState row) {
);
}
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.WorkoutHistoriesCompanion _workoutHistoryCompanion(
domain.WorkoutHistory history,
) {
@ -1350,12 +1500,17 @@ db.WorkoutHistorySetResultsCompanion _workoutHistorySetResultCompanion(
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)),
@ -1398,12 +1553,15 @@ domain.WorkoutHistorySetResult _workoutHistorySetResultFromRow(
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),
@ -1446,6 +1604,17 @@ domain.MediaKind _mediaKindFromDb(String value) => switch (value) {
_ => 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 _setResultStatusToDb(domain.SetResultStatus status) => switch (status) {
domain.SetResultStatus.completed => 'completed',
domain.SetResultStatus.skipped => 'skipped',
@ -1457,6 +1626,21 @@ domain.SetResultStatus _setResultStatusFromDb(String value) => switch (value) {
_ => 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',
),
};
domain.ActiveWorkoutStatus _activeStatusFromDb(String value) => switch (value) {
'running' => domain.ActiveWorkoutStatus.running,
'paused' => domain.ActiveWorkoutStatus.paused,

View File

@ -65,6 +65,8 @@ class Exercises extends SyncableTable {
BoolColumn get hasTimeMeasure => boolean()();
BoolColumn get hasRepsMeasure => boolean()();
BoolColumn get hasScoreMeasure => boolean()();
TextColumn get scoreInputMode =>
text().withDefault(const Constant('manual'))();
TextColumn get scoreLabel => text().nullable()();
TextColumn get scoreUnit => text().nullable()();
DateTimeColumn get archivedAt => dateTime().nullable()();
@ -72,9 +74,11 @@ class Exercises extends SyncableTable {
@override
List<String> get customConstraints => [
'CHECK (has_time_measure OR has_reps_measure OR has_score_measure)',
'CHECK (NOT has_score_measure OR (score_label IS NOT NULL '
'AND length(trim(score_label)) > 0 AND score_unit IS NOT NULL '
'AND length(trim(score_unit)) > 0))',
"CHECK (score_input_mode IN ('manual', 'stopwatch'))",
"CHECK (has_score_measure OR score_input_mode = 'manual')",
"CHECK (score_input_mode != 'manual' OR NOT has_score_measure OR "
'(score_label IS NOT NULL AND length(trim(score_label)) > 0 '
'AND score_unit IS NOT NULL AND length(trim(score_unit)) > 0))',
];
}
@ -111,6 +115,8 @@ class ProgramExercises extends SyncableTable {
BoolColumn get availableTimeSnapshot => boolean()();
BoolColumn get availableRepsSnapshot => boolean()();
BoolColumn get availableScoreSnapshot => boolean()();
TextColumn get scoreInputModeSnapshot =>
text().withDefault(const Constant('manual'))();
TextColumn get scoreLabelSnapshot => text().nullable()();
TextColumn get scoreUnitSnapshot => text().nullable()();
IntColumn get setsCount => integer()();
@ -120,6 +126,7 @@ class ProgramExercises extends SyncableTable {
IntColumn get targetTimeSeconds => integer().nullable()();
IntColumn get targetReps => integer().nullable()();
RealColumn get targetScore => real().nullable()();
IntColumn get targetScoreTimeMs => integer().nullable()();
IntColumn get restSecondsOverride => integer().nullable()();
@override
@ -134,11 +141,18 @@ class ProgramExercises extends SyncableTable {
'CHECK (target_time_seconds IS NULL OR target_time_seconds > 0)',
'CHECK (target_reps IS NULL OR target_reps > 0)',
'CHECK (target_score IS NULL OR target_score >= 0)',
'CHECK (target_score_time_ms IS NULL OR target_score_time_ms > 0)',
'CHECK (rest_seconds_override IS NULL OR rest_seconds_override >= 0)',
'CHECK (target_time_seconds IS NULL OR time_enabled)',
'CHECK (target_reps IS NULL OR reps_enabled)',
'CHECK (target_score IS NULL OR score_enabled)',
'CHECK (NOT available_score_snapshot OR (score_label_snapshot '
"CHECK (score_input_mode_snapshot IN ('manual', 'stopwatch'))",
'CHECK (target_score IS NULL OR '
"(score_enabled AND score_input_mode_snapshot = 'manual'))",
'CHECK (target_score_time_ms IS NULL OR '
"(score_enabled AND score_input_mode_snapshot = 'stopwatch'))",
'CHECK (target_score IS NULL OR target_score_time_ms IS NULL)',
"CHECK (score_input_mode_snapshot != 'manual' OR "
'NOT available_score_snapshot OR (score_label_snapshot '
'IS NOT NULL AND length(trim(score_label_snapshot)) > 0 '
'AND score_unit_snapshot IS NOT NULL '
'AND length(trim(score_unit_snapshot)) > 0))',
@ -185,6 +199,7 @@ class WorkoutTemplateExerciseOverrides extends SyncableTable {
IntColumn get targetTimeSecondsOverride => integer().nullable()();
IntColumn get targetRepsOverride => integer().nullable()();
RealColumn get targetScoreOverride => real().nullable()();
IntColumn get targetScoreTimeMsOverride => integer().nullable()();
@override
List<String> get customConstraints => [
@ -194,6 +209,10 @@ class WorkoutTemplateExerciseOverrides extends SyncableTable {
'target_time_seconds_override > 0)',
'CHECK (target_reps_override IS NULL OR target_reps_override > 0)',
'CHECK (target_score_override IS NULL OR target_score_override >= 0)',
'CHECK (target_score_time_ms_override IS NULL OR '
'target_score_time_ms_override > 0)',
'CHECK (target_score_override IS NULL OR '
'target_score_time_ms_override IS NULL)',
];
}
@ -241,6 +260,9 @@ class ActiveSetResults extends SyncableTable {
IntColumn get actualTimeMs => integer().nullable()();
IntColumn get actualReps => integer().nullable()();
RealColumn get actualScore => real().nullable()();
IntColumn get actualScoreTimeMs => integer().nullable()();
TextColumn get scoreInputModeSnapshot =>
text().withDefault(const Constant('manual'))();
TextColumn get scoreLabelSnapshot => text().nullable()();
TextColumn get scoreUnitSnapshot => text().nullable()();
TextColumn get note => text().nullable()();
@ -256,9 +278,17 @@ class ActiveSetResults extends SyncableTable {
'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 (status IN ('completed', 'skipped'))",
'CHECK (status != \'skipped\' OR (actual_time_ms IS NULL '
'AND actual_reps IS NULL AND actual_score IS NULL))',
'AND actual_reps IS NULL AND actual_score IS NULL '
'AND actual_score_time_ms IS NULL))',
"CHECK (score_input_mode_snapshot IN ('manual', 'stopwatch'))",
'CHECK (actual_score IS NULL OR '
"score_input_mode_snapshot = 'manual')",
'CHECK (actual_score_time_ms IS NULL OR '
"score_input_mode_snapshot = 'stopwatch')",
'CHECK (actual_score IS NULL OR actual_score_time_ms IS NULL)',
'CHECK (actual_score IS NULL OR (score_label_snapshot IS NOT NULL '
'AND length(trim(score_label_snapshot)) > 0 '
'AND score_unit_snapshot IS NOT NULL '
@ -266,6 +296,32 @@ class ActiveSetResults extends SyncableTable {
];
}
class ActiveScoreStopwatchStates extends SyncableTable {
@override
String get tableName => 'active_score_stopwatch_states';
TextColumn get activeWorkoutSessionId =>
text().references(ActiveWorkoutSessions, #id)();
IntColumn get programIndex => integer()();
IntColumn get exerciseIndex => integer()();
IntColumn get setIndex => integer()();
TextColumn get status => text()();
DateTimeColumn get startedAt => dateTime()();
IntColumn get accumulatedMs => integer()();
DateTimeColumn get stoppedAt => dateTime().nullable()();
@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 (status IN ('running', 'stopped'))",
'CHECK (accumulated_ms >= 0)',
];
}
class ActiveRestStates extends SyncableTable {
@override
String get tableName => 'active_rest_states';
@ -326,12 +382,16 @@ class WorkoutHistorySetResults extends SyncableTable {
BoolColumn get timeEnabledSnapshot => boolean()();
BoolColumn get repsEnabledSnapshot => boolean()();
BoolColumn get scoreEnabledSnapshot => boolean()();
TextColumn get scoreInputModeSnapshot =>
text().withDefault(const Constant('manual'))();
IntColumn get targetTimeSecondsSnapshot => integer().nullable()();
IntColumn get targetRepsSnapshot => integer().nullable()();
RealColumn get targetScoreSnapshot => real().nullable()();
IntColumn get targetScoreTimeMsSnapshot => integer().nullable()();
IntColumn get actualTimeMs => integer().nullable()();
IntColumn get actualReps => integer().nullable()();
RealColumn get actualScore => real().nullable()();
IntColumn get actualScoreTimeMs => integer().nullable()();
TextColumn get scoreLabelSnapshot => text().nullable()();
TextColumn get scoreUnitSnapshot => text().nullable()();
DateTimeColumn get startedAt => dateTime().nullable()();
@ -351,12 +411,28 @@ class WorkoutHistorySetResults extends SyncableTable {
'target_time_seconds_snapshot > 0)',
'CHECK (target_reps_snapshot IS NULL OR target_reps_snapshot > 0)',
'CHECK (target_score_snapshot IS NULL OR target_score_snapshot >= 0)',
'CHECK (target_score_time_ms_snapshot IS NULL OR '
'target_score_time_ms_snapshot > 0)',
'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 (status IN ('completed', 'skipped'))",
'CHECK (status != \'skipped\' OR (actual_time_ms IS NULL '
'AND actual_reps IS NULL AND actual_score IS NULL))',
'AND actual_reps IS NULL AND actual_score IS NULL '
'AND actual_score_time_ms IS NULL))',
"CHECK (score_input_mode_snapshot IN ('manual', 'stopwatch'))",
'CHECK (target_score_snapshot IS NULL OR '
"(score_enabled_snapshot AND score_input_mode_snapshot = 'manual'))",
'CHECK (target_score_time_ms_snapshot IS NULL OR '
"(score_enabled_snapshot AND score_input_mode_snapshot = 'stopwatch'))",
'CHECK (target_score_snapshot IS NULL OR '
'target_score_time_ms_snapshot IS NULL)',
'CHECK (actual_score IS NULL OR '
"score_input_mode_snapshot = 'manual')",
'CHECK (actual_score_time_ms IS NULL OR '
"score_input_mode_snapshot = 'stopwatch')",
'CHECK (actual_score IS NULL OR actual_score_time_ms IS NULL)',
'CHECK (actual_score IS NULL OR (score_label_snapshot IS NOT NULL '
'AND length(trim(score_label_snapshot)) > 0 '
'AND score_unit_snapshot IS NOT NULL '