Réorganise l'écran d'exécution autour d'une carte "Exercice actif" sous le compteur SÉRIE X/Y (nom, médias, temps de série, action "Démarrer l'exercice"). Le timer de série devient un état persistant dédié (plus un DateTime UI volatile), pause-aware, et "Démarrer l'exercice" lance en une action tous les chronos qui doivent démarrer en début de série (série, première étape temps, score chrono si stopwatch). Implémentation restée non commitée depuis sa réalisation ; QA a validé les vérifications statiques exécutables en sandbox (dart analyze, git diff --check). flutter analyze / flutter test restent à relancer dans un environnement avec cache Flutter SDK accessible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
865 lines
36 KiB
Dart
865 lines
36 KiB
Dart
import 'package:drift/drift.dart';
|
|
|
|
abstract class SyncableTable extends Table {
|
|
TextColumn get id => text()();
|
|
DateTimeColumn get createdAt => dateTime()();
|
|
DateTimeColumn get updatedAt => dateTime()();
|
|
DateTimeColumn get deletedAt => dateTime().nullable()();
|
|
IntColumn get schemaVersion => integer().withDefault(const Constant(1))();
|
|
TextColumn get syncState => text().customConstraint(
|
|
"NOT NULL CHECK (sync_state IN ('localOnly', 'dirty', 'synced', "
|
|
"'deleted'))",
|
|
)();
|
|
IntColumn get localRevision =>
|
|
integer().customConstraint('NOT NULL CHECK (local_revision >= 0)')();
|
|
TextColumn get originDeviceId => text().withLength(min: 1)();
|
|
TextColumn get futureOwnerProfileId => text().nullable()();
|
|
DateTimeColumn get lastSyncedAt => dateTime().nullable()();
|
|
TextColumn get remoteRevision => text().nullable()();
|
|
|
|
@override
|
|
Set<Column> get primaryKey => {id};
|
|
|
|
@override
|
|
List<String> get customConstraints => const [];
|
|
}
|
|
|
|
class OnlineAccountSessions extends Table {
|
|
@override
|
|
String get tableName => 'online_account_sessions';
|
|
|
|
TextColumn get id => text()();
|
|
TextColumn get serverUserId => text().withLength(min: 1)();
|
|
TextColumn get email => text().withLength(min: 1)();
|
|
TextColumn get displayName => text().nullable()();
|
|
BoolColumn get isLoggedIn => boolean()();
|
|
DateTimeColumn get createdAt => dateTime()();
|
|
DateTimeColumn get updatedAt => dateTime()();
|
|
DateTimeColumn get lastAuthenticatedAt => dateTime().nullable()();
|
|
|
|
@override
|
|
Set<Column> get primaryKey => {id};
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
'UNIQUE (server_user_id)',
|
|
'CHECK (length(trim(email)) > 0)',
|
|
'CHECK (display_name IS NULL OR length(trim(display_name)) > 0)',
|
|
];
|
|
}
|
|
|
|
class SyncMetadataEntries extends Table {
|
|
@override
|
|
String get tableName => 'sync_metadata';
|
|
|
|
TextColumn get id => text().withDefault(const Constant('singleton'))();
|
|
TextColumn get serverCursor => text().nullable()();
|
|
DateTimeColumn get lastSuccessfulSyncAt => dateTime().nullable()();
|
|
DateTimeColumn get lastAttemptAt => dateTime().nullable()();
|
|
DateTimeColumn get lastFailureAt => dateTime().nullable()();
|
|
TextColumn get status => text().withDefault(const Constant('idle'))();
|
|
IntColumn get pendingPushCount => integer().nullable()();
|
|
|
|
@override
|
|
Set<Column> get primaryKey => {id};
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
"CHECK (id = 'singleton')",
|
|
"CHECK (status IN ('idle', 'syncing', 'success', 'failure'))",
|
|
'CHECK (pending_push_count IS NULL OR pending_push_count >= 0)',
|
|
];
|
|
}
|
|
|
|
class RemoteResourceMappings extends Table {
|
|
@override
|
|
String get tableName => 'remote_resource_mappings';
|
|
|
|
TextColumn get id => text()();
|
|
TextColumn get resourceType => text()();
|
|
TextColumn get clientId => text().withLength(min: 1)();
|
|
TextColumn get serverId => text().withLength(min: 1)();
|
|
DateTimeColumn get serverUpdatedAt => dateTime()();
|
|
|
|
@override
|
|
Set<Column> get primaryKey => {id};
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
"CHECK (resource_type IN ('exercise', 'program', 'workoutTemplate', "
|
|
"'workoutHistory', 'mediaAsset'))",
|
|
'UNIQUE (resource_type, client_id)',
|
|
];
|
|
}
|
|
|
|
class ShareInboxItems extends Table {
|
|
@override
|
|
String get tableName => 'share_inbox_items';
|
|
|
|
TextColumn get shareId => text()();
|
|
TextColumn get senderUserId => text().withLength(min: 1)();
|
|
TextColumn get resourceType => text()();
|
|
TextColumn get payloadJson => text().withLength(min: 1)();
|
|
TextColumn get status => text()();
|
|
DateTimeColumn get createdAt => dateTime()();
|
|
DateTimeColumn get updatedAt => dateTime()();
|
|
DateTimeColumn get respondedAt => dateTime().nullable()();
|
|
|
|
@override
|
|
Set<Column> get primaryKey => {shareId};
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
"CHECK (resource_type IN ('program', 'workoutTemplate'))",
|
|
"CHECK (status IN ('pending', 'accepted', 'declined', 'revoked'))",
|
|
];
|
|
}
|
|
|
|
class PendingShareActions extends Table {
|
|
@override
|
|
String get tableName => 'pending_share_actions';
|
|
|
|
TextColumn get id => text()();
|
|
TextColumn get actionType => text()();
|
|
TextColumn get shareId => text().nullable()();
|
|
TextColumn get resourceType => text().nullable()();
|
|
TextColumn get payloadJson => text().nullable()();
|
|
TextColumn get recipientEmailsJson => text().nullable()();
|
|
DateTimeColumn get createdAt => dateTime()();
|
|
DateTimeColumn get lastAttemptAt => dateTime().nullable()();
|
|
IntColumn get attemptCount =>
|
|
integer().customConstraint('NOT NULL CHECK (attempt_count >= 0)')();
|
|
TextColumn get status => text()();
|
|
|
|
@override
|
|
Set<Column> get primaryKey => {id};
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
"CHECK (action_type IN ('send', 'accept', 'decline', 'revoke'))",
|
|
"CHECK (resource_type IS NULL OR resource_type IN "
|
|
"('program', 'workoutTemplate'))",
|
|
"CHECK (status IN ('pending', 'succeeded', 'failed'))",
|
|
];
|
|
}
|
|
|
|
class MediaAssets extends SyncableTable {
|
|
@override
|
|
String get tableName => 'media_assets';
|
|
|
|
TextColumn get kind => text()();
|
|
TextColumn get localUri => text().withLength(min: 1)();
|
|
TextColumn get mimeType => text().nullable()();
|
|
IntColumn get sizeBytes => integer().nullable()();
|
|
IntColumn get width => integer().nullable()();
|
|
IntColumn get height => integer().nullable()();
|
|
IntColumn get durationMs => integer().nullable()();
|
|
TextColumn get checksum => text().nullable()();
|
|
TextColumn get remoteUri => text().nullable()();
|
|
TextColumn get thumbnailLocalUri => text().nullable()();
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
"CHECK (kind IN ('image', 'video'))",
|
|
'CHECK (size_bytes IS NULL OR size_bytes >= 0)',
|
|
'CHECK (width IS NULL OR width > 0)',
|
|
'CHECK (height IS NULL OR height > 0)',
|
|
'CHECK (duration_ms IS NULL OR duration_ms >= 0)',
|
|
];
|
|
}
|
|
|
|
class Exercises extends SyncableTable {
|
|
@override
|
|
String get tableName => 'exercises';
|
|
|
|
TextColumn get name => text().withLength(min: 1)();
|
|
TextColumn get description => text().nullable()();
|
|
@ReferenceName('exerciseIconReferences')
|
|
TextColumn get iconMediaId =>
|
|
text().nullable().references(MediaAssets, #id)();
|
|
@ReferenceName('exerciseVideoReferences')
|
|
TextColumn get videoMediaId =>
|
|
text().nullable().references(MediaAssets, #id)();
|
|
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()();
|
|
IntColumn get defaultTargetTimeSeconds => integer().nullable()();
|
|
IntColumn get defaultTargetReps => integer().nullable()();
|
|
RealColumn get defaultTargetScore => real().nullable()();
|
|
IntColumn get defaultTargetScoreTimeMs => integer().nullable()();
|
|
BoolColumn get autoStartNextTimedStep =>
|
|
boolean().withDefault(const Constant(true))();
|
|
DateTimeColumn get archivedAt => dateTime().nullable()();
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
'CHECK (has_time_measure OR has_reps_measure OR has_score_measure)',
|
|
"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))',
|
|
'CHECK (default_target_time_seconds IS NULL OR '
|
|
'default_target_time_seconds > 0)',
|
|
'CHECK (default_target_reps IS NULL OR default_target_reps > 0)',
|
|
'CHECK (default_target_score IS NULL OR default_target_score >= 0)',
|
|
'CHECK (default_target_score_time_ms IS NULL OR '
|
|
'default_target_score_time_ms > 0)',
|
|
];
|
|
}
|
|
|
|
class ExerciseImages extends SyncableTable {
|
|
@override
|
|
String get tableName => 'exercise_images';
|
|
|
|
TextColumn get exerciseId => text().references(Exercises, #id)();
|
|
TextColumn get mediaAssetId => text().references(MediaAssets, #id)();
|
|
IntColumn get position => integer()();
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
'UNIQUE (exercise_id, position)',
|
|
'UNIQUE (exercise_id, media_asset_id)',
|
|
'CHECK (position >= 0 AND position < 5)',
|
|
];
|
|
}
|
|
|
|
class ExerciseSteps extends SyncableTable {
|
|
@override
|
|
String get tableName => 'exercise_steps';
|
|
|
|
TextColumn get exerciseId => text().references(Exercises, #id)();
|
|
IntColumn get position => integer()();
|
|
TextColumn get name => text().withLength(min: 1)();
|
|
TextColumn get type => text()();
|
|
IntColumn get defaultTargetValue => integer()();
|
|
BoolColumn get hasScore => boolean()();
|
|
TextColumn get scoreInputMode => text().nullable()();
|
|
TextColumn get scoreLabel => text().nullable()();
|
|
TextColumn get scoreUnit => text().nullable()();
|
|
RealColumn get defaultTargetScore => real().nullable()();
|
|
IntColumn get defaultTargetScoreTimeMs => integer().nullable()();
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
"CHECK (type IN ('time', 'reps'))",
|
|
'CHECK (position >= 0 AND position < 8)',
|
|
'CHECK (default_target_value > 0)',
|
|
"CHECK (score_input_mode IS NULL OR score_input_mode IN ('manual', "
|
|
"'stopwatch'))",
|
|
'CHECK (has_score OR (score_input_mode IS NULL '
|
|
'AND score_label IS NULL AND score_unit IS NULL '
|
|
'AND default_target_score IS NULL '
|
|
'AND default_target_score_time_ms IS NULL))',
|
|
'CHECK (NOT has_score OR score_input_mode IS NOT NULL)',
|
|
"CHECK (NOT has_score OR score_input_mode != 'manual' 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 (default_target_score IS NULL OR default_target_score >= 0)',
|
|
'CHECK (default_target_score_time_ms IS NULL OR '
|
|
'default_target_score_time_ms > 0)',
|
|
"CHECK (score_input_mode != 'manual' OR "
|
|
'default_target_score_time_ms IS NULL)',
|
|
"CHECK (score_input_mode != 'stopwatch' OR "
|
|
'(score_label IS NULL AND score_unit IS NULL '
|
|
'AND default_target_score IS NULL))',
|
|
'CHECK (default_target_score IS NULL OR '
|
|
'default_target_score_time_ms IS NULL)',
|
|
];
|
|
}
|
|
|
|
class Programs extends SyncableTable {
|
|
@override
|
|
String get tableName => 'programs';
|
|
|
|
TextColumn get name => text().withLength(min: 1)();
|
|
IntColumn get defaultRestSeconds => integer()();
|
|
|
|
@override
|
|
List<String> get customConstraints => ['CHECK (default_rest_seconds >= 0)'];
|
|
}
|
|
|
|
class ProgramExercises extends SyncableTable {
|
|
@override
|
|
String get tableName => 'program_exercises';
|
|
|
|
TextColumn get programId => text().references(Programs, #id)();
|
|
TextColumn get sourceExerciseId =>
|
|
text().nullable().references(Exercises, #id)();
|
|
IntColumn get position => integer()();
|
|
TextColumn get exerciseNameSnapshot => text().withLength(min: 1)();
|
|
TextColumn get exerciseDescriptionSnapshot => text().nullable()();
|
|
@ReferenceName('programExerciseImageSnapshotReferences')
|
|
TextColumn get exerciseImageMediaIdSnapshot =>
|
|
text().nullable().references(MediaAssets, #id)();
|
|
TextColumn get exerciseImageMediaIdsSnapshotJson => text().nullable()();
|
|
TextColumn get exerciseStepsSnapshotJson => text().nullable()();
|
|
BoolColumn get autoStartNextTimedStepSnapshot =>
|
|
boolean().withDefault(const Constant(true))();
|
|
BoolColumn get autoStartNextTimedStepOverride => boolean().nullable()();
|
|
|
|
@ReferenceName('programExerciseVideoSnapshotReferences')
|
|
TextColumn get exerciseVideoMediaIdSnapshot =>
|
|
text().nullable().references(MediaAssets, #id)();
|
|
BoolColumn get exerciseArchivedSnapshot =>
|
|
boolean().withDefault(const Constant(false))();
|
|
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()();
|
|
BoolColumn get timeEnabled => boolean()();
|
|
BoolColumn get repsEnabled => boolean()();
|
|
BoolColumn get scoreEnabled => boolean()();
|
|
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
|
|
List<String> get customConstraints => [
|
|
'UNIQUE (program_id, position)',
|
|
'CHECK (position >= 0)',
|
|
'CHECK (sets_count > 0)',
|
|
'CHECK (time_enabled OR reps_enabled OR score_enabled)',
|
|
'CHECK (NOT time_enabled OR available_time_snapshot)',
|
|
'CHECK (NOT reps_enabled OR available_reps_snapshot)',
|
|
'CHECK (NOT score_enabled OR available_score_snapshot)',
|
|
'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 (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))',
|
|
];
|
|
}
|
|
|
|
class WorkoutTemplates extends SyncableTable {
|
|
@override
|
|
String get tableName => 'workout_templates';
|
|
|
|
TextColumn get name => text().withLength(min: 1)();
|
|
DateTimeColumn get lastStartedAt => dateTime().nullable()();
|
|
}
|
|
|
|
class WorkoutTemplatePrograms extends SyncableTable {
|
|
@override
|
|
String get tableName => 'workout_template_programs';
|
|
|
|
TextColumn get workoutTemplateId =>
|
|
text().references(WorkoutTemplates, #id)();
|
|
TextColumn get sourceProgramId =>
|
|
text().nullable().references(Programs, #id)();
|
|
IntColumn get position => integer()();
|
|
TextColumn get programNameSnapshot => text().withLength(min: 1)();
|
|
IntColumn get defaultRestSecondsSnapshot => integer()();
|
|
TextColumn get programSnapshotJson => text().withLength(min: 1)();
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
'UNIQUE (workout_template_id, position)',
|
|
'CHECK (position >= 0)',
|
|
'CHECK (default_rest_seconds_snapshot >= 0)',
|
|
];
|
|
}
|
|
|
|
class WorkoutTemplateExerciseOverrides extends SyncableTable {
|
|
@override
|
|
String get tableName => 'workout_template_exercise_overrides';
|
|
|
|
TextColumn get workoutTemplateProgramId =>
|
|
text().references(WorkoutTemplatePrograms, #id)();
|
|
TextColumn get snapshotProgramExerciseId => text().withLength(min: 1)();
|
|
IntColumn get setsCountOverride => integer().nullable()();
|
|
IntColumn get targetTimeSecondsOverride => integer().nullable()();
|
|
IntColumn get targetRepsOverride => integer().nullable()();
|
|
RealColumn get targetScoreOverride => real().nullable()();
|
|
IntColumn get targetScoreTimeMsOverride => integer().nullable()();
|
|
BoolColumn get autoStartNextTimedStepOverride => boolean().nullable()();
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
'UNIQUE (workout_template_program_id, snapshot_program_exercise_id)',
|
|
'CHECK (sets_count_override IS NULL OR sets_count_override > 0)',
|
|
'CHECK (target_time_seconds_override IS NULL OR '
|
|
'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)',
|
|
];
|
|
}
|
|
|
|
class ActiveWorkoutSessions extends SyncableTable {
|
|
@override
|
|
String get tableName => 'active_workout_sessions';
|
|
|
|
TextColumn get sourceWorkoutTemplateId =>
|
|
text().nullable().references(WorkoutTemplates, #id)();
|
|
TextColumn get status => text()();
|
|
DateTimeColumn get startedAt => dateTime()();
|
|
DateTimeColumn get pausedAt => dateTime().nullable()();
|
|
DateTimeColumn get endedAt => dateTime().nullable()();
|
|
DateTimeColumn get lastPersistedAt => dateTime()();
|
|
IntColumn get elapsedActiveMs => integer()();
|
|
IntColumn get currentProgramIndex => integer()();
|
|
IntColumn get currentExerciseIndex => integer()();
|
|
IntColumn get currentSetIndex => integer()();
|
|
TextColumn get resolvedTemplateSnapshotJson => text().withLength(min: 1)();
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
"CHECK (status IN ('running', 'paused', 'savedExit', 'completed', "
|
|
"'abandoned'))",
|
|
'CHECK (elapsed_active_ms >= 0)',
|
|
'CHECK (current_program_index >= 0)',
|
|
'CHECK (current_exercise_index >= 0)',
|
|
'CHECK (current_set_index >= 0)',
|
|
];
|
|
}
|
|
|
|
class ActiveSetResults extends SyncableTable {
|
|
@override
|
|
String get tableName => 'active_set_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()();
|
|
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 scoreInputModeSnapshot =>
|
|
text().withDefault(const Constant('manual'))();
|
|
TextColumn get scoreLabelSnapshot => text().nullable()();
|
|
TextColumn get scoreUnitSnapshot => text().nullable()();
|
|
TextColumn get note => text().nullable()();
|
|
TextColumn get status => text().withDefault(const Constant('completed'))();
|
|
|
|
@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 (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_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 '
|
|
'AND length(trim(score_unit_snapshot)) > 0))',
|
|
];
|
|
}
|
|
|
|
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', 'paused', 'stopped'))",
|
|
'CHECK (accumulated_ms >= 0)',
|
|
];
|
|
}
|
|
|
|
class ActiveSetTimerStates extends SyncableTable {
|
|
@override
|
|
String get tableName => 'active_set_timer_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().nullable()();
|
|
IntColumn get accumulatedMs => integer()();
|
|
DateTimeColumn get stoppedAt => dateTime().nullable()();
|
|
DateTimeColumn get skippedAt => 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', 'paused', 'stopped', 'skipped'))",
|
|
'CHECK (status != \'running\' OR started_at IS NOT NULL)',
|
|
'CHECK (accumulated_ms >= 0)',
|
|
'CHECK (stopped_at IS NULL OR skipped_at IS NULL)',
|
|
];
|
|
}
|
|
|
|
class ActiveRestStates extends SyncableTable {
|
|
@override
|
|
String get tableName => 'active_rest_states';
|
|
|
|
TextColumn get activeWorkoutSessionId =>
|
|
text().references(ActiveWorkoutSessions, #id)();
|
|
IntColumn get afterProgramIndex => integer()();
|
|
IntColumn get afterExerciseIndex => integer()();
|
|
IntColumn get afterSetIndex => integer()();
|
|
IntColumn get plannedRestSeconds => integer()();
|
|
IntColumn get adjustedRestSeconds => integer()();
|
|
DateTimeColumn get startedAt => dateTime()();
|
|
DateTimeColumn get endedAt => dateTime().nullable()();
|
|
DateTimeColumn get skippedAt => dateTime().nullable()();
|
|
DateTimeColumn get pausedAt => dateTime().nullable()();
|
|
IntColumn get accumulatedPausedMs =>
|
|
integer().withDefault(const Constant(0))();
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
'CHECK (after_program_index >= 0)',
|
|
'CHECK (after_exercise_index >= 0)',
|
|
'CHECK (after_set_index >= 0)',
|
|
'CHECK (planned_rest_seconds >= 0)',
|
|
'CHECK (adjusted_rest_seconds >= 0)',
|
|
'CHECK (accumulated_paused_ms >= 0)',
|
|
'CHECK (ended_at IS NULL OR skipped_at IS NULL)',
|
|
];
|
|
}
|
|
|
|
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';
|
|
|
|
TextColumn get sourceWorkoutTemplateId =>
|
|
text().nullable().references(WorkoutTemplates, #id)();
|
|
TextColumn get sourceActiveWorkoutSessionId =>
|
|
text().nullable().references(ActiveWorkoutSessions, #id)();
|
|
TextColumn get nameSnapshot => text().withLength(min: 1)();
|
|
DateTimeColumn get startedAt => dateTime()();
|
|
DateTimeColumn get endedAt => dateTime()();
|
|
IntColumn get totalActiveMs => integer()();
|
|
BoolColumn get completed => boolean()();
|
|
TextColumn get historySnapshotJson => text().withLength(min: 1)();
|
|
|
|
@override
|
|
List<String> get customConstraints => ['CHECK (total_active_ms >= 0)'];
|
|
}
|
|
|
|
class WorkoutHistorySetResults extends SyncableTable {
|
|
@override
|
|
String get tableName => 'workout_history_set_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()();
|
|
TextColumn get programNameSnapshot => text().withLength(min: 1)();
|
|
TextColumn get exerciseNameSnapshot => text().withLength(min: 1)();
|
|
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()();
|
|
DateTimeColumn get completedAt => dateTime().nullable()();
|
|
TextColumn get status => text().withDefault(const Constant('completed'))();
|
|
|
|
@override
|
|
List<String> get customConstraints => [
|
|
'UNIQUE (workout_history_id, program_index, exercise_index, '
|
|
'set_index)',
|
|
'CHECK (program_index >= 0)',
|
|
'CHECK (exercise_index >= 0)',
|
|
'CHECK (set_index >= 0)',
|
|
'CHECK (time_enabled_snapshot OR reps_enabled_snapshot OR '
|
|
'score_enabled_snapshot)',
|
|
'CHECK (target_time_seconds_snapshot IS NULL OR '
|
|
'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_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 '
|
|
'AND length(trim(score_unit_snapshot)) > 0))',
|
|
];
|
|
}
|
|
|
|
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';
|
|
|
|
TextColumn get id => text()();
|
|
TextColumn get entityType => text().withLength(min: 1)();
|
|
TextColumn get entityId => text().withLength(min: 1)();
|
|
TextColumn get operation => text()();
|
|
TextColumn get payloadHash => text().nullable()();
|
|
IntColumn get localRevision => integer()();
|
|
TextColumn get originDeviceId => text().withLength(min: 1)();
|
|
DateTimeColumn get createdAt => dateTime()();
|
|
DateTimeColumn get syncedAt => dateTime().nullable()();
|
|
|
|
@override
|
|
Set<Column> get primaryKey => {id};
|
|
|
|
@override
|
|
List<String> get customConstraints => const [
|
|
"CHECK (operation IN ('insert', 'update', 'softDelete', 'restore'))",
|
|
'CHECK (local_revision >= 0)',
|
|
];
|
|
}
|