feat(core): consolide le chainage type metier d'exercice -> strategie Health Services -> payload montre -> consommation Kotlin
Finalise #183 : modele/persistance des types metier d'exercice, mapping vers la strategie Health Services, propagation du payload dans le contrat watch bridge et consommation cote Kotlin montre. Perimetre complet QA vert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -968,6 +968,16 @@ FROM pending_share_actions
|
||||
"business_types_json TEXT NOT NULL DEFAULT '[]' "
|
||||
'CHECK (json_valid(business_types_json))',
|
||||
);
|
||||
await _addColumnIfMissing(
|
||||
tableName: 'program_exercises',
|
||||
columnName: 'health_services_exercise_type_strategy_snapshot_json',
|
||||
definition:
|
||||
'health_services_exercise_type_strategy_snapshot_json TEXT NOT NULL '
|
||||
"DEFAULT '[\"RUNNING\",\"WALKING\","
|
||||
"\"HIGH_INTENSITY_INTERVAL_TRAINING\",\"WORKOUT\"]' "
|
||||
'CHECK (json_valid('
|
||||
'health_services_exercise_type_strategy_snapshot_json))',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _backfillWorkoutHistorySetSourceExerciseIds() async {
|
||||
|
||||
@ -193,6 +193,10 @@ final class DriftStarterSeedRepository
|
||||
await database
|
||||
.into(database.programExercises)
|
||||
.insertOnConflictUpdate(_programExerciseCompanion(exercise));
|
||||
await _writeProgramExerciseHealthServicesStrategySnapshot(
|
||||
database,
|
||||
exercise,
|
||||
);
|
||||
}
|
||||
await database
|
||||
.into(database.workoutTemplates)
|
||||
@ -523,6 +527,10 @@ final class DriftLocalSyncChangeRepository
|
||||
await database
|
||||
.into(database.programExercises)
|
||||
.insertOnConflictUpdate(_programExerciseCompanion(exercise));
|
||||
await _writeProgramExerciseHealthServicesStrategySnapshot(
|
||||
database,
|
||||
exercise,
|
||||
);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
@ -996,7 +1004,7 @@ final class DriftProgramRepository implements ProgramRepository {
|
||||
.get();
|
||||
return _programFromRow(
|
||||
row,
|
||||
exercises.map(_programExerciseFromRow).toList(),
|
||||
await _programExercisesFromRows(database, exercises),
|
||||
isExample: await _isProgramExample(id),
|
||||
);
|
||||
}
|
||||
@ -1021,7 +1029,7 @@ final class DriftProgramRepository implements ProgramRepository {
|
||||
programs.add(
|
||||
_programFromRow(
|
||||
row,
|
||||
exercises.map(_programExerciseFromRow).toList(),
|
||||
await _programExercisesFromRows(database, exercises),
|
||||
isExample: await _isProgramExample(row.id),
|
||||
),
|
||||
);
|
||||
@ -1055,9 +1063,15 @@ final class DriftProgramRepository implements ProgramRepository {
|
||||
tableName: 'program_exercises',
|
||||
entityType: 'ProgramExercise',
|
||||
metadata: exercise.metadata,
|
||||
write: () => database
|
||||
.into(database.programExercises)
|
||||
.insertOnConflictUpdate(_programExerciseCompanion(exercise)),
|
||||
write: () async {
|
||||
await database
|
||||
.into(database.programExercises)
|
||||
.insertOnConflictUpdate(_programExerciseCompanion(exercise));
|
||||
await _writeProgramExerciseHealthServicesStrategySnapshot(
|
||||
database,
|
||||
exercise,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -3461,6 +3475,23 @@ Future<void> _writeExerciseStarterMetadata(
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _writeProgramExerciseHealthServicesStrategySnapshot(
|
||||
db.AppDatabase database,
|
||||
domain.ProgramExercise exercise,
|
||||
) async {
|
||||
await database.customStatement(
|
||||
'UPDATE program_exercises SET '
|
||||
'health_services_exercise_type_strategy_snapshot_json = ? '
|
||||
'WHERE id = ?',
|
||||
[
|
||||
_encodeHealthServicesExerciseTypes(
|
||||
exercise.healthServicesExerciseTypeStrategySnapshot,
|
||||
),
|
||||
exercise.metadata.id,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _writeProgramStarterMetadata(
|
||||
db.AppDatabase database,
|
||||
domain.Program program,
|
||||
@ -4083,7 +4114,21 @@ db.ProgramExercisesCompanion _programExerciseCompanion(
|
||||
);
|
||||
}
|
||||
|
||||
domain.ProgramExercise _programExerciseFromRow(db.ProgramExercise row) {
|
||||
Future<List<domain.ProgramExercise>> _programExercisesFromRows(
|
||||
db.AppDatabase database,
|
||||
List<db.ProgramExercise> rows,
|
||||
) async {
|
||||
final output = <domain.ProgramExercise>[];
|
||||
for (final row in rows) {
|
||||
output.add(await _programExerciseFromRow(database, row));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
Future<domain.ProgramExercise> _programExerciseFromRow(
|
||||
db.AppDatabase database,
|
||||
db.ProgramExercise row,
|
||||
) async {
|
||||
return domain.ProgramExercise(
|
||||
metadata: _metadataFromRow(row),
|
||||
programId: row.programId,
|
||||
@ -4102,6 +4147,8 @@ domain.ProgramExercise _programExerciseFromRow(db.ProgramExercise row) {
|
||||
autoStartNextTimedStepSnapshot: row.autoStartNextTimedStepSnapshot,
|
||||
autoStartNextTimedStepOverride: row.autoStartNextTimedStepOverride,
|
||||
exerciseVideoMediaIdSnapshot: row.exerciseVideoMediaIdSnapshot,
|
||||
healthServicesExerciseTypeStrategySnapshot:
|
||||
await _programExerciseHealthServicesStrategySnapshot(database, row.id),
|
||||
exerciseArchivedSnapshot: row.exerciseArchivedSnapshot,
|
||||
availableTimeSnapshot: row.availableTimeSnapshot,
|
||||
availableRepsSnapshot: row.availableRepsSnapshot,
|
||||
@ -4121,6 +4168,25 @@ domain.ProgramExercise _programExerciseFromRow(db.ProgramExercise row) {
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<domain.HealthServicesExerciseType>>
|
||||
_programExerciseHealthServicesStrategySnapshot(
|
||||
db.AppDatabase database,
|
||||
String id,
|
||||
) async {
|
||||
final row = await database
|
||||
.customSelect(
|
||||
'SELECT health_services_exercise_type_strategy_snapshot_json '
|
||||
'FROM program_exercises WHERE id = ? LIMIT 1',
|
||||
variables: [Variable<String>(id)],
|
||||
)
|
||||
.getSingleOrNull();
|
||||
return _decodeHealthServicesExerciseTypesJson(
|
||||
row?.readNullable<String>(
|
||||
'health_services_exercise_type_strategy_snapshot_json',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
db.WorkoutTemplatesCompanion _workoutTemplateCompanion(
|
||||
domain.WorkoutTemplate template,
|
||||
) {
|
||||
@ -5802,6 +5868,10 @@ List<domain.ProgramExercise> _programExercisesFromPayload(
|
||||
),
|
||||
exerciseVideoMediaIdSnapshot:
|
||||
map['exerciseVideoMediaIdSnapshot'] as String?,
|
||||
healthServicesExerciseTypeStrategySnapshot:
|
||||
_healthServicesExerciseTypesFromPayload(
|
||||
map['healthServicesExerciseTypeStrategy'],
|
||||
),
|
||||
exerciseStepsSnapshot: _stepsFromPayload(
|
||||
map['exerciseStepsSnapshot'],
|
||||
),
|
||||
@ -6069,6 +6139,12 @@ String _encodeBusinessExerciseTypes(List<domain.BusinessExerciseType> types) {
|
||||
return jsonEncode(types.map(_businessExerciseTypeToDb).toList());
|
||||
}
|
||||
|
||||
String _encodeHealthServicesExerciseTypes(
|
||||
List<domain.HealthServicesExerciseType> types,
|
||||
) {
|
||||
return jsonEncode(types.map((type) => type.wireName).toList());
|
||||
}
|
||||
|
||||
List<domain.BusinessExerciseType> _decodeBusinessExerciseTypesJson(
|
||||
String? encoded,
|
||||
) {
|
||||
@ -6083,6 +6159,20 @@ List<domain.BusinessExerciseType> _decodeBusinessExerciseTypesJson(
|
||||
}
|
||||
}
|
||||
|
||||
List<domain.HealthServicesExerciseType> _decodeHealthServicesExerciseTypesJson(
|
||||
String? encoded,
|
||||
) {
|
||||
if (encoded == null || encoded.trim().isEmpty) {
|
||||
return domain.legacyHealthServicesExerciseTypeStrategy;
|
||||
}
|
||||
try {
|
||||
final decoded = jsonDecode(encoded);
|
||||
return _healthServicesExerciseTypesFromPayload(decoded);
|
||||
} on Object {
|
||||
return domain.legacyHealthServicesExerciseTypeStrategy;
|
||||
}
|
||||
}
|
||||
|
||||
List<domain.BusinessExerciseType> _businessExerciseTypesFromPayload(
|
||||
Object? value,
|
||||
) {
|
||||
@ -6099,6 +6189,24 @@ List<domain.BusinessExerciseType> _businessExerciseTypesFromPayload(
|
||||
return List.unmodifiable(output);
|
||||
}
|
||||
|
||||
List<domain.HealthServicesExerciseType> _healthServicesExerciseTypesFromPayload(
|
||||
Object? value,
|
||||
) {
|
||||
if (value is! List) {
|
||||
return domain.legacyHealthServicesExerciseTypeStrategy;
|
||||
}
|
||||
final output = <domain.HealthServicesExerciseType>[];
|
||||
for (final raw in value.whereType<String>()) {
|
||||
final type = _healthServicesExerciseTypeFromDbOrNull(raw);
|
||||
if (type != null && !output.contains(type)) {
|
||||
output.add(type);
|
||||
}
|
||||
}
|
||||
return output.isEmpty
|
||||
? domain.legacyHealthServicesExerciseTypeStrategy
|
||||
: List.unmodifiable(output);
|
||||
}
|
||||
|
||||
List<domain.ExerciseStep> _stepsFromPayload(Object? value) {
|
||||
if (value is! List) {
|
||||
return const [];
|
||||
@ -6364,6 +6472,17 @@ domain.BusinessExerciseType? _businessExerciseTypeFromDbOrNull(String value) =>
|
||||
_ => null,
|
||||
};
|
||||
|
||||
domain.HealthServicesExerciseType? _healthServicesExerciseTypeFromDbOrNull(
|
||||
String value,
|
||||
) => switch (value) {
|
||||
'RUNNING' => domain.HealthServicesExerciseType.running,
|
||||
'WALKING' => domain.HealthServicesExerciseType.walking,
|
||||
'HIGH_INTENSITY_INTERVAL_TRAINING' =>
|
||||
domain.HealthServicesExerciseType.highIntensityIntervalTraining,
|
||||
'WORKOUT' => domain.HealthServicesExerciseType.workout,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
String _setResultStatusToDb(domain.SetResultStatus status) => switch (status) {
|
||||
domain.SetResultStatus.completed => 'completed',
|
||||
domain.SetResultStatus.skipped => 'skipped',
|
||||
|
||||
Reference in New Issue
Block a user