feat(ui): integre affichage stats par scope et selection des types metier d'exercice cote telephone
UI telephone pour #179 (graphes/statistiques par etape, serie, exercice, seance) et #183 (choix des types metier d'exercice, hors pont Health Services). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -30,6 +30,7 @@ final class WorkoutExecutionScreen extends StatefulWidget {
|
||||
this.videoMediaBuilder,
|
||||
this.stepUseCases,
|
||||
this.sensorUseCases,
|
||||
this.telemetryUseCases,
|
||||
this.stepAudioCuePlayer,
|
||||
this.watchAlertPublisher,
|
||||
super.key,
|
||||
@ -46,6 +47,7 @@ final class WorkoutExecutionScreen extends StatefulWidget {
|
||||
final Future<MediaAsset?> Function(String id)? mediaAssetLoader;
|
||||
final VideoMediaBuilder? videoMediaBuilder;
|
||||
final ActiveWorkoutSensorUseCases? sensorUseCases;
|
||||
final WorkoutTelemetryUseCases? telemetryUseCases;
|
||||
final ExerciseStepAudioCuePlayer? stepAudioCuePlayer;
|
||||
final WatchAlertPublisher? watchAlertPublisher;
|
||||
|
||||
@ -274,11 +276,11 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
: null,
|
||||
),
|
||||
),
|
||||
if (_hasLiveSensorMetrics(_sensorState)) ...[
|
||||
if (_shouldShowLiveSensorBar) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
child: _LiveSensorBar(
|
||||
sensorState: _sensorState!,
|
||||
sensorState: _sensorState,
|
||||
muted: _isSensorStateStale(_sensorState),
|
||||
),
|
||||
),
|
||||
@ -405,10 +407,10 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text('Repos avant la prochaine série'),
|
||||
if (_hasLiveSensorMetrics(_sensorState)) ...[
|
||||
if (_shouldShowLiveSensorBar) ...[
|
||||
const SizedBox(height: 12),
|
||||
_LiveSensorBar(
|
||||
sensorState: _sensorState!,
|
||||
sensorState: _sensorState,
|
||||
muted: _isSensorStateStale(_sensorState),
|
||||
),
|
||||
],
|
||||
@ -485,10 +487,10 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text('Pause', style: Theme.of(context).textTheme.headlineMedium),
|
||||
if (_hasLiveSensorMetrics(_sensorState)) ...[
|
||||
if (_shouldShowLiveSensorBar) ...[
|
||||
const SizedBox(height: 12),
|
||||
_LiveSensorBar(
|
||||
sensorState: _sensorState!,
|
||||
sensorState: _sensorState,
|
||||
muted: _isSensorStateStale(_sensorState),
|
||||
),
|
||||
],
|
||||
@ -638,6 +640,8 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
return hasGlobalTimer && !_hasSetExecutionStarted;
|
||||
}
|
||||
|
||||
bool get _shouldShowLiveSensorBar => widget.sensorUseCases != null;
|
||||
|
||||
bool get _usesSetTimerForStopwatchScore {
|
||||
return _exercise.timeEnabled && _exercise.stopwatchScoreEnabled;
|
||||
}
|
||||
@ -1837,6 +1841,7 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
workoutTemplateUseCases: widget.workoutTemplateUseCases,
|
||||
activeUseCases: widget.activeUseCases,
|
||||
stepUseCases: widget.stepUseCases,
|
||||
telemetryUseCases: widget.telemetryUseCases,
|
||||
closeUseCase: widget.closeUseCase,
|
||||
),
|
||||
),
|
||||
@ -1894,6 +1899,7 @@ final class _WorkoutExecutionScreenState extends State<WorkoutExecutionScreen> {
|
||||
initialSession: session,
|
||||
activeUseCases: widget.activeUseCases,
|
||||
stepUseCases: widget.stepUseCases,
|
||||
telemetryUseCases: widget.telemetryUseCases,
|
||||
closeUseCase: widget.closeUseCase,
|
||||
historyUseCases: widget.historyUseCases,
|
||||
workoutTemplateUseCases: widget.workoutTemplateUseCases,
|
||||
@ -2306,7 +2312,7 @@ final class _ExecutionAppBarTitle extends StatelessWidget {
|
||||
final class _LiveSensorBar extends StatelessWidget {
|
||||
const _LiveSensorBar({required this.sensorState, required this.muted});
|
||||
|
||||
final ActiveWorkoutSensorState sensorState;
|
||||
final ActiveWorkoutSensorState? sensorState;
|
||||
final bool muted;
|
||||
|
||||
@override
|
||||
@ -2318,24 +2324,21 @@ final class _LiveSensorBar extends StatelessWidget {
|
||||
runSpacing: 4,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
if (sensorState.latestHeartRateBpm case final heartRate?)
|
||||
_LiveSensorPill(
|
||||
icon: Icons.favorite,
|
||||
label: 'FC $heartRate bpm',
|
||||
muted: false,
|
||||
),
|
||||
if (_distanceLabel(sensorState) case final distanceLabel?)
|
||||
_LiveSensorPill(
|
||||
icon: Icons.directions_run,
|
||||
label: distanceLabel,
|
||||
muted: false,
|
||||
),
|
||||
if (_caloriesLabel(sensorState) case final caloriesLabel?)
|
||||
_LiveSensorPill(
|
||||
icon: Icons.local_fire_department,
|
||||
label: caloriesLabel,
|
||||
muted: false,
|
||||
),
|
||||
_LiveSensorPill(
|
||||
icon: Icons.favorite,
|
||||
label: _heartRateMetricLabel(sensorState),
|
||||
muted: sensorState?.latestHeartRateBpm == null,
|
||||
),
|
||||
_LiveSensorPill(
|
||||
icon: Icons.directions_run,
|
||||
label: _distanceMetricLabel(sensorState),
|
||||
muted: _distanceLabel(sensorState) == null,
|
||||
),
|
||||
_LiveSensorPill(
|
||||
icon: Icons.local_fire_department,
|
||||
label: _caloriesMetricLabel(sensorState),
|
||||
muted: _caloriesLabel(sensorState) == null,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -5320,6 +5323,29 @@ bool _isSensorStateStale(ActiveWorkoutSensorState? state) {
|
||||
const Duration(seconds: 15);
|
||||
}
|
||||
|
||||
String _heartRateMetricLabel(ActiveWorkoutSensorState? state) {
|
||||
final heartRate = state?.latestHeartRateBpm;
|
||||
if (heartRate != null) {
|
||||
return 'FC $heartRate bpm';
|
||||
}
|
||||
return _sensorUnavailableLabel(state);
|
||||
}
|
||||
|
||||
String _distanceMetricLabel(ActiveWorkoutSensorState? state) {
|
||||
return _distanceLabel(state) ?? _sensorUnavailableLabel(state);
|
||||
}
|
||||
|
||||
String _caloriesMetricLabel(ActiveWorkoutSensorState? state) {
|
||||
return _caloriesLabel(state) ?? _sensorUnavailableLabel(state);
|
||||
}
|
||||
|
||||
String _sensorUnavailableLabel(ActiveWorkoutSensorState? state) {
|
||||
if (state == null) {
|
||||
return 'Mesure en attente';
|
||||
}
|
||||
return 'Donnée indisponible';
|
||||
}
|
||||
|
||||
String? _distanceLabel(ActiveWorkoutSensorState? state) {
|
||||
final meters = state?.latestDistanceMeters;
|
||||
if (meters == null || meters < 0) {
|
||||
@ -5339,13 +5365,6 @@ String? _caloriesLabel(ActiveWorkoutSensorState? state) {
|
||||
return null;
|
||||
}
|
||||
|
||||
bool _hasLiveSensorMetrics(ActiveWorkoutSensorState? state) {
|
||||
return state != null &&
|
||||
(state.latestHeartRateBpm != null ||
|
||||
state.latestDistanceMeters != null ||
|
||||
_caloriesLabel(state) != null);
|
||||
}
|
||||
|
||||
String _formatStepCountdown(Duration duration) {
|
||||
final totalSeconds = (duration.inMilliseconds / 1000)
|
||||
.ceil()
|
||||
|
||||
Reference in New Issue
Block a user