Files
tuiche/lib/pages/sleep_report/component/BreatheStandardWidget.dart
2026-01-31 14:43:47 +08:00

503 lines
19 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:ef/ef.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:flutterflow_ui/flutterflow_ui.dart';
import 'package:vbvs_app/common/color/appConstants.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
import 'package:vbvs_app/enum/APPPackageType.dart';
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
import 'package:vbvs_app/pages/sleep_report/chart/TimeSeriesChart.dart';
import 'package:EasyDartModule/EasyDartModule.dart' as es;
class BreatheStandardWidget extends StatefulWidget {
var sleepReport;
BreatheStandardWidget({super.key, required this.sleepReport});
@override
State<BreatheStandardWidget> createState() => _BreatheStandardWidgetState();
}
class _BreatheStandardWidgetState extends State<BreatheStandardWidget> {
@override
void setState(VoidCallback callback) {
super.setState(callback);
}
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
// 计算y轴的最大最小值
(double, double) _calculateYMinMax(List<TimeSeriesPoint> dataPoints) {
if (dataPoints.isEmpty) {
return (8.0, 20.0);
}
// 过滤掉无效数据点(值为-1的
final validPoints = dataPoints.where((point) => point.value >= 0).toList();
if (validPoints.isEmpty) {
return (8.0, 20.0);
}
// 找出数据中的实际最小值和最大值
double dataMin =
validPoints.map((point) => point.value).reduce((a, b) => a < b ? a : b);
double dataMax =
validPoints.map((point) => point.value).reduce((a, b) => a > b ? a : b);
// 设置默认范围
double yMin = 8.0;
double yMax = 20.0;
// 如果数据范围超出了默认范围,则调整
if (dataMin < yMin) {
// 最小值为0不能为负数且向下浮动2
yMin = (dataMin - 2).clamp(0.0, double.infinity);
}
if (dataMax > yMax) {
// 向上浮动2
yMax = dataMax + 2;
}
return (yMin, yMax);
}
@override
Widget build(BuildContext context) {
try {
if (widget.sleepReport == null ||
widget.sleepReport is! Map ||
widget.sleepReport.isEmpty) {
return Container();
}
final startTime = widget.sleepReport['startTime'];
final endTime = widget.sleepReport['endTime'];
List<Map<String, dynamic>> data =
(widget.sleepReport['brbc'] as List).cast<Map<String, dynamic>>();
List<TimeSeriesPoint> dataPoints = [];
if (data != null && data.isNotEmpty) {
data.forEach((item) {
final x = item['st'] as int;
if (item['value'] == null || item['value'] == '') {
dataPoints.add(TimeSeriesPoint(x, -1));
return;
}
final y = (item['value'] as num).toDouble();
dataPoints.add(TimeSeriesPoint(x, y));
});
}
// 计算动态的y轴范围
final (yMin, yMax) = _calculateYMinMax(dataPoints);
List<Map<String, dynamic>> brs =
(widget.sleepReport['brs'] as List).cast<Map<String, dynamic>>();
Map<String, dynamic>? avgBreath = brs.firstWhere(
(element) => element['id'] == 307,
orElse: () => {},
);
Map<String, dynamic>? baseBreath = brs.firstWhere(
(element) => element['id'] == 305,
orElse: () => {},
);
Map<String, dynamic>? minBreath = brs.firstWhere(
(element) => element['id'] == 308,
orElse: () => {},
);
Map<String, dynamic>? maxBreath = brs.firstWhere(
(element) => element['id'] == 309,
orElse: () => {},
);
String range = baseBreath['range'] ?? '';
int min = 0;
int max = 0;
if (range.isNotEmpty && range.contains('~')) {
List<String> parts = range.split('~');
if (parts.length == 2) {
min = int.tryParse(parts[0]) ?? 0;
max = int.tryParse(parts[1]) ?? 0;
}
}
return Container(
width: double.infinity,
decoration: BoxDecoration(
color: themeController.currentColor.sc5,
borderRadius:
BorderRadius.circular(AppConstants().normal_container_radius),
),
child: Padding(
padding:
EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"呼吸数据".tr,
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: AppConstants().title_text_fontSize),
),
ClickableContainer(
backgroundColor: Colors.transparent,
highlightColor: Colors.white,
padding: EdgeInsetsDirectional.fromSTEB(
14.rpx, 10.rpx, 14.rpx, 10.rpx),
borderRadius: 0.rpx,
onTap: () {
if (AppConstants().ent_type ==
APPPackageType.MHT.code) {
showTipDialog(
context,
Container(
child: Text(
"呼吸数据是指用户在睡眠过程中呼吸的基本数据,是评估睡眠呼吸质量、筛查睡眠呼吸障碍的核心指标。"
.tr,
style: TextStyle(
fontSize: 26.rpx,
color: Colors.black,
),
),
),
backgroundColor: Color(0xFFFFFFFF),
colors: [
Color(0XFF1592AA),
Color(0xFF0C83A7),
Color(0xFF006FA3)
],
);
} else {
showTipDialog(
context,
Container(
child: Text(
"呼吸数据是指用户在睡眠过程中呼吸的基本数据,是评估睡眠呼吸质量、筛查睡眠呼吸障碍的核心指标。"
.tr,
style: TextStyle(
fontSize: 26.rpx,
color: themeController.currentColor.sc3,
),
),
),
backgroundColor: themeController.currentColor.sc17,
colors: AppConstants().thNormalButton,
);
}
},
child: Container(
padding:
EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0.rpx, 0),
width: 28.rpx,
height: 28.rpx,
child: SvgPicture.asset(
'assets/img/icon/explain.svg',
fit: BoxFit.cover,
color: themeController.currentColor.sc4,
),
),
),
],
),
),
SizedBox(
height: 31.rpx,
),
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 30.rpx, 0.rpx),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
width: 14.rpx,
height: 14.rpx,
decoration: BoxDecoration(
color: themeController.currentColor.sc2,
shape: BoxShape.circle,
),
),
SizedBox(width: 15.rpx),
Text(
'正常范围'.tr + "${range}",
style: TextStyle(
fontSize: AppConstants().smaller_text_fontSize,
color: themeController.currentColor.sc3,
),
),
],
),
Container(
width: double.infinity,
child: TimeSeriesChart(
startTime: startTime,
endTime: endTime,
yMin: yMin,
yMax: yMax,
dataPoints: dataPoints,
),
),
].divide(SizedBox(
height: 18.rpx,
)),
),
),
// Padding(
// padding:
// EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 0.rpx, 0.rpx),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// children: [
// Column(
// children: [
// Text(
// "${avgBreath['name']}",
// style: TextStyle(
// color: themeController.currentColor.sc3,
// fontSize: AppConstants().normal_text_fontSize),
// ),
// Row(
// crossAxisAlignment: CrossAxisAlignment.end,
// children: [
// Text(
// "${avgBreath['value']}",
// style: TextStyle(
// color: themeController.currentColor.sc2,
// fontSize:
// AppConstants().normal_text_fontSize),
// ),
// Text(
// "${avgBreath['unit']}",
// style: TextStyle(
// color: themeController.currentColor.sc3,
// fontSize: AppConstants().small_text_fontSize),
// ),
// ].divide(SizedBox(
// width: 6.rpx,
// )),
// ),
// ],
// ),
// Column(
// children: [
// Text(
// "${baseBreath['name']}",
// style: TextStyle(
// color: themeController.currentColor.sc3,
// fontSize: AppConstants().normal_text_fontSize),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// Row(
// crossAxisAlignment: CrossAxisAlignment.end,
// children: [
// Text(
// "${baseBreath['value']}",
// style: TextStyle(
// color: themeController.currentColor.sc2,
// fontSize:
// AppConstants().normal_text_fontSize),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// Text(
// "${baseBreath['unit']}",
// style: TextStyle(
// color: themeController.currentColor.sc3,
// fontSize: AppConstants().small_text_fontSize),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// ].divide(SizedBox(
// width: 6.rpx,
// )),
// ),
// ],
// ),
// Column(
// children: [
// Text(
// "${minBreath['name']}",
// style: TextStyle(
// color: themeController.currentColor.sc3,
// fontSize: AppConstants().normal_text_fontSize),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// Row(
// crossAxisAlignment: CrossAxisAlignment.end,
// children: [
// Text(
// "${minBreath['value']}",
// style: TextStyle(
// color: themeController.currentColor.sc2,
// fontSize:
// AppConstants().normal_text_fontSize),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// Text(
// "${minBreath['unit']}",
// style: TextStyle(
// color: themeController.currentColor.sc3,
// fontSize: AppConstants().small_text_fontSize),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// ].divide(SizedBox(
// width: 6.rpx,
// )),
// ),
// ],
// ),
// Column(
// children: [
// Text(
// "${maxBreath['name']}",
// style: TextStyle(
// color: themeController.currentColor.sc3,
// fontSize: AppConstants().normal_text_fontSize),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// Row(
// crossAxisAlignment: CrossAxisAlignment.end,
// children: [
// Text(
// "${maxBreath['value']}",
// style: TextStyle(
// color: themeController.currentColor.sc2,
// fontSize:
// AppConstants().normal_text_fontSize),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// Text(
// "${maxBreath['unit']}",
// style: TextStyle(
// color: themeController.currentColor.sc3,
// fontSize: AppConstants().small_text_fontSize),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// ].divide(SizedBox(
// width: 6.rpx,
// )),
// ),
// ],
// ),
// ],
// ),
// ),
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 0.rpx, 0.rpx),
child: Row(
children: [
_buildBreathItem(avgBreath),
_buildBreathItem(baseBreath),
_buildBreathItem(minBreath),
_buildBreathItem(maxBreath),
],
),
),
],
),
),
);
} catch (e) {
es.EasyDartModule.logger.error("呼吸基准监测绘制异常${e}");
return Container();
}
}
Widget _buildBreathItem(Map<String, dynamic> data) {
return Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 4.rpx, vertical: 4.rpx),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 名称 - 最多2行
Flexible(
child: Text(
"${data['name']}",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: AppConstants().normal_text_fontSize,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
SizedBox(height: 4.rpx),
// 值 + 单位 - 最多2行
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
fit: FlexFit.loose,
child: Text(
"${data['value']}",
style: TextStyle(
color: themeController.currentColor.sc2,
fontSize: AppConstants().normal_text_fontSize,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
SizedBox(width: 4.rpx),
Flexible(
fit: FlexFit.loose,
child: Text(
"${data['unit']}",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: AppConstants().small_text_fontSize,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
],
),
),
],
),
),
);
}
}