fix(core): fusionne la strategie Health Services au niveau seance et nullifie sourceExerciseId au pull si absent

Complement #183 : fusion de la strategie Health Services au niveau seance pour eviter que le premier exercice fige tout le choix de strategie sur la seance.
Fix #185 : nullification defensive de sourceExerciseId au pull quand l'exercice source est absent, pour ne plus casser le chargement des programmes apres suppression d'un exercice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 17:12:03 +02:00
parent c43b93ef99
commit 326bd1893c
4 changed files with 249 additions and 32 deletions

View File

@ -4160,6 +4160,10 @@ final class WatchSessionProjectionProjector {
currentStep: currentStep,
timers: allTimers,
);
final healthServicesExerciseTypeStrategy =
_sessionHealthServicesExerciseTypeStrategy(
session.resolvedTemplateSnapshotJson,
);
return WatchSessionProjection(
deviceSessionId: session.metadata.id,
@ -4220,8 +4224,7 @@ final class WatchSessionProjectionProjector {
manualScoreTargetLabel: manualScoreProjection?.targetLabel,
manualScoreRepsTargetValue: manualScoreProjection?.repsTargetValue,
manualScoreScope: manualScoreProjection?.scope,
healthServicesExerciseTypeStrategy:
snapshot.healthServicesExerciseTypeStrategy,
healthServicesExerciseTypeStrategy: healthServicesExerciseTypeStrategy,
);
}
@ -7216,14 +7219,73 @@ _ResolvedExerciseSnapshot? _findExerciseSnapshot({
scoreUnitSnapshot: exercise['scoreUnitSnapshot'] as String?,
setsCount: exercise['setsCount'] as int? ?? 0,
restSeconds: exercise['restSecondsOverride'] as int? ?? 0,
healthServicesExerciseTypeStrategy: _stringListFromSnapshot(
exercise['healthServicesExerciseTypeStrategy'],
),
steps: _exerciseStepsFromSnapshot(exercise['exerciseStepsSnapshot']),
autoStartNextTimedStepEffective: autoStartNextTimedStepEffective,
);
}
const _legacyHealthServicesExerciseTypeStrategy = [
'RUNNING',
'WALKING',
'HIGH_INTENSITY_INTERVAL_TRAINING',
'WORKOUT',
];
const _knownHealthServicesExerciseTypeNames = {
'RUNNING',
'WALKING',
'HIGH_INTENSITY_INTERVAL_TRAINING',
'WORKOUT',
};
List<String> _sessionHealthServicesExerciseTypeStrategy(
String resolvedTemplateSnapshotJson,
) {
try {
final decoded =
jsonDecode(resolvedTemplateSnapshotJson) as Map<String, dynamic>;
final programs = (decoded['programs'] as List<dynamic>? ?? const []);
final output = <String>[];
void add(String type) {
if (_knownHealthServicesExerciseTypeNames.contains(type) &&
!output.contains(type)) {
output.add(type);
}
}
for (final rawProgram in programs) {
if (rawProgram is! Map) {
continue;
}
final program = Map<String, dynamic>.from(rawProgram);
final programSnapshotJson = program['programSnapshotJson'] as String?;
if (programSnapshotJson == null) {
continue;
}
final programSnapshot =
jsonDecode(programSnapshotJson) as Map<String, dynamic>;
final exercises =
(programSnapshot['exercises'] as List<dynamic>? ?? const []);
for (final rawExercise in exercises) {
if (rawExercise is! Map) {
continue;
}
final exercise = Map<String, dynamic>.from(rawExercise);
for (final type in _stringListFromSnapshot(
exercise['healthServicesExerciseTypeStrategy'],
)) {
add(type);
}
}
}
return output.isEmpty
? _legacyHealthServicesExerciseTypeStrategy
: List.unmodifiable(output);
} on Object {
return _legacyHealthServicesExerciseTypeStrategy;
}
}
bool? _templateStepChainingOverride(
Map<String, dynamic> resolvedTemplateSnapshot, {
required String workoutTemplateProgramId,
@ -7439,9 +7501,6 @@ Map<String, _ResolvedExerciseSnapshot> _exerciseSnapshotsById(
scoreUnitSnapshot: exercise['scoreUnitSnapshot'] as String?,
setsCount: exercise['setsCount'] as int? ?? 0,
restSeconds: exercise['restSecondsOverride'] as int? ?? 0,
healthServicesExerciseTypeStrategy: _stringListFromSnapshot(
exercise['healthServicesExerciseTypeStrategy'],
),
steps: _exerciseStepsFromSnapshot(exercise['exerciseStepsSnapshot']),
autoStartNextTimedStepEffective:
(exercise['autoStartNextTimedStepOverride'] as bool?) ??
@ -7472,7 +7531,6 @@ final class _ResolvedExerciseSnapshot {
this.scoreUnitSnapshot,
required this.setsCount,
required this.restSeconds,
this.healthServicesExerciseTypeStrategy = const [],
this.steps = const [],
this.autoStartNextTimedStepEffective = true,
});
@ -7494,7 +7552,6 @@ final class _ResolvedExerciseSnapshot {
final String? scoreUnitSnapshot;
final int setsCount;
final int restSeconds;
final List<String> healthServicesExerciseTypeStrategy;
final List<ExerciseStep> steps;
final bool autoStartNextTimedStepEffective;
}