|
@@ -56,9 +56,7 @@ class ActivityBurnHeartRateZoneCard extends StatelessWidget { |
|
@@ -56,9 +56,7 @@ class ActivityBurnHeartRateZoneCard extends StatelessWidget { |
|
56
|
Text(
|
56
|
Text(
|
|
57
|
HealthReportSubjectScope.titleOf(
|
57
|
HealthReportSubjectScope.titleOf(
|
|
58
|
context,
|
58
|
context,
|
|
59
|
- summary.hasData
|
|
|
|
60
|
- ? context.l10n.activityHeartRateZone
|
|
|
|
61
|
- : context.l10n.activityRealtimeHeartRate,
|
59
|
+ context.l10n.activityHeartRateZone,
|
|
62
|
),
|
60
|
),
|
|
63
|
style: TextStyle(
|
61
|
style: TextStyle(
|
|
64
|
color: _h1,
|
62
|
color: _h1,
|
|
@@ -284,11 +282,13 @@ class ActivityBurnHeartRateZoneCard extends StatelessWidget { |
|
@@ -284,11 +282,13 @@ class ActivityBurnHeartRateZoneCard extends StatelessWidget { |
|
284
|
}
|
282
|
}
|
|
285
|
|
283
|
|
|
286
|
DateTime get _startTime {
|
284
|
DateTime get _startTime {
|
|
287
|
- if (summary.startTime != null) return summary.startTime!;
|
|
|
|
288
|
- if (summary.points.isNotEmpty) return summary.points.first.time;
|
285
|
+ if (summary.startTime != null) return _dayStart(summary.startTime!);
|
|
|
|
286
|
+ if (summary.points.isNotEmpty) return _dayStart(summary.points.first.time);
|
|
289
|
return DateTime(0);
|
287
|
return DateTime(0);
|
|
290
|
}
|
288
|
}
|
|
291
|
|
289
|
|
|
|
|
290
|
+ DateTime _dayStart(DateTime time) => DateTime(time.year, time.month, time.day);
|
|
|
|
291
|
+
|
|
292
|
List<ActivityBurnHeartRatePoint> _points(DateTime start, DateTime end) {
|
292
|
List<ActivityBurnHeartRatePoint> _points(DateTime start, DateTime end) {
|
|
293
|
return summary.points
|
293
|
return summary.points
|
|
294
|
.where(
|
294
|
.where(
|
|
@@ -463,27 +463,26 @@ class _HeartRateAxis { |
|
@@ -463,27 +463,26 @@ class _HeartRateAxis { |
|
463
|
ActivityBurnHeartRateSummary summary,
|
463
|
ActivityBurnHeartRateSummary summary,
|
|
464
|
DateTime start,
|
464
|
DateTime start,
|
|
465
|
) {
|
465
|
) {
|
|
|
|
466
|
+ final maxMinutes = _dynamicMaxMinutes(start, DateTime.now());
|
|
466
|
final dayEnd = start.add(const Duration(days: 1));
|
467
|
final dayEnd = start.add(const Duration(days: 1));
|
|
467
|
final points = summary.points
|
468
|
final points = summary.points
|
|
468
|
.where(
|
469
|
.where(
|
|
469
|
(point) =>
|
470
|
(point) =>
|
|
470
|
point.bpm > 0 &&
|
471
|
point.bpm > 0 &&
|
|
471
|
!point.time.isBefore(start) &&
|
472
|
!point.time.isBefore(start) &&
|
|
472
|
- point.time.isBefore(dayEnd),
|
473
|
+ point.time.isBefore(dayEnd) &&
|
|
|
|
474
|
+ point.time.difference(start).inMinutes <= maxMinutes,
|
|
473
|
)
|
475
|
)
|
|
474
|
.toList();
|
476
|
.toList();
|
|
475
|
if (points.isEmpty) {
|
477
|
if (points.isEmpty) {
|
|
476
|
return _HeartRateAxis(
|
478
|
return _HeartRateAxis(
|
|
477
|
- endTime: start.add(const Duration(hours: 18)),
|
|
|
|
478
|
- maxX: 1080,
|
479
|
+ endTime: start.add(Duration(minutes: maxMinutes)),
|
|
|
|
480
|
+ maxX: maxMinutes.toDouble(),
|
|
479
|
minY: 40,
|
481
|
minY: 40,
|
|
480
|
maxY: 200,
|
482
|
maxY: 200,
|
|
481
|
);
|
483
|
);
|
|
482
|
}
|
484
|
}
|
|
483
|
|
485
|
|
|
484
|
- final coveredEnd = points.last.time;
|
|
|
|
485
|
- final coveredMinutes = coveredEnd.difference(start).inMinutes;
|
|
|
|
486
|
- final maxMinutes = _nextSixHourTick(coveredMinutes);
|
|
|
|
487
|
final heartRates = points.map((point) => point.bpm);
|
486
|
final heartRates = points.map((point) => point.bpm);
|
|
488
|
final minY = heartRates.reduce(math.min);
|
487
|
final minY = heartRates.reduce(math.min);
|
|
489
|
var maxY = _roundUpToFiveOrTen(heartRates.reduce(math.max));
|
488
|
var maxY = _roundUpToFiveOrTen(heartRates.reduce(math.max));
|
|
@@ -506,11 +505,21 @@ class _HeartRateAxis { |
|
@@ -506,11 +505,21 @@ class _HeartRateAxis { |
|
506
|
return value.toStringAsFixed(1);
|
505
|
return value.toStringAsFixed(1);
|
|
507
|
}
|
506
|
}
|
|
508
|
|
507
|
|
|
|
|
508
|
+ static int _dynamicMaxMinutes(DateTime start, DateTime now) {
|
|
|
|
509
|
+ final today = DateTime(now.year, now.month, now.day);
|
|
|
|
510
|
+ if (start.isBefore(today)) return 1440;
|
|
|
|
511
|
+ if (start.isAfter(today)) return 360;
|
|
|
|
512
|
+
|
|
|
|
513
|
+ final coveredMinutes = now.difference(start).inMinutes;
|
|
|
|
514
|
+ return _nextSixHourTick(coveredMinutes);
|
|
|
|
515
|
+ }
|
|
|
|
516
|
+
|
|
509
|
static int _nextSixHourTick(int coveredMinutes) {
|
517
|
static int _nextSixHourTick(int coveredMinutes) {
|
|
510
|
const tickMinutes = 360;
|
518
|
const tickMinutes = 360;
|
|
511
|
final clamped = coveredMinutes.clamp(0, 1440);
|
519
|
final clamped = coveredMinutes.clamp(0, 1440);
|
|
512
|
if (clamped == 0) return tickMinutes;
|
520
|
if (clamped == 0) return tickMinutes;
|
|
513
|
- return (clamped ~/ tickMinutes + 1).clamp(1, 4) * tickMinutes;
|
521
|
+ final tickIndex = (clamped / tickMinutes).ceil().clamp(1, 4).toInt();
|
|
|
|
522
|
+ return tickIndex * tickMinutes;
|
|
514
|
}
|
523
|
}
|
|
515
|
|
524
|
|
|
516
|
static double _roundUpToFiveOrTen(double value) => (value / 5).ceil() * 5.0;
|
525
|
static double _roundUpToFiveOrTen(double value) => (value / 5).ceil() * 5.0;
|