|
...
|
...
|
@@ -29,7 +29,8 @@ class SleepWeekReportView extends StatelessWidget { |
|
|
|
|
|
|
|
static TextStyle get titleTextStyle => SleepReportTextStyles.titleTextStyle;
|
|
|
|
|
|
|
|
static StrutStyle get titleStrutStyle => SleepReportTextStyles.titleStrutStyle;
|
|
|
|
static StrutStyle get titleStrutStyle =>
|
|
|
|
SleepReportTextStyles.titleStrutStyle;
|
|
|
|
|
|
|
|
static int minutesFromEvening(DateTime time) {
|
|
|
|
final minutes = time.hour * 60 + time.minute;
|
|
...
|
...
|
@@ -202,7 +203,7 @@ class _SleepPeriodTrendContent extends StatelessWidget { |
|
|
|
List<SleepReport> days, {
|
|
|
|
required bool longest,
|
|
|
|
}) {
|
|
|
|
final values = days.where((day) => day.duration != null);
|
|
|
|
final values = days.where((day) => day.hasSleepData);
|
|
|
|
if (values.isEmpty) return null;
|
|
|
|
return values.reduce((selected, day) {
|
|
|
|
final selectedValue = selected.duration!.minutes;
|
|
...
|
...
|
@@ -216,11 +217,11 @@ class _SleepPeriodTrendContent extends StatelessWidget { |
|
|
|
List<SleepReport> days, {
|
|
|
|
required bool best,
|
|
|
|
}) {
|
|
|
|
final values = days.where((day) => day.qualityScore != null);
|
|
|
|
final values = days.where((day) => day.validQualityScore != null);
|
|
|
|
if (values.isEmpty) return null;
|
|
|
|
return values.reduce((selected, day) {
|
|
|
|
final selectedValue = selected.qualityScore!;
|
|
|
|
final dayValue = day.qualityScore!;
|
|
|
|
final selectedValue = selected.validQualityScore!;
|
|
|
|
final dayValue = day.validQualityScore!;
|
|
|
|
if (best) return dayValue > selectedValue ? day : selected;
|
|
|
|
return dayValue < selectedValue ? day : selected;
|
|
|
|
});
|
|
...
|
...
|
@@ -230,7 +231,9 @@ class _SleepPeriodTrendContent extends StatelessWidget { |
|
|
|
List<SleepReport> days, {
|
|
|
|
required bool earliest,
|
|
|
|
}) {
|
|
|
|
final values = days.where((day) => day.heartRate.sleepStart != null);
|
|
|
|
final values = days.where(
|
|
|
|
(day) => day.hasSleepData && day.heartRate.sleepStart != null,
|
|
|
|
);
|
|
|
|
if (values.isEmpty) return null;
|
|
|
|
return values.reduce((selected, day) {
|
|
|
|
final selectedValue = SleepWeekReportView.minutesFromEvening(
|
|
...
|
...
|
@@ -325,7 +328,7 @@ class _TopSummary extends StatelessWidget { |
|
|
|
|
|
|
|
String _formatComparison(double? value) {
|
|
|
|
if (!hasData || value == null) return '-';
|
|
|
|
return value.abs().round().toString();
|
|
|
|
return value.abs().toInt().toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
...
|
...
|
@@ -466,7 +469,7 @@ class _DurationChartState extends State<_DurationChart> { |
|
|
|
final values = <double>[
|
|
|
|
8,
|
|
|
|
for (final day in widget.days)
|
|
|
|
if (day.duration != null) day.duration!.minutes / 60,
|
|
|
|
if (day.hasSleepData) day.duration!.minutes / 60,
|
|
|
|
if (widget.averageDurationMinutes != null)
|
|
|
|
widget.averageDurationMinutes! / 60,
|
|
|
|
if (_targetDurationHours case final target?) target,
|
|
...
|
...
|
@@ -477,7 +480,7 @@ class _DurationChartState extends State<_DurationChart> { |
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
final hasData = widget.days.any((day) => day.duration != null);
|
|
|
|
final hasData = widget.days.any((day) => day.hasSleepData);
|
|
|
|
return Column(
|
|
|
|
children: [
|
|
|
|
_ChartLegend(
|
|
...
|
...
|
@@ -540,7 +543,7 @@ class _DurationChartState extends State<_DurationChart> { |
|
|
|
barTouchData: _barTouchData(
|
|
|
|
days: widget.days,
|
|
|
|
dataBuilder: _ChartSummaryData.duration,
|
|
|
|
hasData: (report) => report.duration != null,
|
|
|
|
hasData: (report) => report.hasSleepData,
|
|
|
|
onSelected: (_, offset) => setState(() => _selectedOffset = offset),
|
|
|
|
),
|
|
|
|
gridData: const FlGridData(show: false),
|
|
...
|
...
|
@@ -590,7 +593,7 @@ class _QualityChartState extends State<_QualityChart> { |
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
final hasData = widget.days.any((day) => day.qualityScore != null);
|
|
|
|
final hasData = widget.days.any((day) => day.validQualityScore != null);
|
|
|
|
return Column(
|
|
|
|
children: [
|
|
|
|
const _ChartLegend(
|
|
...
|
...
|
@@ -646,7 +649,7 @@ class _QualityChartState extends State<_QualityChart> { |
|
|
|
barTouchData: _barTouchData(
|
|
|
|
days: widget.days,
|
|
|
|
dataBuilder: _ChartSummaryData.quality,
|
|
|
|
hasData: (report) => report.qualityScore != null,
|
|
|
|
hasData: (report) => report.validQualityScore != null,
|
|
|
|
onSelected: (_, offset) => setState(() => _selectedOffset = offset),
|
|
|
|
),
|
|
|
|
gridData: const FlGridData(show: false),
|
|
...
|
...
|
@@ -661,7 +664,7 @@ class _QualityChartState extends State<_QualityChart> { |
|
|
|
x: i,
|
|
|
|
barRods: [
|
|
|
|
BarChartRodData(
|
|
|
|
toY: (widget.days[i].qualityScore ?? 0).toDouble(),
|
|
|
|
toY: (widget.days[i].validQualityScore ?? 0).toDouble(),
|
|
|
|
width: _barWidth(widget.compact),
|
|
|
|
color: SleepWeekReportView.blue,
|
|
|
|
borderRadius: const BorderRadius.vertical(
|
|
...
|
...
|
@@ -686,7 +689,9 @@ class _BedtimeChart extends StatelessWidget { |
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
final hasData = days.any((day) => day.heartRate.sleepStart != null);
|
|
|
|
final hasData = days.any(
|
|
|
|
(day) => day.hasSleepData && day.heartRate.sleepStart != null,
|
|
|
|
);
|
|
|
|
final axis = _BedtimeAxis.fromReports(days);
|
|
|
|
return Stack(
|
|
|
|
alignment: Alignment.center,
|
|
...
|
...
|
@@ -706,7 +711,7 @@ class _BedtimeChart extends StatelessWidget { |
|
|
|
LineChartData _chartData(_BedtimeAxis axis) {
|
|
|
|
final points = <FlSpot>[
|
|
|
|
for (var i = 0; i < days.length; i++)
|
|
|
|
if (days[i].heartRate.sleepStart != null)
|
|
|
|
if (days[i].hasSleepData && days[i].heartRate.sleepStart != null)
|
|
|
|
FlSpot(
|
|
|
|
i.toDouble(),
|
|
|
|
_bedtimeY(days[i].heartRate.sleepStart!),
|
|
...
|
...
|
@@ -758,7 +763,7 @@ class _BedtimeChart extends StatelessWidget { |
|
|
|
}
|
|
|
|
|
|
|
|
String _timeAxisLabel(double value) {
|
|
|
|
final hour = value.round() % 24;
|
|
|
|
final hour = value.toInt() % 24;
|
|
|
|
return '${hour.toString().padLeft(2, '0')}:00';
|
|
|
|
}
|
|
|
|
}
|
|
...
|
...
|
@@ -783,6 +788,7 @@ class _BedtimeAxis { |
|
|
|
|
|
|
|
factory _BedtimeAxis.fromReports(List<SleepReport> days) {
|
|
|
|
final values = days
|
|
|
|
.where((day) => day.hasSleepData)
|
|
|
|
.map((day) => day.heartRate.sleepStart)
|
|
|
|
.whereType<DateTime>()
|
|
|
|
.map(SleepWeekReportView.minutesFromEvening)
|
|
...
|
...
|
@@ -945,10 +951,12 @@ LineTouchTooltipData _lineTooltipData({ |
|
|
|
fitInsideVertically: false,
|
|
|
|
getTooltipItems: (spots) {
|
|
|
|
return spots.map((spot) {
|
|
|
|
final index = spot.x.round();
|
|
|
|
final index = spot.x.toInt();
|
|
|
|
if (index < 0 || index >= days.length) return null;
|
|
|
|
final report = days[index];
|
|
|
|
if (report.heartRate.sleepStart == null) return null;
|
|
|
|
if (!report.hasSleepData || report.heartRate.sleepStart == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return _lineTooltipItem(dataBuilder(report));
|
|
|
|
}).toList();
|
|
|
|
},
|
|
...
|
...
|
@@ -1016,7 +1024,7 @@ class _ChartSummaryData { |
|
|
|
}
|
|
|
|
|
|
|
|
factory _ChartSummaryData.quality(SleepReport report) {
|
|
|
|
final score = report.qualityScore!;
|
|
|
|
final score = report.validQualityScore!;
|
|
|
|
return _ChartSummaryData(
|
|
|
|
title: l10n.sleepQualityScore(
|
|
|
|
report.qualityLevel.shortText,
|
|
...
|
...
|
@@ -1244,7 +1252,7 @@ class _HorizontalDashedGridPainter extends CustomPainter { |
|
|
|
final paint = Paint()
|
|
|
|
..color = SleepWeekReportView.grid
|
|
|
|
..strokeWidth = 1;
|
|
|
|
final steps = ((maxY - minY) / interval).round();
|
|
|
|
final steps = ((maxY - minY) / interval).ceil();
|
|
|
|
for (var index = 0; index <= steps; index++) {
|
|
|
|
final y = size.height * (1 - index / steps);
|
|
|
|
for (var x = 0.0; x < size.width; x += 4) {
|
|
...
|
...
|
@@ -1323,7 +1331,7 @@ class _RightAxisLabels extends StatelessWidget { |
|
|
|
return IgnorePointer(
|
|
|
|
child: LayoutBuilder(
|
|
|
|
builder: (context, constraints) {
|
|
|
|
final steps = ((maxY - minY) / interval).round();
|
|
|
|
final steps = ((maxY - minY) / interval).ceil();
|
|
|
|
return Stack(
|
|
|
|
clipBehavior: Clip.none,
|
|
|
|
children: [
|
|
...
|
...
|
@@ -1454,10 +1462,11 @@ class _ExtremeMetric extends StatelessWidget { |
|
|
|
required Color color,
|
|
|
|
required SleepReport? report,
|
|
|
|
}) {
|
|
|
|
final score = report?.validQualityScore;
|
|
|
|
return _ExtremeMetric(
|
|
|
|
title: title,
|
|
|
|
color: color,
|
|
|
|
value: report?.qualityScore == null ? '-' : '${report!.qualityScore}',
|
|
|
|
value: score == null ? '-' : '$score',
|
|
|
|
unit: l10n.reportUnitScore,
|
|
|
|
date: report?.date,
|
|
|
|
);
|
|
...
|
...
|
@@ -1468,7 +1477,8 @@ class _ExtremeMetric extends StatelessWidget { |
|
|
|
required Color color,
|
|
|
|
required SleepReport? report,
|
|
|
|
}) {
|
|
|
|
final start = report?.heartRate.sleepStart;
|
|
|
|
final start =
|
|
|
|
report?.hasSleepData == true ? report?.heartRate.sleepStart : null;
|
|
|
|
return _ExtremeMetric(
|
|
|
|
title: title,
|
|
|
|
color: color,
|
...
|
...
|
|