feat(watch): clôture lot #91 - fréquence cardiaque live, notifications de séance et finitions montre
Consolide le lot applicatif watch companion validé : - télémétrie fréquence cardiaque live remontée montre -> téléphone (collecteur watch, adapter Wear Data Layer, persistance Drift, propagation aux écrans historique/programme/profil/exécution) - notifications de séance en arrière-plan côté téléphone (service foreground de statut + passerelle applicative) - finitions montre : chrono d'étape, score d'étape, retrait du bouton "lancer une séance", thème, icônes et polices watch_app Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -375,6 +375,11 @@ final class Exercise {
|
||||
defaultTargetScoreTimeMs,
|
||||
'Default target score time ms',
|
||||
);
|
||||
_requireLinkedStepSeriesScoreShape(
|
||||
steps: steps,
|
||||
scoreEnabled: hasScoreMeasure,
|
||||
scoreInputMode: scoreInputMode,
|
||||
);
|
||||
}
|
||||
|
||||
final EntityMetadata metadata;
|
||||
@ -508,6 +513,7 @@ final class ExerciseStep {
|
||||
this.scoreUnit,
|
||||
this.defaultTargetScore,
|
||||
this.defaultTargetScoreTimeMs,
|
||||
this.linkedToSeriesScore = false,
|
||||
}) : id = _nonBlank(id, 'Exercise step id'),
|
||||
name = _nonBlank(name, 'Exercise step name') {
|
||||
_requireNonNegative(position, 'Exercise step position');
|
||||
@ -519,6 +525,7 @@ final class ExerciseStep {
|
||||
scoreUnit: scoreUnit,
|
||||
defaultTargetScore: defaultTargetScore,
|
||||
defaultTargetScoreTimeMs: defaultTargetScoreTimeMs,
|
||||
linkedToSeriesScore: linkedToSeriesScore,
|
||||
);
|
||||
}
|
||||
|
||||
@ -533,6 +540,7 @@ final class ExerciseStep {
|
||||
final String? scoreUnit;
|
||||
final double? defaultTargetScore;
|
||||
final int? defaultTargetScoreTimeMs;
|
||||
final bool linkedToSeriesScore;
|
||||
|
||||
Map<String, Object?> toSnapshotJson() => {
|
||||
'id': id,
|
||||
@ -546,6 +554,7 @@ final class ExerciseStep {
|
||||
'scoreUnit': scoreUnit,
|
||||
'defaultTargetScore': defaultTargetScore,
|
||||
'defaultTargetScoreTimeMs': defaultTargetScoreTimeMs,
|
||||
'linkedToSeriesScore': linkedToSeriesScore,
|
||||
};
|
||||
}
|
||||
|
||||
@ -658,6 +667,11 @@ final class ProgramExercise {
|
||||
targetScore: targetScore,
|
||||
targetScoreTimeMs: targetScoreTimeMs,
|
||||
);
|
||||
_requireLinkedStepSeriesScoreShape(
|
||||
steps: exerciseStepsSnapshot,
|
||||
scoreEnabled: scoreEnabled,
|
||||
scoreInputMode: scoreInputModeSnapshot,
|
||||
);
|
||||
}
|
||||
|
||||
final EntityMetadata metadata;
|
||||
@ -1100,6 +1114,47 @@ final class ActiveScoreStopwatchState {
|
||||
}
|
||||
}
|
||||
|
||||
final class ActiveManualScoreState {
|
||||
ActiveManualScoreState({
|
||||
required this.metadata,
|
||||
required this.activeWorkoutSessionId,
|
||||
required this.programIndex,
|
||||
required this.exerciseIndex,
|
||||
required this.setIndex,
|
||||
required this.value,
|
||||
required this.updatedAt,
|
||||
}) {
|
||||
_requireNonNegative(programIndex, 'Program index');
|
||||
_requireNonNegative(exerciseIndex, 'Exercise index');
|
||||
_requireNonNegative(setIndex, 'Set index');
|
||||
_requireNullableNonNegativeDouble(value, 'Manual score value');
|
||||
}
|
||||
|
||||
final EntityMetadata metadata;
|
||||
final String activeWorkoutSessionId;
|
||||
final int programIndex;
|
||||
final int exerciseIndex;
|
||||
final int setIndex;
|
||||
final double value;
|
||||
final DateTime updatedAt;
|
||||
|
||||
ActiveManualScoreState copyWith({
|
||||
EntityMetadata? metadata,
|
||||
double? value,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return ActiveManualScoreState(
|
||||
metadata: metadata ?? this.metadata,
|
||||
activeWorkoutSessionId: activeWorkoutSessionId,
|
||||
programIndex: programIndex,
|
||||
exerciseIndex: exerciseIndex,
|
||||
setIndex: setIndex,
|
||||
value: value ?? this.value,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class ActiveSetTimerState {
|
||||
ActiveSetTimerState({
|
||||
required this.metadata,
|
||||
@ -1405,7 +1460,7 @@ final class ActiveExerciseStepResult {
|
||||
}
|
||||
|
||||
final class WorkoutHistory {
|
||||
const WorkoutHistory({
|
||||
WorkoutHistory({
|
||||
required this.metadata,
|
||||
this.sourceWorkoutTemplateId,
|
||||
this.sourceActiveWorkoutSessionId,
|
||||
@ -1415,9 +1470,17 @@ final class WorkoutHistory {
|
||||
required this.totalActiveMs,
|
||||
required this.completed,
|
||||
required this.historySnapshotJson,
|
||||
this.averageHeartRateBpm,
|
||||
this.maxHeartRateBpm,
|
||||
this.results = const [],
|
||||
this.stepResults = const [],
|
||||
});
|
||||
}) {
|
||||
_requireNullablePositiveDouble(
|
||||
averageHeartRateBpm,
|
||||
'Average heart rate bpm',
|
||||
);
|
||||
_requireNullablePositive(maxHeartRateBpm, 'Max heart rate bpm');
|
||||
}
|
||||
|
||||
final EntityMetadata metadata;
|
||||
final String? sourceWorkoutTemplateId;
|
||||
@ -1428,8 +1491,36 @@ final class WorkoutHistory {
|
||||
final int totalActiveMs;
|
||||
final bool completed;
|
||||
final String historySnapshotJson;
|
||||
final double? averageHeartRateBpm;
|
||||
final int? maxHeartRateBpm;
|
||||
final List<WorkoutHistorySetResult> results;
|
||||
final List<WorkoutHistoryStepResult> stepResults;
|
||||
|
||||
WorkoutHistory copyWith({
|
||||
EntityMetadata? metadata,
|
||||
Object? averageHeartRateBpm = _unchanged,
|
||||
Object? maxHeartRateBpm = _unchanged,
|
||||
}) {
|
||||
return WorkoutHistory(
|
||||
metadata: metadata ?? this.metadata,
|
||||
sourceWorkoutTemplateId: sourceWorkoutTemplateId,
|
||||
sourceActiveWorkoutSessionId: sourceActiveWorkoutSessionId,
|
||||
nameSnapshot: nameSnapshot,
|
||||
startedAt: startedAt,
|
||||
endedAt: endedAt,
|
||||
totalActiveMs: totalActiveMs,
|
||||
completed: completed,
|
||||
historySnapshotJson: historySnapshotJson,
|
||||
averageHeartRateBpm: averageHeartRateBpm == _unchanged
|
||||
? this.averageHeartRateBpm
|
||||
: averageHeartRateBpm as double?,
|
||||
maxHeartRateBpm: maxHeartRateBpm == _unchanged
|
||||
? this.maxHeartRateBpm
|
||||
: maxHeartRateBpm as int?,
|
||||
results: results,
|
||||
stepResults: stepResults,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class WorkoutHistorySetResult {
|
||||
@ -1721,6 +1812,12 @@ void _requireNullableNonNegativeDouble(double? value, String label) {
|
||||
}
|
||||
}
|
||||
|
||||
void _requireNullablePositiveDouble(double? value, String label) {
|
||||
if (value != null && value <= 0) {
|
||||
throw DomainException('$label must be positive.');
|
||||
}
|
||||
}
|
||||
|
||||
void _requireScoreTargetShape({
|
||||
required bool scoreEnabled,
|
||||
required ScoreInputMode scoreInputMode,
|
||||
@ -1753,12 +1850,14 @@ void _validateExerciseStepScoreShape({
|
||||
required String? scoreUnit,
|
||||
required double? defaultTargetScore,
|
||||
required int? defaultTargetScoreTimeMs,
|
||||
required bool linkedToSeriesScore,
|
||||
}) {
|
||||
if (!hasScore) {
|
||||
if (scoreLabel != null ||
|
||||
scoreUnit != null ||
|
||||
defaultTargetScore != null ||
|
||||
defaultTargetScoreTimeMs != null) {
|
||||
defaultTargetScoreTimeMs != null ||
|
||||
linkedToSeriesScore) {
|
||||
throw const DomainException(
|
||||
'Disabled exercise step score must not define score values.',
|
||||
);
|
||||
@ -1784,6 +1883,11 @@ void _validateExerciseStepScoreShape({
|
||||
);
|
||||
}
|
||||
case ScoreInputMode.stopwatch:
|
||||
if (linkedToSeriesScore) {
|
||||
throw const DomainException(
|
||||
'Linked series score requires manual exercise step score.',
|
||||
);
|
||||
}
|
||||
if (scoreLabel != null ||
|
||||
scoreUnit != null ||
|
||||
defaultTargetScore != null) {
|
||||
@ -1820,6 +1924,19 @@ void _requireScoreResultShape({
|
||||
}
|
||||
}
|
||||
|
||||
void _requireLinkedStepSeriesScoreShape({
|
||||
required List<ExerciseStep> steps,
|
||||
required bool scoreEnabled,
|
||||
required ScoreInputMode scoreInputMode,
|
||||
}) {
|
||||
if (steps.any((step) => step.linkedToSeriesScore) &&
|
||||
(!scoreEnabled || scoreInputMode != ScoreInputMode.manual)) {
|
||||
throw const DomainException(
|
||||
'Linked step scores require manual series score.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _validateExerciseStepResult({
|
||||
required int programIndex,
|
||||
required int exerciseIndex,
|
||||
|
||||
Reference in New Issue
Block a user