chore(wip): lot #165-169 validé QA + rework foreground #163 (KO - preuve device/toolchain insuffisante)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -162,13 +162,9 @@ final class HistoryDetailScreen extends StatelessWidget {
|
||||
Text(_formatDateTime(history.startedAt)),
|
||||
const SizedBox(height: 4),
|
||||
Text('Durée : ${_formatDurationMs(history.totalActiveMs)}'),
|
||||
if (history.averageHeartRateBpm != null &&
|
||||
history.maxHeartRateBpm != null) ...[
|
||||
if (_hasWatchStats(history)) ...[
|
||||
const SizedBox(height: 16),
|
||||
_HistoryHeartRateSummary(
|
||||
averageHeartRateBpm: history.averageHeartRateBpm!,
|
||||
maxHeartRateBpm: history.maxHeartRateBpm!,
|
||||
),
|
||||
_HistoryWatchStatsSummary(history: history),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
for (final program in detail.programs) ...[
|
||||
@ -315,52 +311,163 @@ final class HistoryDetailScreen extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
final class _HistoryHeartRateSummary extends StatelessWidget {
|
||||
const _HistoryHeartRateSummary({
|
||||
required this.averageHeartRateBpm,
|
||||
required this.maxHeartRateBpm,
|
||||
});
|
||||
final class _HistoryWatchStatsSummary extends StatelessWidget {
|
||||
const _HistoryWatchStatsSummary({required this.history});
|
||||
|
||||
final double averageHeartRateBpm;
|
||||
final int maxHeartRateBpm;
|
||||
final WorkoutHistory history;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final minHeartRate = history.minHeartRateBpm;
|
||||
final averageHeartRate = history.averageHeartRateBpm;
|
||||
final maxHeartRate = history.maxHeartRateBpm;
|
||||
final distance = history.totalDistanceMeters;
|
||||
final calories = history.totalCaloriesKcal;
|
||||
return CourtBlazerAccentPanel(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'Fréquence cardiaque',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
Text('Stats montre', style: Theme.of(context).textTheme.titleSmall),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _HistoryHeartRateMetric(
|
||||
label: 'Moyenne',
|
||||
value: '${averageHeartRateBpm.round()}',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _HistoryHeartRateMetric(
|
||||
label: 'Max',
|
||||
value: '$maxHeartRateBpm',
|
||||
),
|
||||
if (minHeartRate != null ||
|
||||
averageHeartRate != null ||
|
||||
maxHeartRate != null) ...[
|
||||
Text('Fréquence cardiaque'),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
if (minHeartRate != null)
|
||||
Expanded(
|
||||
child: _HistoryWatchMetric(
|
||||
label: 'Min',
|
||||
value: '$minHeartRate bpm',
|
||||
),
|
||||
),
|
||||
if (minHeartRate != null && averageHeartRate != null)
|
||||
const SizedBox(width: 8),
|
||||
if (averageHeartRate != null)
|
||||
Expanded(
|
||||
child: _HistoryWatchMetric(
|
||||
label: 'Moyenne',
|
||||
value: '${averageHeartRate.round()} bpm',
|
||||
),
|
||||
),
|
||||
if ((minHeartRate != null || averageHeartRate != null) &&
|
||||
maxHeartRate != null)
|
||||
const SizedBox(width: 8),
|
||||
if (maxHeartRate != null)
|
||||
Expanded(
|
||||
child: _HistoryWatchMetric(
|
||||
label: 'Max',
|
||||
value: '$maxHeartRate bpm',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (averageHeartRate != null && maxHeartRate != null) ...[
|
||||
const SizedBox(height: 10),
|
||||
_HistoryWatchStatsBars(
|
||||
minHeartRateBpm: minHeartRate,
|
||||
averageHeartRateBpm: averageHeartRate,
|
||||
maxHeartRateBpm: maxHeartRate,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
if (distance != null || calories != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
if (distance != null)
|
||||
Expanded(
|
||||
child: _HistoryWatchMetric(
|
||||
label: 'Distance',
|
||||
value: _formatHistoryDistance(distance),
|
||||
),
|
||||
),
|
||||
if (distance != null && calories != null)
|
||||
const SizedBox(width: 8),
|
||||
if (calories != null)
|
||||
Expanded(
|
||||
child: _HistoryWatchMetric(
|
||||
label: 'Calories',
|
||||
value: '${calories.round()} kcal',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class _HistoryHeartRateMetric extends StatelessWidget {
|
||||
const _HistoryHeartRateMetric({required this.label, required this.value});
|
||||
final class _HistoryWatchStatsBars extends StatelessWidget {
|
||||
const _HistoryWatchStatsBars({
|
||||
required this.minHeartRateBpm,
|
||||
required this.averageHeartRateBpm,
|
||||
required this.maxHeartRateBpm,
|
||||
});
|
||||
|
||||
final int? minHeartRateBpm;
|
||||
final double averageHeartRateBpm;
|
||||
final int maxHeartRateBpm;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final max = maxHeartRateBpm.toDouble().clamp(1, double.infinity);
|
||||
final values = [
|
||||
if (minHeartRateBpm != null) ('Min', minHeartRateBpm!.toDouble()),
|
||||
('Moy', averageHeartRateBpm),
|
||||
('Max', maxHeartRateBpm.toDouble()),
|
||||
];
|
||||
return Semantics(
|
||||
label: 'Graphique stats montre',
|
||||
key: const ValueKey('history-watch-stats-graph'),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
for (final item in values) ...[
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 52,
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: FractionallySizedBox(
|
||||
heightFactor: (item.$2 / max).clamp(0.08, 1),
|
||||
widthFactor: 0.62,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFC9A24A),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
item.$1,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (item != values.last) const SizedBox(width: 8),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class _HistoryWatchMetric extends StatelessWidget {
|
||||
const _HistoryWatchMetric({required this.label, required this.value});
|
||||
|
||||
final String label;
|
||||
final String value;
|
||||
@ -373,14 +480,8 @@ final class _HistoryHeartRateMetric extends StatelessWidget {
|
||||
Text(label, style: Theme.of(context).textTheme.labelMedium),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: AppTextStyles.scoreNumber(context),
|
||||
children: [
|
||||
TextSpan(text: value),
|
||||
TextSpan(
|
||||
text: ' bpm',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
style: AppTextStyles.scoreNumber(context).copyWith(fontSize: 24),
|
||||
children: [TextSpan(text: value)],
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -388,6 +489,21 @@ final class _HistoryHeartRateMetric extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
bool _hasWatchStats(WorkoutHistory history) {
|
||||
return history.minHeartRateBpm != null ||
|
||||
history.averageHeartRateBpm != null ||
|
||||
history.maxHeartRateBpm != null ||
|
||||
history.totalDistanceMeters != null ||
|
||||
history.totalCaloriesKcal != null;
|
||||
}
|
||||
|
||||
String _formatHistoryDistance(double meters) {
|
||||
if (meters >= 1000) {
|
||||
return '${(meters / 1000).toStringAsFixed(2)} km';
|
||||
}
|
||||
return '${meters.round()} m';
|
||||
}
|
||||
|
||||
final class HistoryDetailData {
|
||||
const HistoryDetailData({required this.programs});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user