|
...
|
...
|
@@ -376,10 +376,11 @@ class TodayHrvChartCard extends StatelessWidget { |
|
|
|
return l10n.waitingForData;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<TextSpan>? _getTooltip(BarChartRodData rod, int x, int? state) {
|
|
|
|
List<TextSpan>? _getTooltip(
|
|
|
|
BarChartRodData rod, int x, int? state, bool? isWorkout) {
|
|
|
|
return [
|
|
|
|
TextSpan(
|
|
|
|
text: '○ ',
|
|
|
|
text: isWorkout == true ? '◉ ' : '○ ',
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: 14,
|
|
|
|
color: _getColorByState(state),
|
|
...
|
...
|
@@ -390,7 +391,9 @@ class TodayHrvChartCard extends StatelessWidget { |
|
|
|
),
|
|
|
|
),
|
|
|
|
TextSpan(
|
|
|
|
text: '${_getStatusByState(state)} ${_formatNumber(rod.toY)}%',
|
|
|
|
text: isWorkout == true
|
|
|
|
? l10n.frequentMovement
|
|
|
|
: '${_getStatusByState(state)} ${_formatNumber(rod.toY)}%',
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: 18,
|
|
|
|
color: _getColorByState(state),
|
|
...
|
...
|
@@ -531,13 +534,16 @@ class _HrvLineChartState extends State<_HrvLineChart> { |
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
final maxY = widget.spots.isEmpty
|
|
|
|
? 100.0
|
|
|
|
: widget.spots.map((s) => s.y).reduce((a, b) => a > b ? a : b) * 1.1;
|
|
|
|
return LineChart(
|
|
|
|
LineChartData(
|
|
|
|
clipData: const FlClipData.none(),
|
|
|
|
minX: 0,
|
|
|
|
maxX: widget.maxSeconds,
|
|
|
|
minY: 0,
|
|
|
|
maxY: 100,
|
|
|
|
maxY: maxY,
|
|
|
|
gridData: FlGridData(
|
|
|
|
show: true,
|
|
|
|
drawVerticalLine: true,
|
|
...
|
...
|
@@ -626,7 +632,7 @@ class _HrvLineChartState extends State<_HrvLineChart> { |
|
|
|
],
|
|
|
|
lineTouchData: LineTouchData(
|
|
|
|
getTouchLineStart: (barData, spotIndex) => 0,
|
|
|
|
getTouchLineEnd: (barData, spotIndex) => 100,
|
|
|
|
getTouchLineEnd: (barData, spotIndex) => maxY,
|
|
|
|
handleBuiltInTouches: false,
|
|
|
|
touchSpotThreshold: 20.0,
|
|
|
|
touchCallback:
|
|
...
|
...
|
@@ -743,7 +749,7 @@ class _StressBarChart extends StatefulWidget { |
|
|
|
final VoidCallback onTapPurchase;
|
|
|
|
final double maxSeconds;
|
|
|
|
final Color Function(int?) getColorByState;
|
|
|
|
final List<TextSpan>? Function(BarChartRodData, int, int?) getTooltip;
|
|
|
|
final List<TextSpan>? Function(BarChartRodData, int, int?, bool?) getTooltip;
|
|
|
|
final String? Function(double) timeLabel;
|
|
|
|
|
|
|
|
@override
|
|
...
|
...
|
@@ -802,24 +808,26 @@ class _StressBarChartState extends State<_StressBarChart> { |
|
|
|
..sort((a, b) => a.fromTime.compareTo(b.fromTime));
|
|
|
|
|
|
|
|
// 2. 合并相邻且间隔小于 2 小时的睡眠区间,避免因短暂醒来分段导致多个床图标或跨天首尾大面积相连
|
|
|
|
final mergedList = <V2SleepTimeRange>[];
|
|
|
|
for (final range in sortedList) {
|
|
|
|
if (mergedList.isEmpty) {
|
|
|
|
mergedList.add(range);
|
|
|
|
} else {
|
|
|
|
final lastRange = mergedList.last;
|
|
|
|
if (range.fromTime - lastRange.toTime <= 2 * 3600) {
|
|
|
|
mergedList[mergedList.length - 1] = V2SleepTimeRange(
|
|
|
|
fromTime: lastRange.fromTime,
|
|
|
|
toTime: range.toTime > lastRange.toTime
|
|
|
|
? range.toTime
|
|
|
|
: lastRange.toTime,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
mergedList.add(range);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// final mergedList = <V2SleepTimeRange>[];
|
|
|
|
// for (final range in sortedList) {
|
|
|
|
// if (mergedList.isEmpty) {
|
|
|
|
// mergedList.add(range);
|
|
|
|
// } else {
|
|
|
|
// final lastRange = mergedList.last;
|
|
|
|
// if (range.fromTime - lastRange.toTime <= 2 * 3600) {
|
|
|
|
// mergedList[mergedList.length - 1] = V2SleepTimeRange(
|
|
|
|
// fromTime: lastRange.fromTime,
|
|
|
|
// toTime: range.toTime > lastRange.toTime
|
|
|
|
// ? range.toTime
|
|
|
|
// : lastRange.toTime,
|
|
|
|
// );
|
|
|
|
// } else {
|
|
|
|
// mergedList.add(range);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// 2. 不合并睡眠段,直接使用排序后的列表
|
|
|
|
final mergedList = sortedList;
|
|
|
|
|
|
|
|
// 3. 渲染合并后的睡眠区间 Positioned widgets
|
|
|
|
for (final range in mergedList) {
|
|
...
|
...
|
@@ -888,7 +896,8 @@ class _StressBarChartState extends State<_StressBarChart> { |
|
|
|
}
|
|
|
|
|
|
|
|
// 槽时间秒 → state 映射
|
|
|
|
final slotStateMap = <int, ({int value, int? state})>{};
|
|
|
|
final slotStateMap =
|
|
|
|
<int, ({int value, int? state, bool? isWorkout})>{};
|
|
|
|
for (final p in widget.stressPoints) {
|
|
|
|
final sec =
|
|
|
|
TodayHrvChartCard._tsToSecondsFromMidnight(p.time).round();
|
|
...
|
...
|
@@ -896,7 +905,14 @@ class _StressBarChartState extends State<_StressBarChart> { |
|
|
|
final v = p.value ?? 0;
|
|
|
|
final existing = slotStateMap[slot];
|
|
|
|
if (existing == null || v > existing.value) {
|
|
|
|
slotStateMap[slot] = (value: v, state: p.state);
|
|
|
|
final isWorkout = (p.isWorkout ?? 0) == 1;
|
|
|
|
final isSuspectedActivity = (p.isSuspectedActivity ?? 0) == 1;
|
|
|
|
final isMoving = (p.isMoving ?? 0) == 1;
|
|
|
|
slotStateMap[slot] = (
|
|
|
|
value: v,
|
|
|
|
state: p.state,
|
|
|
|
isWorkout: (isWorkout || isSuspectedActivity || isMoving)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
...
|
...
|
@@ -1116,8 +1132,8 @@ class _StressBarChartState extends State<_StressBarChart> { |
|
|
|
color: Colors.white,
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
),
|
|
|
|
children: widget.getTooltip(
|
|
|
|
group.barRods[0], group.x, state),
|
|
|
|
children: widget.getTooltip(group.barRods[0], group.x,
|
|
|
|
state, entry.isWorkout),
|
|
|
|
textAlign: TextAlign.start,
|
|
|
|
);
|
|
|
|
},
|
...
|
...
|
|