feat(exécution): refonte des chronos de séance et carte exercice actif (ticket #90)
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>
This commit is contained in:
@ -11,6 +11,7 @@ part 'app_database.g.dart';
|
||||
ActiveExerciseStepResults,
|
||||
ActiveRestStates,
|
||||
ActiveScoreStopwatchStates,
|
||||
ActiveSetTimerStates,
|
||||
ActiveSetResults,
|
||||
ActiveWorkoutSessions,
|
||||
ChangeLogEntries,
|
||||
@ -46,13 +47,14 @@ final class AppDatabase extends _$AppDatabase {
|
||||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 14;
|
||||
int get schemaVersion => 15;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration {
|
||||
return MigrationStrategy(
|
||||
onCreate: (migrator) async {
|
||||
await migrator.createAll();
|
||||
await _migrateToSchema15();
|
||||
await _createIndexes();
|
||||
},
|
||||
onUpgrade: (migrator, from, to) async {
|
||||
@ -103,6 +105,9 @@ final class AppDatabase extends _$AppDatabase {
|
||||
if (from < 14) {
|
||||
await _migrateToSchema14();
|
||||
}
|
||||
if (from < 15) {
|
||||
await _migrateToSchema15();
|
||||
}
|
||||
await _createIndexes();
|
||||
},
|
||||
beforeOpen: (details) async {
|
||||
@ -171,6 +176,10 @@ final class AppDatabase extends _$AppDatabase {
|
||||
'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_set_timer_states_session_id '
|
||||
'ON active_set_timer_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)',
|
||||
@ -223,6 +232,7 @@ const _syncableTableNames = [
|
||||
'active_exercise_step_results',
|
||||
'active_rest_states',
|
||||
'active_score_stopwatch_states',
|
||||
'active_set_timer_states',
|
||||
'active_set_results',
|
||||
'active_workout_sessions',
|
||||
'exercises',
|
||||
@ -494,4 +504,141 @@ FROM exercises
|
||||
'CHECK (auto_start_next_timed_step_override IN (0, 1))',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _migrateToSchema15() async {
|
||||
await customStatement('PRAGMA foreign_keys = OFF');
|
||||
await customStatement('''
|
||||
CREATE TABLE active_score_stopwatch_states_new (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
deleted_at INTEGER,
|
||||
schema_version INTEGER NOT NULL DEFAULT 1,
|
||||
sync_state TEXT NOT NULL CHECK (sync_state IN ('localOnly', 'dirty', 'synced', 'deleted')),
|
||||
local_revision INTEGER NOT NULL CHECK (local_revision >= 0),
|
||||
origin_device_id TEXT NOT NULL,
|
||||
future_owner_profile_id TEXT,
|
||||
last_synced_at INTEGER,
|
||||
remote_revision TEXT,
|
||||
active_workout_session_id TEXT NOT NULL REFERENCES active_workout_sessions(id),
|
||||
program_index INTEGER NOT NULL,
|
||||
exercise_index INTEGER NOT NULL,
|
||||
set_index INTEGER NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
started_at INTEGER NOT NULL,
|
||||
accumulated_ms INTEGER NOT NULL,
|
||||
stopped_at INTEGER,
|
||||
UNIQUE (active_workout_session_id, program_index, exercise_index, set_index),
|
||||
CHECK (length(trim(origin_device_id)) > 0),
|
||||
CHECK (future_owner_profile_id IS NULL OR length(trim(future_owner_profile_id)) > 0),
|
||||
CHECK (remote_revision IS NULL OR length(trim(remote_revision)) > 0),
|
||||
CHECK (program_index >= 0),
|
||||
CHECK (exercise_index >= 0),
|
||||
CHECK (set_index >= 0),
|
||||
CHECK (status IN ('running', 'paused', 'stopped')),
|
||||
CHECK (accumulated_ms >= 0)
|
||||
)
|
||||
''');
|
||||
await customStatement('''
|
||||
INSERT INTO active_score_stopwatch_states_new (
|
||||
id,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at,
|
||||
schema_version,
|
||||
sync_state,
|
||||
local_revision,
|
||||
origin_device_id,
|
||||
future_owner_profile_id,
|
||||
last_synced_at,
|
||||
remote_revision,
|
||||
active_workout_session_id,
|
||||
program_index,
|
||||
exercise_index,
|
||||
set_index,
|
||||
status,
|
||||
started_at,
|
||||
accumulated_ms,
|
||||
stopped_at
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at,
|
||||
schema_version,
|
||||
sync_state,
|
||||
local_revision,
|
||||
origin_device_id,
|
||||
future_owner_profile_id,
|
||||
last_synced_at,
|
||||
remote_revision,
|
||||
active_workout_session_id,
|
||||
program_index,
|
||||
exercise_index,
|
||||
set_index,
|
||||
status,
|
||||
started_at,
|
||||
accumulated_ms,
|
||||
stopped_at
|
||||
FROM active_score_stopwatch_states
|
||||
''');
|
||||
await customStatement('DROP TABLE active_score_stopwatch_states');
|
||||
await customStatement(
|
||||
'ALTER TABLE active_score_stopwatch_states_new '
|
||||
'RENAME TO active_score_stopwatch_states',
|
||||
);
|
||||
await customStatement('PRAGMA foreign_keys = ON');
|
||||
await customStatement('''
|
||||
CREATE TABLE IF NOT EXISTS active_set_timer_states (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
deleted_at INTEGER,
|
||||
schema_version INTEGER NOT NULL DEFAULT 1,
|
||||
sync_state TEXT NOT NULL CHECK (sync_state IN ('localOnly', 'dirty', 'synced', 'deleted')),
|
||||
local_revision INTEGER NOT NULL CHECK (local_revision >= 0),
|
||||
origin_device_id TEXT NOT NULL,
|
||||
future_owner_profile_id TEXT,
|
||||
last_synced_at INTEGER,
|
||||
remote_revision TEXT,
|
||||
active_workout_session_id TEXT NOT NULL REFERENCES active_workout_sessions(id),
|
||||
program_index INTEGER NOT NULL,
|
||||
exercise_index INTEGER NOT NULL,
|
||||
set_index INTEGER NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
started_at INTEGER,
|
||||
accumulated_ms INTEGER NOT NULL,
|
||||
stopped_at INTEGER,
|
||||
skipped_at INTEGER,
|
||||
UNIQUE (active_workout_session_id, program_index, exercise_index, set_index),
|
||||
CHECK (length(trim(origin_device_id)) > 0),
|
||||
CHECK (future_owner_profile_id IS NULL OR length(trim(future_owner_profile_id)) > 0),
|
||||
CHECK (remote_revision IS NULL OR length(trim(remote_revision)) > 0),
|
||||
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)
|
||||
)
|
||||
''');
|
||||
if (!await _hasColumn('active_rest_states', 'paused_at')) {
|
||||
await customStatement(
|
||||
'ALTER TABLE active_rest_states ADD COLUMN paused_at INTEGER',
|
||||
);
|
||||
}
|
||||
if (!await _hasColumn('active_rest_states', 'accumulated_paused_ms')) {
|
||||
await customStatement(
|
||||
'ALTER TABLE active_rest_states ADD COLUMN accumulated_paused_ms '
|
||||
'INTEGER NOT NULL DEFAULT 0 CHECK (accumulated_paused_ms >= 0)',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _hasColumn(String tableName, String columnName) async {
|
||||
final rows = await customSelect('PRAGMA table_info($tableName)').get();
|
||||
return rows.any((row) => row.data['name'] == columnName);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user