chore(wip): consolidation intermédiaire multi-tickets (sprints Statistiques, UI, Bug resolution, Serveur-client)
Regroupe l'état de travail en cours réalisé dans un même worktree sur plusieurs tickets/sprints (#85, #136, #145, #155-160, #162-164), mélangeant des tickets QA et inProgress. Ne constitue pas une feature terminée : commit de sauvegarde avant triage/split par ticket en branches feature/* dédiées. Exclut les dossiers d'environnement de build locaux et le heap dump parasite (.gitignore mis à jour). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -28,6 +28,8 @@ part 'app_database.g.dart';
|
||||
RemoteResourceMappings,
|
||||
ShareInboxItems,
|
||||
SyncMetadataEntries,
|
||||
WorkoutTelemetryAggregates,
|
||||
WorkoutTelemetrySamples,
|
||||
WorkoutHistories,
|
||||
WorkoutHistorySetResults,
|
||||
WorkoutHistoryStepResults,
|
||||
@ -49,7 +51,7 @@ final class AppDatabase extends _$AppDatabase {
|
||||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 22;
|
||||
int get schemaVersion => 24;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration {
|
||||
@ -129,6 +131,12 @@ final class AppDatabase extends _$AppDatabase {
|
||||
if (from < 22) {
|
||||
await _migrateToSchema22();
|
||||
}
|
||||
if (from < 23) {
|
||||
await _migrateToSchema23();
|
||||
}
|
||||
if (from < 24) {
|
||||
await _migrateToSchema24(migrator);
|
||||
}
|
||||
await _createIndexes();
|
||||
},
|
||||
beforeOpen: (details) async {
|
||||
@ -138,6 +146,9 @@ final class AppDatabase extends _$AppDatabase {
|
||||
}
|
||||
|
||||
Future<void> _createIndexes() async {
|
||||
if (!await _hasTable(_syncableTableNames.first)) {
|
||||
return;
|
||||
}
|
||||
for (final tableName in _syncableTableNames) {
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_${tableName}_deleted_at '
|
||||
@ -250,6 +261,14 @@ final class AppDatabase extends _$AppDatabase {
|
||||
'ON workout_history (completed, started_at) '
|
||||
'WHERE deleted_at IS NULL',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_workout_telemetry_samples_session '
|
||||
'ON workout_telemetry_samples (session_id, captured_at)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_workout_telemetry_aggregates_session '
|
||||
'ON workout_telemetry_aggregates (session_id, scope)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_workout_history_set_results_history_id '
|
||||
'ON workout_history_set_results (workout_history_id)',
|
||||
@ -816,6 +835,127 @@ CREATE TABLE IF NOT EXISTS active_set_timer_states (
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _migrateToSchema23() async {
|
||||
await customStatement('''
|
||||
CREATE TABLE IF NOT EXISTS share_inbox_items_v23 (
|
||||
share_id TEXT NOT NULL PRIMARY KEY,
|
||||
sender_user_id TEXT NOT NULL,
|
||||
resource_type TEXT NOT NULL CHECK (
|
||||
resource_type IN ('program', 'workoutTemplate', 'pack')
|
||||
),
|
||||
payload_json TEXT NOT NULL,
|
||||
status TEXT NOT NULL CHECK (
|
||||
status IN ('pending', 'accepted', 'declined', 'revoked')
|
||||
),
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
responded_at INTEGER
|
||||
)
|
||||
''');
|
||||
await customStatement('''
|
||||
INSERT INTO share_inbox_items_v23 (
|
||||
share_id,
|
||||
sender_user_id,
|
||||
resource_type,
|
||||
payload_json,
|
||||
status,
|
||||
created_at,
|
||||
updated_at,
|
||||
responded_at
|
||||
)
|
||||
SELECT
|
||||
share_id,
|
||||
sender_user_id,
|
||||
resource_type,
|
||||
payload_json,
|
||||
status,
|
||||
created_at,
|
||||
updated_at,
|
||||
responded_at
|
||||
FROM share_inbox_items
|
||||
''');
|
||||
await customStatement('DROP TABLE share_inbox_items');
|
||||
await customStatement(
|
||||
'ALTER TABLE share_inbox_items_v23 RENAME TO share_inbox_items',
|
||||
);
|
||||
|
||||
await customStatement('''
|
||||
CREATE TABLE IF NOT EXISTS pending_share_actions_v23 (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
action_type TEXT NOT NULL CHECK (
|
||||
action_type IN ('send', 'accept', 'decline', 'revoke')
|
||||
),
|
||||
share_id TEXT,
|
||||
resource_type TEXT CHECK (
|
||||
resource_type IS NULL OR
|
||||
resource_type IN ('program', 'workoutTemplate', 'pack')
|
||||
),
|
||||
payload_json TEXT,
|
||||
recipient_emails_json TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
last_attempt_at INTEGER,
|
||||
attempt_count INTEGER NOT NULL CHECK (attempt_count >= 0),
|
||||
status TEXT NOT NULL CHECK (status IN ('pending', 'succeeded', 'failed'))
|
||||
)
|
||||
''');
|
||||
await customStatement('''
|
||||
INSERT INTO pending_share_actions_v23 (
|
||||
id,
|
||||
action_type,
|
||||
share_id,
|
||||
resource_type,
|
||||
payload_json,
|
||||
recipient_emails_json,
|
||||
created_at,
|
||||
last_attempt_at,
|
||||
attempt_count,
|
||||
status
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
action_type,
|
||||
share_id,
|
||||
resource_type,
|
||||
payload_json,
|
||||
recipient_emails_json,
|
||||
created_at,
|
||||
last_attempt_at,
|
||||
attempt_count,
|
||||
status
|
||||
FROM pending_share_actions
|
||||
''');
|
||||
await customStatement('DROP TABLE pending_share_actions');
|
||||
await customStatement(
|
||||
'ALTER TABLE pending_share_actions_v23 RENAME TO pending_share_actions',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _migrateToSchema24(Migrator migrator) async {
|
||||
await _addColumnIfMissing(
|
||||
tableName: 'workout_history',
|
||||
columnName: 'min_heart_rate_bpm',
|
||||
definition:
|
||||
'min_heart_rate_bpm INTEGER CHECK '
|
||||
'(min_heart_rate_bpm IS NULL OR min_heart_rate_bpm > 0)',
|
||||
);
|
||||
await _addColumnIfMissing(
|
||||
tableName: 'workout_history',
|
||||
columnName: 'total_distance_meters',
|
||||
definition:
|
||||
'total_distance_meters REAL CHECK '
|
||||
'(total_distance_meters IS NULL OR total_distance_meters >= 0)',
|
||||
);
|
||||
await _addColumnIfMissing(
|
||||
tableName: 'workout_history',
|
||||
columnName: 'total_calories_kcal',
|
||||
definition:
|
||||
'total_calories_kcal REAL CHECK '
|
||||
'(total_calories_kcal IS NULL OR total_calories_kcal >= 0)',
|
||||
);
|
||||
await migrator.createTable(workoutTelemetrySamples);
|
||||
await migrator.createTable(workoutTelemetryAggregates);
|
||||
}
|
||||
|
||||
Future<void> _backfillWorkoutHistorySetSourceExerciseIds() async {
|
||||
await customStatement(r'''
|
||||
UPDATE workout_history_set_results AS result
|
||||
@ -890,4 +1030,12 @@ WHERE result.source_exercise_id_snapshot IS NULL
|
||||
final rows = await customSelect('PRAGMA table_info($tableName)').get();
|
||||
return rows.any((row) => row.data['name'] == columnName);
|
||||
}
|
||||
|
||||
Future<bool> _hasTable(String tableName) async {
|
||||
final rows = await customSelect(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?",
|
||||
variables: [Variable<String>(tableName)],
|
||||
).get();
|
||||
return rows.isNotEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -950,25 +950,38 @@ final class DriftWorkoutTemplateRepository
|
||||
@override
|
||||
Future<void> save(domain.WorkoutTemplate template) async {
|
||||
await database.transaction(() async {
|
||||
await _upsertWithChangeLog(
|
||||
database: database,
|
||||
tableName: 'workout_templates',
|
||||
entityType: 'WorkoutTemplate',
|
||||
metadata: template.metadata,
|
||||
write: () => database
|
||||
.into(database.workoutTemplates)
|
||||
.insertOnConflictUpdate(_workoutTemplateCompanion(template)),
|
||||
);
|
||||
await _writeWorkoutTemplateStarterMetadata(database, template);
|
||||
for (final program in template.programs) {
|
||||
await saveProgram(program);
|
||||
}
|
||||
for (final override in template.overrides) {
|
||||
await saveOverride(override);
|
||||
await _saveTemplate(template);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveAll(List<domain.WorkoutTemplate> templates) async {
|
||||
await database.transaction(() async {
|
||||
for (final template in templates) {
|
||||
await _saveTemplate(template);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _saveTemplate(domain.WorkoutTemplate template) async {
|
||||
await _upsertWithChangeLog(
|
||||
database: database,
|
||||
tableName: 'workout_templates',
|
||||
entityType: 'WorkoutTemplate',
|
||||
metadata: template.metadata,
|
||||
write: () => database
|
||||
.into(database.workoutTemplates)
|
||||
.insertOnConflictUpdate(_workoutTemplateCompanion(template)),
|
||||
);
|
||||
await _writeWorkoutTemplateStarterMetadata(database, template);
|
||||
for (final program in template.programs) {
|
||||
await saveProgram(program);
|
||||
}
|
||||
for (final override in template.overrides) {
|
||||
await saveOverride(override);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveProgram(domain.WorkoutTemplateProgram program) async {
|
||||
await _upsertWithChangeLog(
|
||||
@ -1614,11 +1627,18 @@ final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository {
|
||||
@override
|
||||
Future<void> patchHeartRateSummary({
|
||||
required String historyId,
|
||||
int? minHeartRateBpm,
|
||||
required double averageHeartRateBpm,
|
||||
required int maxHeartRateBpm,
|
||||
double? totalDistanceMeters,
|
||||
double? totalCaloriesKcal,
|
||||
required DateTime patchedAt,
|
||||
}) async {
|
||||
if (averageHeartRateBpm <= 0 || maxHeartRateBpm <= 0) {
|
||||
if ((minHeartRateBpm != null && minHeartRateBpm <= 0) ||
|
||||
averageHeartRateBpm <= 0 ||
|
||||
maxHeartRateBpm <= 0 ||
|
||||
(totalDistanceMeters != null && totalDistanceMeters < 0) ||
|
||||
(totalCaloriesKcal != null && totalCaloriesKcal < 0)) {
|
||||
return;
|
||||
}
|
||||
final row =
|
||||
@ -1626,8 +1646,11 @@ final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository {
|
||||
(table) =>
|
||||
table.id.equals(historyId) &
|
||||
table.deletedAt.isNull() &
|
||||
table.minHeartRateBpm.isNull() &
|
||||
table.averageHeartRateBpm.isNull() &
|
||||
table.maxHeartRateBpm.isNull(),
|
||||
table.maxHeartRateBpm.isNull() &
|
||||
table.totalDistanceMeters.isNull() &
|
||||
table.totalCaloriesKcal.isNull(),
|
||||
))
|
||||
.getSingleOrNull();
|
||||
if (row == null) {
|
||||
@ -1641,8 +1664,11 @@ final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository {
|
||||
updatedAt: Value(patchedAt.toUtc()),
|
||||
syncState: const Value('dirty'),
|
||||
localRevision: Value(revision),
|
||||
minHeartRateBpm: Value(minHeartRateBpm),
|
||||
averageHeartRateBpm: Value(averageHeartRateBpm),
|
||||
maxHeartRateBpm: Value(maxHeartRateBpm),
|
||||
totalDistanceMeters: Value(totalDistanceMeters),
|
||||
totalCaloriesKcal: Value(totalCaloriesKcal),
|
||||
),
|
||||
);
|
||||
await _writeChangeLog(
|
||||
@ -1736,6 +1762,103 @@ final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository {
|
||||
}
|
||||
}
|
||||
|
||||
final class DriftWorkoutTelemetryRepository
|
||||
implements WorkoutTelemetryRepository {
|
||||
const DriftWorkoutTelemetryRepository(this.database);
|
||||
|
||||
final db.AppDatabase database;
|
||||
|
||||
@override
|
||||
Future<bool> saveSample(domain.WorkoutTelemetrySample sample) async {
|
||||
final existing = await (database.select(
|
||||
database.workoutTelemetrySamples,
|
||||
)..where((table) => table.id.equals(sample.id))).getSingleOrNull();
|
||||
if (existing != null) {
|
||||
return false;
|
||||
}
|
||||
final inserted = await database
|
||||
.into(database.workoutTelemetrySamples)
|
||||
.insert(
|
||||
_workoutTelemetrySampleCompanion(sample),
|
||||
mode: InsertMode.insertOrIgnore,
|
||||
);
|
||||
return inserted > 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<domain.WorkoutTelemetrySample>> listSamples(
|
||||
String sessionId,
|
||||
) async {
|
||||
final rows =
|
||||
await (database.select(database.workoutTelemetrySamples)
|
||||
..where((table) => table.sessionId.equals(sessionId))
|
||||
..orderBy([(table) => OrderingTerm.asc(table.capturedAt)]))
|
||||
.get();
|
||||
return rows.map(_workoutTelemetrySampleFromRow).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> replaceAggregatesForSession({
|
||||
required String sessionId,
|
||||
required List<domain.WorkoutTelemetryAggregate> aggregates,
|
||||
}) async {
|
||||
await database.transaction(() async {
|
||||
await (database.delete(
|
||||
database.workoutTelemetryAggregates,
|
||||
)..where((table) => table.sessionId.equals(sessionId))).go();
|
||||
if (aggregates.isEmpty) {
|
||||
return;
|
||||
}
|
||||
await database.batch((batch) {
|
||||
batch.insertAll(
|
||||
database.workoutTelemetryAggregates,
|
||||
aggregates.map(_workoutTelemetryAggregateCompanion).toList(),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<domain.WorkoutTelemetryAggregate>> listAggregates(
|
||||
String sessionId,
|
||||
) async {
|
||||
final rows = await (database.select(
|
||||
database.workoutTelemetryAggregates,
|
||||
)..where((table) => table.sessionId.equals(sessionId))).get();
|
||||
return rows.map(_workoutTelemetryAggregateFromRow).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<domain.WorkoutTelemetryAggregate?> findAggregate({
|
||||
required String sessionId,
|
||||
required domain.WorkoutTelemetryAggregateScope scope,
|
||||
int? programIndex,
|
||||
int? exerciseIndex,
|
||||
int? setIndex,
|
||||
int? passageIndex,
|
||||
int? stepIndex,
|
||||
}) async {
|
||||
final rows =
|
||||
await (database.select(database.workoutTelemetryAggregates)..where(
|
||||
(table) =>
|
||||
table.sessionId.equals(sessionId) &
|
||||
table.scope.equals(scope.wireName),
|
||||
))
|
||||
.get();
|
||||
for (final row in rows) {
|
||||
final aggregate = _workoutTelemetryAggregateFromRow(row);
|
||||
if (aggregate.programIndex == programIndex &&
|
||||
aggregate.exerciseIndex == exerciseIndex &&
|
||||
aggregate.setIndex == setIndex &&
|
||||
aggregate.passageIndex == passageIndex &&
|
||||
aggregate.stepIndex == stepIndex) {
|
||||
return aggregate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final class DriftLocalDataBackupRepository
|
||||
implements LocalDataBackupRepository {
|
||||
const DriftLocalDataBackupRepository(this.database);
|
||||
@ -4363,8 +4486,52 @@ db.WorkoutHistoriesCompanion _workoutHistoryCompanion(
|
||||
totalActiveMs: Value(history.totalActiveMs),
|
||||
completed: Value(history.completed),
|
||||
historySnapshotJson: Value(history.historySnapshotJson),
|
||||
minHeartRateBpm: Value(history.minHeartRateBpm),
|
||||
averageHeartRateBpm: Value(history.averageHeartRateBpm),
|
||||
maxHeartRateBpm: Value(history.maxHeartRateBpm),
|
||||
totalDistanceMeters: Value(history.totalDistanceMeters),
|
||||
totalCaloriesKcal: Value(history.totalCaloriesKcal),
|
||||
);
|
||||
}
|
||||
|
||||
db.WorkoutTelemetrySamplesCompanion _workoutTelemetrySampleCompanion(
|
||||
domain.WorkoutTelemetrySample sample,
|
||||
) {
|
||||
return db.WorkoutTelemetrySamplesCompanion.insert(
|
||||
id: sample.id,
|
||||
sessionId: sample.sessionId,
|
||||
capturedAt: sample.capturedAt.toUtc(),
|
||||
programIndex: Value(sample.programIndex),
|
||||
exerciseIndex: Value(sample.exerciseIndex),
|
||||
setIndex: Value(sample.setIndex),
|
||||
passageIndex: Value(sample.passageIndex),
|
||||
stepIndex: Value(sample.stepIndex),
|
||||
programSnapshotId: Value(sample.programSnapshotId),
|
||||
exerciseSnapshotId: Value(sample.exerciseSnapshotId),
|
||||
stepSnapshotId: Value(sample.stepSnapshotId),
|
||||
heartRateBpm: Value(sample.heartRateBpm),
|
||||
distanceMeters: Value(sample.distanceMeters),
|
||||
caloriesKcal: Value(sample.caloriesKcal),
|
||||
);
|
||||
}
|
||||
|
||||
db.WorkoutTelemetryAggregatesCompanion _workoutTelemetryAggregateCompanion(
|
||||
domain.WorkoutTelemetryAggregate aggregate,
|
||||
) {
|
||||
return db.WorkoutTelemetryAggregatesCompanion.insert(
|
||||
sessionId: aggregate.sessionId,
|
||||
scope: aggregate.scope.wireName,
|
||||
programIndex: Value(aggregate.programIndex),
|
||||
exerciseIndex: Value(aggregate.exerciseIndex),
|
||||
setIndex: Value(aggregate.setIndex),
|
||||
passageIndex: Value(aggregate.passageIndex),
|
||||
stepIndex: Value(aggregate.stepIndex),
|
||||
sampleCount: aggregate.sampleCount,
|
||||
minHeartRateBpm: Value(aggregate.minHeartRateBpm),
|
||||
averageHeartRateBpm: Value(aggregate.averageHeartRateBpm),
|
||||
maxHeartRateBpm: Value(aggregate.maxHeartRateBpm),
|
||||
totalDistanceMeters: Value(aggregate.totalDistanceMeters),
|
||||
totalCaloriesKcal: Value(aggregate.totalCaloriesKcal),
|
||||
);
|
||||
}
|
||||
|
||||
@ -4480,13 +4647,57 @@ domain.WorkoutHistory _workoutHistoryFromRow(
|
||||
totalActiveMs: row.totalActiveMs,
|
||||
completed: row.completed,
|
||||
historySnapshotJson: row.historySnapshotJson,
|
||||
minHeartRateBpm: row.minHeartRateBpm,
|
||||
averageHeartRateBpm: row.averageHeartRateBpm,
|
||||
maxHeartRateBpm: row.maxHeartRateBpm,
|
||||
totalDistanceMeters: row.totalDistanceMeters,
|
||||
totalCaloriesKcal: row.totalCaloriesKcal,
|
||||
results: results,
|
||||
stepResults: stepResults,
|
||||
);
|
||||
}
|
||||
|
||||
domain.WorkoutTelemetrySample _workoutTelemetrySampleFromRow(
|
||||
db.WorkoutTelemetrySample row,
|
||||
) {
|
||||
return domain.WorkoutTelemetrySample(
|
||||
id: row.id,
|
||||
sessionId: row.sessionId,
|
||||
capturedAt: _utc(row.capturedAt),
|
||||
programIndex: row.programIndex,
|
||||
exerciseIndex: row.exerciseIndex,
|
||||
setIndex: row.setIndex,
|
||||
passageIndex: row.passageIndex,
|
||||
stepIndex: row.stepIndex,
|
||||
programSnapshotId: row.programSnapshotId,
|
||||
exerciseSnapshotId: row.exerciseSnapshotId,
|
||||
stepSnapshotId: row.stepSnapshotId,
|
||||
heartRateBpm: row.heartRateBpm,
|
||||
distanceMeters: row.distanceMeters,
|
||||
caloriesKcal: row.caloriesKcal,
|
||||
);
|
||||
}
|
||||
|
||||
domain.WorkoutTelemetryAggregate _workoutTelemetryAggregateFromRow(
|
||||
db.WorkoutTelemetryAggregate row,
|
||||
) {
|
||||
return domain.WorkoutTelemetryAggregate(
|
||||
sessionId: row.sessionId,
|
||||
scope: domain.WorkoutTelemetryAggregateScope.parse(row.scope),
|
||||
programIndex: row.programIndex,
|
||||
exerciseIndex: row.exerciseIndex,
|
||||
setIndex: row.setIndex,
|
||||
passageIndex: row.passageIndex,
|
||||
stepIndex: row.stepIndex,
|
||||
sampleCount: row.sampleCount,
|
||||
minHeartRateBpm: row.minHeartRateBpm,
|
||||
averageHeartRateBpm: row.averageHeartRateBpm,
|
||||
maxHeartRateBpm: row.maxHeartRateBpm,
|
||||
totalDistanceMeters: row.totalDistanceMeters,
|
||||
totalCaloriesKcal: row.totalCaloriesKcal,
|
||||
);
|
||||
}
|
||||
|
||||
domain.WorkoutHistorySetResult _workoutHistorySetResultFromRow(
|
||||
db.WorkoutHistorySetResult row,
|
||||
) {
|
||||
@ -4739,8 +4950,11 @@ Map<String, Object?> _workoutHistoryPayload(domain.WorkoutHistory history) => {
|
||||
'totalActiveMs': history.totalActiveMs,
|
||||
'completed': history.completed,
|
||||
'historySnapshotJson': history.historySnapshotJson,
|
||||
'minHeartRateBpm': history.minHeartRateBpm,
|
||||
'averageHeartRateBpm': history.averageHeartRateBpm,
|
||||
'maxHeartRateBpm': history.maxHeartRateBpm,
|
||||
'totalDistanceMeters': history.totalDistanceMeters,
|
||||
'totalCaloriesKcal': history.totalCaloriesKcal,
|
||||
};
|
||||
|
||||
Map<String, Object?> _localWorkoutHistoryPayload(
|
||||
@ -4895,8 +5109,11 @@ domain.WorkoutHistory _workoutHistoryFromLocalBackupPayload(
|
||||
completed: payload['completed'] as bool? ?? false,
|
||||
historySnapshotJson:
|
||||
payload['historySnapshotJson'] as String? ?? '{"programs":[]}',
|
||||
minHeartRateBpm: payload['minHeartRateBpm'] as int?,
|
||||
averageHeartRateBpm: (payload['averageHeartRateBpm'] as num?)?.toDouble(),
|
||||
maxHeartRateBpm: payload['maxHeartRateBpm'] as int?,
|
||||
totalDistanceMeters: (payload['totalDistanceMeters'] as num?)?.toDouble(),
|
||||
totalCaloriesKcal: (payload['totalCaloriesKcal'] as num?)?.toDouble(),
|
||||
results: _workoutHistorySetResultsFromPayload(payload['results'], metadata),
|
||||
stepResults: _workoutHistoryStepResultsFromPayload(
|
||||
payload['stepResults'],
|
||||
@ -5412,12 +5629,14 @@ OnlineSyncStatus _onlineSyncStatusFromDb(String value) => switch (value) {
|
||||
String _shareResourceTypeToDb(domain.ShareResourceType type) => switch (type) {
|
||||
domain.ShareResourceType.program => 'program',
|
||||
domain.ShareResourceType.workoutTemplate => 'workoutTemplate',
|
||||
domain.ShareResourceType.pack => 'pack',
|
||||
};
|
||||
|
||||
domain.ShareResourceType _shareResourceTypeFromDb(String value) =>
|
||||
switch (value) {
|
||||
'program' => domain.ShareResourceType.program,
|
||||
'workoutTemplate' => domain.ShareResourceType.workoutTemplate,
|
||||
'pack' => domain.ShareResourceType.pack,
|
||||
_ => throw domain.DomainException('Unknown share resource type: $value'),
|
||||
};
|
||||
|
||||
|
||||
@ -110,7 +110,7 @@ class ShareInboxItems extends Table {
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => [
|
||||
"CHECK (resource_type IN ('program', 'workoutTemplate'))",
|
||||
"CHECK (resource_type IN ('program', 'workoutTemplate', 'pack'))",
|
||||
"CHECK (status IN ('pending', 'accepted', 'declined', 'revoked'))",
|
||||
];
|
||||
}
|
||||
@ -138,7 +138,7 @@ class PendingShareActions extends Table {
|
||||
List<String> get customConstraints => [
|
||||
"CHECK (action_type IN ('send', 'accept', 'decline', 'revoke'))",
|
||||
'CHECK (resource_type IS NULL OR resource_type IN '
|
||||
"('program', 'workoutTemplate'))",
|
||||
"('program', 'workoutTemplate', 'pack'))",
|
||||
"CHECK (status IN ('pending', 'succeeded', 'failed'))",
|
||||
];
|
||||
}
|
||||
@ -750,14 +750,94 @@ class WorkoutHistories extends SyncableTable {
|
||||
IntColumn get totalActiveMs => integer()();
|
||||
BoolColumn get completed => boolean()();
|
||||
TextColumn get historySnapshotJson => text().withLength(min: 1)();
|
||||
IntColumn get minHeartRateBpm => integer().nullable()();
|
||||
RealColumn get averageHeartRateBpm => real().nullable()();
|
||||
IntColumn get maxHeartRateBpm => integer().nullable()();
|
||||
RealColumn get totalDistanceMeters => real().nullable()();
|
||||
RealColumn get totalCaloriesKcal => real().nullable()();
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => [
|
||||
'CHECK (total_active_ms >= 0)',
|
||||
'CHECK (min_heart_rate_bpm IS NULL OR min_heart_rate_bpm > 0)',
|
||||
'CHECK (average_heart_rate_bpm IS NULL OR average_heart_rate_bpm > 0)',
|
||||
'CHECK (max_heart_rate_bpm IS NULL OR max_heart_rate_bpm > 0)',
|
||||
'CHECK (total_distance_meters IS NULL OR total_distance_meters >= 0)',
|
||||
'CHECK (total_calories_kcal IS NULL OR total_calories_kcal >= 0)',
|
||||
];
|
||||
}
|
||||
|
||||
class WorkoutTelemetrySamples extends Table {
|
||||
@override
|
||||
String get tableName => 'workout_telemetry_samples';
|
||||
|
||||
TextColumn get id => text()();
|
||||
TextColumn get sessionId => text().withLength(min: 1)();
|
||||
DateTimeColumn get capturedAt => dateTime()();
|
||||
IntColumn get programIndex => integer().nullable()();
|
||||
IntColumn get exerciseIndex => integer().nullable()();
|
||||
IntColumn get setIndex => integer().nullable()();
|
||||
IntColumn get passageIndex => integer().nullable()();
|
||||
IntColumn get stepIndex => integer().nullable()();
|
||||
TextColumn get programSnapshotId => text().nullable()();
|
||||
TextColumn get exerciseSnapshotId => text().nullable()();
|
||||
TextColumn get stepSnapshotId => text().nullable()();
|
||||
IntColumn get heartRateBpm => integer().nullable()();
|
||||
RealColumn get distanceMeters => real().nullable()();
|
||||
RealColumn get caloriesKcal => real().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => [
|
||||
'CHECK (program_index IS NULL OR program_index >= 0)',
|
||||
'CHECK (exercise_index IS NULL OR exercise_index >= 0)',
|
||||
'CHECK (set_index IS NULL OR set_index >= 0)',
|
||||
'CHECK (passage_index IS NULL OR passage_index >= 0)',
|
||||
'CHECK (step_index IS NULL OR step_index >= 0)',
|
||||
'CHECK (heart_rate_bpm IS NULL OR heart_rate_bpm > 0)',
|
||||
'CHECK (distance_meters IS NULL OR distance_meters >= 0)',
|
||||
'CHECK (calories_kcal IS NULL OR calories_kcal >= 0)',
|
||||
'CHECK (heart_rate_bpm IS NOT NULL OR distance_meters IS NOT NULL OR '
|
||||
'calories_kcal IS NOT NULL)',
|
||||
];
|
||||
}
|
||||
|
||||
class WorkoutTelemetryAggregates extends Table {
|
||||
@override
|
||||
String get tableName => 'workout_telemetry_aggregates';
|
||||
|
||||
TextColumn get sessionId => text().withLength(min: 1)();
|
||||
TextColumn get scope => text()();
|
||||
IntColumn get programIndex => integer().nullable()();
|
||||
IntColumn get exerciseIndex => integer().nullable()();
|
||||
IntColumn get setIndex => integer().nullable()();
|
||||
IntColumn get passageIndex => integer().nullable()();
|
||||
IntColumn get stepIndex => integer().nullable()();
|
||||
IntColumn get sampleCount => integer()();
|
||||
IntColumn get minHeartRateBpm => integer().nullable()();
|
||||
RealColumn get averageHeartRateBpm => real().nullable()();
|
||||
IntColumn get maxHeartRateBpm => integer().nullable()();
|
||||
RealColumn get totalDistanceMeters => real().nullable()();
|
||||
RealColumn get totalCaloriesKcal => real().nullable()();
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => [
|
||||
"CHECK (scope IN ('session', 'exercise', 'set', 'step'))",
|
||||
'CHECK (program_index IS NULL OR program_index >= 0)',
|
||||
'CHECK (exercise_index IS NULL OR exercise_index >= 0)',
|
||||
'CHECK (set_index IS NULL OR set_index >= 0)',
|
||||
'CHECK (passage_index IS NULL OR passage_index >= 0)',
|
||||
'CHECK (step_index IS NULL OR step_index >= 0)',
|
||||
'CHECK (sample_count >= 0)',
|
||||
'CHECK (min_heart_rate_bpm IS NULL OR min_heart_rate_bpm > 0)',
|
||||
'CHECK (average_heart_rate_bpm IS NULL OR average_heart_rate_bpm > 0)',
|
||||
'CHECK (max_heart_rate_bpm IS NULL OR max_heart_rate_bpm > 0)',
|
||||
'CHECK (total_distance_meters IS NULL OR total_distance_meters >= 0)',
|
||||
'CHECK (total_calories_kcal IS NULL OR total_calories_kcal >= 0)',
|
||||
'UNIQUE (session_id, scope, program_index, exercise_index, set_index, '
|
||||
'passage_index, step_index)',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user