更新睡眠报告

This commit is contained in:
wyf
2025-05-27 23:09:31 +08:00
parent e0fef11b33
commit 98cd7f4e6a
54 changed files with 4450 additions and 1160 deletions

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
import 'dart:ui' as ui;
class DotBarChart extends StatefulWidget {
final List<Map<String, dynamic>> showLabel;
@@ -39,7 +40,6 @@ class _DotBarChartState extends State<DotBarChart> {
void _showTooltip(BuildContext context, Map<String, dynamic> data,
Offset position, double dotY) {
_removeOverlay();
final RenderBox renderBox = context.findRenderObject() as RenderBox;
final Offset globalPosition = renderBox.localToGlobal(position);
@@ -55,11 +55,6 @@ class _DotBarChartState extends State<DotBarChart> {
color: themeController.currentColor.sc5,
borderRadius: BorderRadius.circular(8.rpx),
boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.6),
// blurRadius: 10.rpx,
// offset: Offset(0, 4.rpx),
// ),
BoxShadow(
color: Colors.black.withOpacity(0.5),
blurRadius: 12.rpx,
@@ -92,7 +87,6 @@ class _DotBarChartState extends State<DotBarChart> {
),
),
);
Overlay.of(context)?.insert(_overlayEntry!);
}
@@ -103,7 +97,6 @@ class _DotBarChartState extends State<DotBarChart> {
int maxTimes = widget.showLabel
.map((e) => e['times'] ?? 0)
.reduce((a, b) => a > b ? a : b);
int yMax = (maxTimes / 10).ceil() * 10;
if (yMax == 0) yMax = 10;
@@ -113,23 +106,35 @@ class _DotBarChartState extends State<DotBarChart> {
int displayMax = step * maxSteps;
List<int> yLabels = List.generate(maxSteps + 1, (index) => step * index);
DateFormat fullFormat = DateFormat('HH:mm');
DateFormat hourFormat = DateFormat('H');
DateTime startDate = DateTime.fromMillisecondsSinceEpoch(widget.startTime);
DateTime endDate = DateTime.fromMillisecondsSinceEpoch(widget.endTime);
int maxXLabels = 11;
int totalPoints = widget.showLabel.length;
List<int> xLabelIndices = [];
// Generate hourly timestamps
List<int> hourlyTimestamps = [];
DateTime currentHour = DateTime(
startDate.year, startDate.month, startDate.day, startDate.hour);
while (currentHour.isBefore(endDate) ||
currentHour.isAtSameMomentAs(endDate)) {
hourlyTimestamps.add(currentHour.millisecondsSinceEpoch);
currentHour = currentHour.add(const Duration(hours: 1));
}
if (totalPoints <= maxXLabels) {
xLabelIndices = List.generate(totalPoints, (i) => i);
} else {
xLabelIndices.add(0);
int middleCount = maxXLabels - 2;
double stepX = (totalPoints - 1) / (middleCount + 1);
for (int i = 1; i <= middleCount; i++) {
xLabelIndices.add((stepX * i).round());
// Calculate positions for hourly labels
List<Map<String, dynamic>> hourLabels = [];
if (widget.showLabel.isNotEmpty) {
int firstDataTime = widget.showLabel.first['time'];
int lastDataTime = widget.showLabel.last['time'];
double totalDuration = (lastDataTime - firstDataTime).toDouble();
for (int timestamp in hourlyTimestamps) {
if (timestamp >= firstDataTime && timestamp <= lastDataTime) {
double position = (timestamp - firstDataTime) / totalDuration;
hourLabels.add({
'time': timestamp,
'position': position,
});
}
}
xLabelIndices.add(totalPoints - 1);
}
double yAxisWidth = 36.rpx;
@@ -225,19 +230,14 @@ class _DotBarChartState extends State<DotBarChart> {
drawableHeight * (1 - times / displayMax);
return Positioned(
left: x - 20.rpx, // Increase touch area
top: y - 20.rpx, // Increase touch area
left: x - 20.rpx,
top: y - 20.rpx,
child: GestureDetector(
onTap: () {
setState(() {
selectedIndex = index;
});
_showTooltip(
context,
data,
Offset(x, y),
y,
);
_showTooltip(context, data, Offset(x, y), y);
},
child: Container(
width: 40.rpx,
@@ -259,33 +259,15 @@ class _DotBarChartState extends State<DotBarChart> {
padding: EdgeInsets.only(left: yAxisWidth),
child: SizedBox(
height: xAxisHeight,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: List.generate(widget.showLabel.length, (index) {
String label = '';
if (xLabelIndices.contains(index)) {
DateTime dt = DateTime.fromMillisecondsSinceEpoch(
widget.showLabel[index]['time']);
if (index == 0 || index == totalPoints - 1) {
label = fullFormat.format(dt);
} else {
label = dt.hour.toString();
}
}
return Expanded(
child: Padding(
padding: EdgeInsets.only(top: 14.rpx),
child: Text(
label,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18.rpx,
color: themeController.currentColor.sc4,
),
),
),
);
}),
child: CustomPaint(
size: Size(double.infinity, xAxisHeight),
painter: _XAxisPainter(
hourLabels: hourLabels,
textColor: themeController.currentColor.sc4,
fontSize: 18.rpx,
startTime: widget.startTime,
endTime: widget.endTime,
),
),
),
),
@@ -328,7 +310,6 @@ class _DotBarChartPainter extends CustomPainter {
double thresholdY =
yAxisTopPadding + drawableHeight * (1 - threshold / yMax);
drawDashedLine(
canvas,
Offset(0, thresholdY),
@@ -339,7 +320,6 @@ class _DotBarChartPainter extends CustomPainter {
);
final double dotRadius = 13.rpx;
for (int i = 0; i < data.length; i++) {
int times = data[i]['times'] ?? 0;
double x = horizontalPadding + i * xStep;
@@ -357,10 +337,8 @@ class _DotBarChartPainter extends CustomPainter {
..style = PaintingStyle.stroke
..color = Colors.white
..strokeWidth = 3.rpx;
canvas.drawCircle(Offset(x, y), dotRadius + 1.rpx, borderPaint);
}
canvas.drawCircle(Offset(x, y), dotRadius, dotPaint);
}
@@ -376,7 +354,6 @@ class _DotBarChartPainter extends CustomPainter {
for (int i = 0; i < yLabelsCount; i++) {
double y = yAxisTopPadding + i * (drawableHeight / (yLabelsCount - 1));
if (i == yLabelsCount - 1) {
canvas.drawLine(Offset(0, y), Offset(size.width, y), solidLinePaint);
} else {
@@ -392,8 +369,14 @@ class _DotBarChartPainter extends CustomPainter {
}
}
void drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint,
double dashWidth, double gapWidth) {
void drawDashedLine(
Canvas canvas,
Offset start,
Offset end,
Paint paint,
double dashWidth,
double gapWidth,
) {
double dx = start.dx;
final double y = start.dy;
while (dx < end.dx) {
@@ -406,3 +389,65 @@ class _DotBarChartPainter extends CustomPainter {
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
class _XAxisPainter extends CustomPainter {
final List<Map<String, dynamic>> hourLabels;
final Color textColor;
final double fontSize;
final int startTime;
final int endTime;
_XAxisPainter({
required this.hourLabels,
required this.textColor,
required this.fontSize,
required this.startTime,
required this.endTime,
});
@override
void paint(Canvas canvas, Size size) {
final textStyle = TextStyle(
color: textColor,
fontSize: fontSize,
);
final textPainter = TextPainter(
textDirection: ui.TextDirection.ltr,
textAlign: TextAlign.center,
);
// Draw start time (leftmost)
final startText = DateFormat('HH:mm')
.format(DateTime.fromMillisecondsSinceEpoch(startTime));
final startTextSpan = TextSpan(text: startText, style: textStyle);
textPainter.text = startTextSpan;
textPainter.layout();
textPainter.paint(canvas, Offset(0, 14.rpx));
// Draw end time (rightmost)
final endText = DateFormat('HH:mm')
.format(DateTime.fromMillisecondsSinceEpoch(endTime));
final endTextSpan = TextSpan(text: endText, style: textStyle);
textPainter.text = endTextSpan;
textPainter.layout();
textPainter.paint(canvas, Offset(size.width - textPainter.width, 14.rpx));
// Draw hourly labels in between
for (var label in hourLabels) {
final position = label['position'] * size.width;
final time = DateTime.fromMillisecondsSinceEpoch(label['time']);
final hourText = DateFormat('h').format(time);
final textSpan = TextSpan(text: hourText, style: textStyle);
textPainter.text = textSpan;
textPainter.layout();
final offset = Offset(
position - textPainter.width / 2,
14.rpx, // Padding from bottom
);
textPainter.paint(canvas, offset);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}