更新睡眠报告
This commit is contained in:
1
assets/img/icon/triangle.svg
Normal file
1
assets/img/icon/triangle.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1748430682589" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11070" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M977.066667 837.546667L546.56 101.12a32.426667 32.426667 0 0 0-27.733333-15.786667h-12.8a32.426667 32.426667 0 0 0-27.733334 15.786667L46.933333 837.546667a33.706667 33.706667 0 0 0 0 32.426666l5.973334 9.813334a31.146667 31.146667 0 0 0 27.733333 16.213333h862.72a31.146667 31.146667 0 0 0 27.733333-16.213333l5.973334-9.813334a33.706667 33.706667 0 0 0 0-32.426666z" p-id="11071"></path></svg>
|
||||
|
After Width: | Height: | Size: 729 B |
BIN
assets/img/xiaoe.png
Normal file
BIN
assets/img/xiaoe.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
@@ -321,7 +321,7 @@
|
||||
"ios启用网络提示":"",
|
||||
"设备报修":"Device repair",
|
||||
"联系人":"Contact",
|
||||
"手机号":"Phone number",
|
||||
"手机号":"Phone",
|
||||
"名称输入提示":"Enter contact name",
|
||||
"手机号输入提示":"Enter contact phone number",
|
||||
"提交":"Submit",
|
||||
@@ -398,7 +398,7 @@
|
||||
"全部消息":"All messages",
|
||||
"请先在设置里的消息通知打开全部消息配置":"Please enable all message notifications in settings first",
|
||||
"请先打开全部消息配置":"Please enable all message notifications first",
|
||||
"正常值":"Normal range:",
|
||||
"正常值":"range:",
|
||||
"今日":"Today",
|
||||
"深色":"Dark",
|
||||
"皮肤指数":"Skin index",
|
||||
|
||||
@@ -421,7 +421,8 @@
|
||||
"心率散点图介绍":"心电散点图是用非线性的图形方法描记的连续心冲击图的RR间期图,因图形由散点组成,又称散点图。",
|
||||
"今日数据":"今日数据",
|
||||
"昨日数据":"昨日数据",
|
||||
"次":"次",
|
||||
"秒":"秒"
|
||||
"次":"频次",
|
||||
"秒":"秒",
|
||||
"当前暂无数据":"当前暂无数据"
|
||||
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/home_page/SleepDataModuleWidget.dart';
|
||||
import 'package:vbvs_app/component/home_page/SleepDateWidget.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||
|
||||
class DynamicReportDetailWidget extends StatefulWidget {
|
||||
@@ -38,7 +39,7 @@ class _DynamicReportDetailWidgetState extends State<DynamicReportDetailWidget> {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Future.delayed(Duration(milliseconds: 100), () {
|
||||
Future.delayed(Duration(milliseconds: 500), () {
|
||||
if (!_hasScrolled &&
|
||||
_scrollController.hasClients &&
|
||||
_scrollController.position.maxScrollExtent > 0) {
|
||||
@@ -121,6 +122,8 @@ class _DynamicReportDetailWidgetState extends State<DynamicReportDetailWidget> {
|
||||
String sleepReportUrl =
|
||||
"${ServiceConstant.sleep_report_url}?mac=$mac&token=${ServiceConstant.sleep_token}&date=$time";
|
||||
Get.toNamed("/sleepReportPage", arguments: sleepReportUrl);
|
||||
} else {
|
||||
TopSlideNotification.show(context,text: "当前暂无数据".tr,textColor: themeController.currentColor.sc9);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
|
||||
@@ -79,156 +79,181 @@ class _SleepDataModuleWidgetState extends State<SleepDataModuleWidget> {
|
||||
showTipDialog(
|
||||
backgroundColor: stringToColor("#FFFFFF"),
|
||||
context,
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
"${widget.data['name']}",
|
||||
style: TextStyle(
|
||||
color: stringToColor("#333333"),
|
||||
fontSize: 36.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 17.rpx,
|
||||
),
|
||||
Text(
|
||||
(widget.data['tips']?.toString().trim().isNotEmpty ?? false)
|
||||
? widget.data['tips'].toString()
|
||||
: "未知数据".tr,
|
||||
style: TextStyle(
|
||||
color: stringToColor("#C8CBD2"),
|
||||
fontSize: 26.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 37.rpx,
|
||||
),
|
||||
Text(
|
||||
"${widget.data['value']}",
|
||||
style: TextStyle(
|
||||
color: stringToColor("${widget.data['color']}"),
|
||||
fontSize: 60.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 81.rpx,
|
||||
),
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
for (int i = 0; i < levelGroups.length; i++) ...[
|
||||
// 每个 levelGroup 区域
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Level 名称
|
||||
Text(
|
||||
levelGroups[i]['levelName'],
|
||||
style: TextStyle(
|
||||
fontSize: 30.rpx,
|
||||
color: stringToColor("#333333"),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 38.rpx),
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: 700.rpx,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"${widget.data['name']}",
|
||||
style: TextStyle(
|
||||
color: stringToColor("#333333"),
|
||||
fontSize: 36.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 17.rpx,
|
||||
),
|
||||
Text(
|
||||
(widget.data['tips']?.toString().trim().isNotEmpty ??
|
||||
false)
|
||||
? widget.data['tips'].toString()
|
||||
: "未知数据".tr,
|
||||
style: TextStyle(
|
||||
color: stringToColor("#C8CBD2"),
|
||||
fontSize: 26.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 37.rpx,
|
||||
),
|
||||
Text(
|
||||
"${widget.data['value']}" +
|
||||
((widget.data['unit'] == null ||
|
||||
widget.data['unit'].toString().isEmpty)
|
||||
? ''
|
||||
: widget.data['unit']),
|
||||
style: TextStyle(
|
||||
color: stringToColor("${widget.data['color']}"),
|
||||
fontSize: 60.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 81.rpx,
|
||||
),
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
for (int i = 0; i < levelGroups.length; i++) ...[
|
||||
// 每个 levelGroup 区域
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Level 名称
|
||||
Text(
|
||||
levelGroups[i]['levelName'],
|
||||
style: TextStyle(
|
||||
fontSize: 30.rpx,
|
||||
color: stringToColor("#333333"),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 38.rpx),
|
||||
|
||||
// 颜色圆点 + key(包一层,和 svg 分离)
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceAround,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start, // 上对齐,避免撑高分割线
|
||||
children:
|
||||
levelGroups[i]['items'].map<Widget>((item) {
|
||||
final bool isSelected =
|
||||
(item['key'] == itemLevel);
|
||||
return Column(
|
||||
children: [
|
||||
// 颜色圆点 + key(参与分割线高度)
|
||||
Column(
|
||||
// 颜色圆点 + key(包一层,和 svg 分离)
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceAround,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start, // 上对齐,避免撑高分割线
|
||||
children: levelGroups[i]['items']
|
||||
.map<Widget>((item) {
|
||||
final bool isSelected =
|
||||
(item['key'] == itemLevel);
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 20.rpx,
|
||||
height: 20.rpx,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
stringToColor(item['color']),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 30.rpx),
|
||||
Text(
|
||||
item['key'],
|
||||
style: TextStyle(
|
||||
color: stringToColor("#333333"),
|
||||
fontSize: 20.rpx,
|
||||
),
|
||||
// 颜色圆点 + key(参与分割线高度)
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 20.rpx,
|
||||
height: 20.rpx,
|
||||
decoration: BoxDecoration(
|
||||
color: stringToColor(
|
||||
item['color']),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 30.rpx),
|
||||
Text(
|
||||
item['key'],
|
||||
style: TextStyle(
|
||||
color:
|
||||
stringToColor("#333333"),
|
||||
fontSize: 20.rpx,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// svg 箭头(不影响分割线高度)
|
||||
SizedBox(height: 20.rpx),
|
||||
isSelected
|
||||
? SvgPicture.asset(
|
||||
'assets/img/icon/triangle.svg',
|
||||
width: 18.rpx,
|
||||
height: 18.rpx,
|
||||
color: themeController
|
||||
.currentColor.sc9,
|
||||
)
|
||||
: SizedBox(height: 18.rpx),
|
||||
],
|
||||
),
|
||||
|
||||
// svg 箭头(不影响分割线高度)
|
||||
SizedBox(height: 20.rpx),
|
||||
isSelected
|
||||
? SvgPicture.asset(
|
||||
'assets/img/icon/triangle.svg',
|
||||
width: 18.rpx,
|
||||
height: 18.rpx,
|
||||
color: themeController
|
||||
.currentColor.sc9,
|
||||
)
|
||||
: SizedBox(height: 18.rpx),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 分割线(只和主要内容等高)
|
||||
if (i != levelGroups.length - 1)
|
||||
Container(
|
||||
width: 1.rpx,
|
||||
color: stringToColor("${widget.data['color']}"),
|
||||
margin: EdgeInsets.symmetric(horizontal: 10.rpx),
|
||||
// 分割线(只和主要内容等高)
|
||||
if (i != levelGroups.length - 1)
|
||||
Container(
|
||||
width: 1.rpx,
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
margin:
|
||||
EdgeInsets.symmetric(horizontal: 10.rpx),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 71.rpx,
|
||||
),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "当前属于".tr, // 第一部分文本
|
||||
style: TextStyle(
|
||||
color: Colors.black, // 你想要的样式
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 71.rpx,
|
||||
),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "当前属于".tr, // 第一部分文本
|
||||
style: TextStyle(
|
||||
color: Colors.black, // 你想要的样式
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
TextSpan(
|
||||
text: itemLevel, // 第二部分文本
|
||||
style: TextStyle(
|
||||
color: stringToColor("${widget.data['color']}"),
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextSpan(
|
||||
text: itemLevel, // 第二部分文本
|
||||
style: TextStyle(
|
||||
color: stringToColor("${widget.data['color']}"),
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (widget.data['onto'] != null && widget.data['onto'] == true) {
|
||||
//跳转睡眠报告
|
||||
Get.toNamed("/newSleepReportPage", arguments: {
|
||||
'date': widget.data['time'] != null
|
||||
? int.parse(widget.data['time'].toString())
|
||||
: DateTime.now().millisecondsSinceEpoch,
|
||||
"mac": 'aaaaaaeeeeeq',
|
||||
'type': 1,
|
||||
'name': 'sleep', //'sleep', 'heartRate' 或 'breathe'
|
||||
'itemName': widget.data['id'],
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
// width: MediaQuery.sizeOf(context).width * 0.267,
|
||||
width: MediaQuery.sizeOf(context).width * 0.267,
|
||||
constraints: BoxConstraints(
|
||||
minWidth: 200.rpx,
|
||||
@@ -251,40 +276,80 @@ class _SleepDataModuleWidgetState extends State<SleepDataModuleWidget> {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'${widget.data['value']}',
|
||||
style: FlutterFlowTheme.of(context).bodyMedium.override(
|
||||
// Expanded(
|
||||
// child: Row(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// crossAxisAlignment: CrossAxisAlignment.end,
|
||||
// children: [
|
||||
// Text(
|
||||
// '${widget.data['value']}',
|
||||
// style: FlutterFlowTheme.of(context).bodyMedium.override(
|
||||
// fontFamily: 'Inter',
|
||||
// fontSize: 36.rpx,
|
||||
// letterSpacing: 0.0,
|
||||
// color: themeController.currentColor.sc3,
|
||||
// ),
|
||||
// maxLines: 1,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// Padding(
|
||||
// padding:
|
||||
// EdgeInsetsDirectional.fromSTEB(0, 0, 0, 10.rpx),
|
||||
// child: Text(
|
||||
// '${widget.data['unit'] ?? ''}',
|
||||
// style: FlutterFlowTheme.of(context)
|
||||
// .bodyMedium
|
||||
// .override(
|
||||
// fontFamily: 'Inter',
|
||||
// fontSize: AppConstants().small_text_fontSize,
|
||||
// letterSpacing: 0.0,
|
||||
// color: themeController.currentColor.sc3,
|
||||
// ),
|
||||
// maxLines: 1,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${widget.data['value']}',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 36.rpx,
|
||||
letterSpacing: 0.0,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
WidgetSpan(child: SizedBox(width: 2.rpx)), // 可选间距
|
||||
TextSpan(
|
||||
text: widget.data['unit'] != null
|
||||
? '${widget.data['unit']}'
|
||||
: '',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: AppConstants().small_text_fontSize,
|
||||
letterSpacing: 0.0,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 10.rpx),
|
||||
child: Text(
|
||||
'${widget.data['unit'] ?? ''}',
|
||||
style: FlutterFlowTheme.of(context).bodyMedium.override(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: AppConstants().small_text_fontSize,
|
||||
letterSpacing: 0.0,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color:
|
||||
themeController.currentColor.sc3), // 强制 ellipsis 颜色
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
if (widget.data['level'] != null)
|
||||
ClickableContainer(
|
||||
backgroundColor: (widget.data['color'] == null ||
|
||||
|
||||
@@ -71,7 +71,8 @@ class BlueteethBindController extends GetControllerEx<BlueteethBindModel> {
|
||||
|
||||
RxString search = "".obs; //搜索关键字
|
||||
|
||||
RxInt connectStatus = 0.obs;
|
||||
RxInt connectStatus = 0.obs;//当前wifi连接状态 0:未连接 1:已连接
|
||||
RxInt blueConnectFlag = 0.obs;//当前蓝牙连接状态 0.正在连接 1.为连接 2.已连接
|
||||
|
||||
RxMap selectWifi = {}.obs; //正在连接wifi信息
|
||||
|
||||
|
||||
@@ -256,6 +256,9 @@ class WeatherModelController extends GetControllerEx<WeatherModel> {
|
||||
Future<void> _getCurrentLocation() async {
|
||||
try {
|
||||
Position position = await _determinePosition();
|
||||
if (position == null) {
|
||||
throw Exception("获取位置失败");
|
||||
}
|
||||
|
||||
String? language = "zh_CN";
|
||||
if (languageController.selectLanguage != null) {
|
||||
|
||||
@@ -103,13 +103,13 @@ void startMessagePolling() {
|
||||
// 避免重复启动
|
||||
_messageTimer?.cancel();
|
||||
|
||||
_messageTimer = Timer.periodic(Duration(seconds: 5), (timer) async {
|
||||
_messageTimer = Timer.periodic(Duration(seconds: 10), (timer) async {
|
||||
try {
|
||||
MessageController messageController = Get.find();
|
||||
messageController.getMessageStatus();
|
||||
// print("轮询消息状态成功");
|
||||
} catch (e) {
|
||||
print("轮询消息状态失败: $e");
|
||||
print("轮询消息状态失败: $e");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:easydevice/easydevice.dart';
|
||||
import 'package:ef/ef.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:flutterflow_ui/flutterflow_ui.dart';
|
||||
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
||||
@@ -15,18 +15,12 @@ import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/component/tool/CustomCard.dart';
|
||||
import 'package:vbvs_app/component/tool/ToggleColorContainer.dart';
|
||||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||
import 'package:vbvs_app/component/tool/cmd.dart';
|
||||
import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
||||
import 'package:vbvs_app/controller/device/body_device_controller.dart';
|
||||
import 'package:vbvs_app/controller/person/person_controller.dart';
|
||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||
import 'package:vbvs_app/enum/BindType.dart';
|
||||
import 'package:vbvs_app/model/BleDeviceData.dart';
|
||||
import 'package:vbvs_app/model/api_response.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/blueteeth_device_page.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
class DeviceDataComponentWidget extends StatefulWidget {
|
||||
final Map<String, dynamic> device;
|
||||
@@ -1040,7 +1034,7 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
Expanded(
|
||||
child: CustomCard(
|
||||
borderRadius: AppConstants().button_container_radius,
|
||||
onTap: () async {
|
||||
@@ -1052,11 +1046,17 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
||||
personController.gender.value =
|
||||
widget.device['person']['gender'] ?? 1;
|
||||
personController.weight?.value =
|
||||
widget.device['person']['weight'].toString() ??
|
||||
"";
|
||||
widget.device['person']['weight'] == null
|
||||
? ''
|
||||
: widget.device['person']['weight']
|
||||
.toString();
|
||||
|
||||
personController.height.value =
|
||||
widget.device['person']['height'].toString() ??
|
||||
"";
|
||||
widget.device['person']['height'] == null
|
||||
? ''
|
||||
: widget.device['person']['height']
|
||||
.toString();
|
||||
|
||||
personController.selectedDiseaseIds.value =
|
||||
widget.device['person']['disease'] ?? [];
|
||||
personController.birthday.value =
|
||||
@@ -1165,7 +1165,8 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
||||
borderRadius: AppConstants().button_container_radius,
|
||||
onTap: () {
|
||||
// TopSlideNotification.show(context, text: "待开发功能".tr);
|
||||
Get.toNamed("/messageReviewPage",arguments: widget.device);
|
||||
Get.toNamed("/messageReviewPage",
|
||||
arguments: widget.device);
|
||||
},
|
||||
colors: [
|
||||
themeController.currentColor.sc1,
|
||||
@@ -1463,19 +1464,3 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_showBluetoothNotEnabledDialog() async {
|
||||
await showDialog(
|
||||
context: Get.context!,
|
||||
builder: (_) => AlertDialog(
|
||||
title: Text("蓝牙未开启"),
|
||||
content: Text("请先打开蓝牙再进行WIFI配置"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(Get.context!).pop(),
|
||||
child: Text("知道了"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -325,6 +325,7 @@ class _InstantBodyPageState extends State<InstantBodyPage> {
|
||||
),
|
||||
].divide(SizedBox(height: 34.rpx)),
|
||||
),
|
||||
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
@@ -342,6 +343,8 @@ class _InstantBodyPageState extends State<InstantBodyPage> {
|
||||
color: themeController
|
||||
.currentColor.sc3,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
'${device['person']?['weight'] ?? '未知数据'.tr}kg',
|
||||
@@ -358,6 +361,7 @@ class _InstantBodyPageState extends State<InstantBodyPage> {
|
||||
),
|
||||
].divide(SizedBox(height: 34.rpx)),
|
||||
),
|
||||
|
||||
]
|
||||
.divide(SizedBox(width: 33.rpx))
|
||||
.addToStart(SizedBox(width: 37.rpx)),
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:flutterflow_ui/flutterflow_ui.dart';
|
||||
import 'package:permission_handler/permission_handler.dart'; // 引入permission_handler
|
||||
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';
|
||||
@@ -15,6 +16,7 @@ import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||
import 'package:vbvs_app/model/BleDeviceData.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/SingleBlueteethDeviceCompoentWidget.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
|
||||
class BlueteethDevicePage extends StatefulWidget {
|
||||
int tid = -1;
|
||||
@@ -730,19 +732,40 @@ class _BlueteethDevicePageState extends State<BlueteethDevicePage> {
|
||||
}
|
||||
|
||||
_showBluetoothNotEnabledDialog() async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
title: Text("蓝牙未开启"),
|
||||
content: Text("请先打开蓝牙再进行设备扫描"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text("知道了"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
await showTipDialog(
|
||||
context,
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
"蓝牙未开启".tr,
|
||||
style: TextStyle(
|
||||
fontSize: AppConstants().title_text_fontSize,
|
||||
color: themeController.currentColor.sc3),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20.rpx,
|
||||
),
|
||||
Text(
|
||||
"请先打开蓝牙在进行设备扫描".tr,
|
||||
style: TextStyle(
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
color: themeController.currentColor.sc3),
|
||||
),
|
||||
],
|
||||
));
|
||||
// await showDialog(
|
||||
// context: context,
|
||||
// builder: (_) => AlertDialog(
|
||||
// title: Text("蓝牙未开启"),
|
||||
// content: Text("请先打开蓝牙再进行设备扫描"),
|
||||
// actions: [
|
||||
// TextButton(
|
||||
// onPressed: () => Navigator.of(context).pop(),
|
||||
// child: Text("知道了"),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -860,7 +860,7 @@ void showWifiDialog(
|
||||
);
|
||||
}
|
||||
|
||||
void showTipDialog(
|
||||
String showTipDialog(
|
||||
BuildContext context,
|
||||
Widget widget, {
|
||||
Color? backgroundColor, // 新增可选参数
|
||||
@@ -990,4 +990,5 @@ void showTipDialog(
|
||||
);
|
||||
},
|
||||
);
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ class _WifiPageState extends State<WifiPage> {
|
||||
// textColor: themeController.currentColor.sc2,
|
||||
// );
|
||||
// });
|
||||
blueteethBindController.blueConnectFlag.value = 2;
|
||||
blueteethBindController.currentDevice = bledevice;
|
||||
if (lisObj != null) {
|
||||
lisObj!.cancel();
|
||||
@@ -116,6 +117,8 @@ class _WifiPageState extends State<WifiPage> {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
blueteethBindController.blueConnectFlag.value = 0;
|
||||
blueteethBindController.updateAll();
|
||||
dealWifi(widget.type['mac']).then((aa) {
|
||||
print("object");
|
||||
});
|
||||
@@ -175,21 +178,50 @@ class _WifiPageState extends State<WifiPage> {
|
||||
SizedBox(
|
||||
width: 14.rpx,
|
||||
),
|
||||
Obx(() {
|
||||
if (blueteethBindController.connectStatus.value ==
|
||||
0) {
|
||||
return SizedBox(
|
||||
width: 24.rpx,
|
||||
height: 24.rpx,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1,
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
}),
|
||||
if (widget.type == null)
|
||||
Obx(() {
|
||||
if (blueteethBindController.connectStatus.value ==
|
||||
0) {
|
||||
return SizedBox(
|
||||
width: 24.rpx,
|
||||
height: 24.rpx,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
}),
|
||||
if (widget.type != null)
|
||||
Obx(() {
|
||||
if (blueteethBindController.blueConnectFlag.value ==
|
||||
0) {
|
||||
return SizedBox(
|
||||
width: 24.rpx,
|
||||
height: 24.rpx,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (blueteethBindController.connectStatus.value ==
|
||||
0&&blueteethBindController.blueConnectFlag.value ==0) {
|
||||
return SizedBox(
|
||||
width: 24.rpx,
|
||||
height: 24.rpx,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
}),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -717,6 +749,20 @@ class _WifiPageState extends State<WifiPage> {
|
||||
horizontal: 20.rpx, vertical: 10.rpx),
|
||||
borderRadius: 20.rpx,
|
||||
onTap: () async {
|
||||
if ((blueteethBindController
|
||||
.blueConnectFlag ==
|
||||
0 ||
|
||||
blueteethBindController
|
||||
.blueConnectFlag ==
|
||||
1) &&
|
||||
widget.type != null) {
|
||||
blueteethBindController
|
||||
.blueConnectFlag.value = 0;
|
||||
dealWifi(widget.type['mac']).then((aa) {
|
||||
print("object");
|
||||
});
|
||||
return;
|
||||
}
|
||||
blueteethBindController
|
||||
.connectStatus.value = 0;
|
||||
blueteethBindController.updateAll();
|
||||
@@ -957,6 +1003,8 @@ class _WifiPageState extends State<WifiPage> {
|
||||
timeoutTimer = Timer(Duration(seconds: 20), () {
|
||||
try {
|
||||
if (!isConnected) {
|
||||
blueteethBindController.blueConnectFlag.value = 1;
|
||||
blueteethBindController.updateAll();
|
||||
// Navigator.of(context).pop(); // 先关闭 dialog
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
TopSlideNotification.show(
|
||||
@@ -1005,6 +1053,7 @@ class _WifiPageState extends State<WifiPage> {
|
||||
var res2 = bledevice.isConnected;
|
||||
if (res2) {
|
||||
// Navigator.pop(context);
|
||||
blueteethBindController.blueConnectFlag.value = 2;
|
||||
TopSlideNotification.show(
|
||||
context,
|
||||
text: "蓝牙绑定.连接成功".tr,
|
||||
|
||||
@@ -1013,7 +1013,7 @@ class _HomePageState extends State<HomePage> {
|
||||
(device) => device['mac'] == mac,
|
||||
);
|
||||
List stateModule = [];
|
||||
|
||||
String currentTime = "";
|
||||
return DynamicReportDetailWidget(
|
||||
targetDevice: targetDevice!,
|
||||
sleepDateWidgets: List.generate(
|
||||
@@ -1030,6 +1030,7 @@ class _HomePageState extends State<HomePage> {
|
||||
dayData['selected'] == true &&
|
||||
dayData['state'] != null) {
|
||||
stateModule = dayData['state'];
|
||||
currentTime = dayData['time'];
|
||||
}
|
||||
return SleepDateWidget(
|
||||
mac: mac,
|
||||
@@ -1053,9 +1054,13 @@ class _HomePageState extends State<HomePage> {
|
||||
stateModule.isNotEmpty
|
||||
? List.generate(
|
||||
stateModule.length,
|
||||
(j) => SleepDataModuleWidget(
|
||||
data: stateModule[j],
|
||||
),
|
||||
(j) {
|
||||
stateModule[j]['onto'] = true;
|
||||
stateModule[j]['time'] = currentTime;
|
||||
return SleepDataModuleWidget(
|
||||
data: stateModule[j],
|
||||
);
|
||||
},
|
||||
)
|
||||
: [],
|
||||
);
|
||||
|
||||
@@ -693,7 +693,7 @@ class _MinePageState extends State<MinePage> {
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
'V1.0.2505.28',
|
||||
'V1.0.2505.29',
|
||||
style:
|
||||
FlutterFlowTheme.of(context)
|
||||
.bodyMedium
|
||||
|
||||
@@ -50,9 +50,7 @@ class FatigueCircleIndicator extends StatelessWidget {
|
||||
'$percent%',
|
||||
style: TextStyle(
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
color: percent > 60
|
||||
? themeController.currentColor.sc9
|
||||
: themeController.currentColor.sc3,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.rpx),
|
||||
@@ -60,9 +58,7 @@ class FatigueCircleIndicator extends StatelessWidget {
|
||||
explain,
|
||||
style: TextStyle(
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
color: percent > 60
|
||||
? themeController.currentColor.sc9
|
||||
: themeController.currentColor.sc3,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -5,69 +5,517 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:math';
|
||||
|
||||
class LineChartByRange extends StatelessWidget {
|
||||
//根据数据自定义
|
||||
// class LineChartByRange extends StatefulWidget {
|
||||
// final List<Map<String, dynamic>> showLabel;
|
||||
// final int startTime;
|
||||
// final int endTime;
|
||||
// final int? threshold;
|
||||
|
||||
// const LineChartByRange({
|
||||
// Key? key,
|
||||
// required this.showLabel,
|
||||
// required this.startTime,
|
||||
// required this.endTime,
|
||||
// this.threshold, // 新增
|
||||
// }) : super(key: key);
|
||||
|
||||
// @override
|
||||
// State<LineChartByRange> createState() => _LineChartByRangeState();
|
||||
// }
|
||||
|
||||
// class _LineChartByRangeState extends State<LineChartByRange> {
|
||||
// Offset? selectedOffset;
|
||||
// Map<String, dynamic>? selectedData;
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// if (widget.showLabel.isEmpty) return const SizedBox();
|
||||
|
||||
// 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;
|
||||
|
||||
// DateTime minTime = DateTime.fromMillisecondsSinceEpoch(widget.startTime);
|
||||
// DateTime maxTime = DateTime.fromMillisecondsSinceEpoch(widget.endTime);
|
||||
|
||||
// return GestureDetector(
|
||||
// onTapDown: (details) {
|
||||
// RenderBox box = context.findRenderObject() as RenderBox;
|
||||
// final localPosition = box.globalToLocal(details.globalPosition);
|
||||
|
||||
// // 查找是否点击到某个点
|
||||
// for (var item in widget.showLabel) {
|
||||
// int start = item['startTime'];
|
||||
// int end = item['endTime'];
|
||||
// int times = item['times'];
|
||||
|
||||
// double chartWidth = box.size.width - 40.rpx; // 与 painter 内一致处理
|
||||
// double chartHeight = box.size.height - 30.rpx;
|
||||
// double xStart = 20.rpx + 12.rpx;
|
||||
|
||||
// int totalDuration =
|
||||
// maxTime.millisecondsSinceEpoch - minTime.millisecondsSinceEpoch;
|
||||
|
||||
// double startX = xStart +
|
||||
// chartWidth *
|
||||
// (start - minTime.millisecondsSinceEpoch) /
|
||||
// totalDuration;
|
||||
// double y = chartHeight * (1 - times / yMax);
|
||||
|
||||
// // 判断点击范围(圆点半径±6.rpx范围)
|
||||
// if ((localPosition - Offset(startX, y)).distance < 10.rpx) {
|
||||
// setState(() {
|
||||
// selectedOffset = Offset(startX, y);
|
||||
// selectedData = item;
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// double endX = xStart +
|
||||
// chartWidth *
|
||||
// (end - minTime.millisecondsSinceEpoch) /
|
||||
// totalDuration;
|
||||
// if ((localPosition - Offset(endX, y)).distance < 10.rpx) {
|
||||
// setState(() {
|
||||
// selectedOffset = Offset(endX, y);
|
||||
// selectedData = item;
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 没点到,清除选中
|
||||
// setState(() {
|
||||
// selectedOffset = null;
|
||||
// selectedData = null;
|
||||
// });
|
||||
// },
|
||||
// child: Stack(
|
||||
// children: [
|
||||
// SizedBox(
|
||||
// height: 500.rpx,
|
||||
// child: CustomPaint(
|
||||
// size: Size(double.infinity, 500.rpx),
|
||||
// painter: _LineChartByRangePainter(
|
||||
// data: widget.showLabel,
|
||||
// yMax: yMax,
|
||||
// minTime: minTime,
|
||||
// maxTime: maxTime,
|
||||
// threshold: widget.threshold, // 新增
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// if (selectedOffset != null && selectedData != null)
|
||||
// Positioned(
|
||||
// left: selectedOffset!.dx - 60.rpx,
|
||||
// top: selectedOffset!.dy - 50.rpx,
|
||||
// child: Container(
|
||||
// padding:
|
||||
// EdgeInsets.symmetric(horizontal: 12.rpx, vertical: 8.rpx),
|
||||
// decoration: BoxDecoration(
|
||||
// color: Colors.black.withOpacity(0.3),
|
||||
// borderRadius: BorderRadius.circular(10.rpx),
|
||||
// ),
|
||||
// child: Text(
|
||||
// '${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(selectedData!['startTime']))} - '
|
||||
// '${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(selectedData!['endTime']))}\n'
|
||||
// '次数: ${selectedData!['times']}',
|
||||
// style: TextStyle(
|
||||
// fontSize: 18.rpx,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class _LineChartByRangePainter extends CustomPainter {
|
||||
// final List<Map<String, dynamic>> data;
|
||||
// final int yMax;
|
||||
// final DateTime minTime;
|
||||
// final DateTime maxTime;
|
||||
// final int? threshold;
|
||||
|
||||
// _LineChartByRangePainter({
|
||||
// required this.data,
|
||||
// required this.yMax,
|
||||
// required this.minTime,
|
||||
// required this.maxTime,
|
||||
// this.threshold,
|
||||
// });
|
||||
|
||||
// @override
|
||||
// void paint(Canvas canvas, Size size) {
|
||||
// double padding = 20.rpx;
|
||||
// double labelInset = 12.rpx;
|
||||
|
||||
// final double xStart = padding + labelInset;
|
||||
// final double xEnd = size.width - padding - labelInset;
|
||||
// final double chartWidth = xEnd - xStart;
|
||||
|
||||
// double chartHeight = size.height - 30.rpx;
|
||||
|
||||
// int totalDuration =
|
||||
// maxTime.millisecondsSinceEpoch - minTime.millisecondsSinceEpoch;
|
||||
// if (totalDuration <= 0) return;
|
||||
|
||||
// Paint linePaint = Paint()
|
||||
// ..style = PaintingStyle.stroke
|
||||
// ..strokeWidth = 3.rpx
|
||||
// ..color = stringToColor("#00C1AA")
|
||||
// ..strokeCap = StrokeCap.round;
|
||||
|
||||
// Paint axisPaint = Paint()
|
||||
// ..color = Colors.grey.withOpacity(0.4)
|
||||
// ..strokeWidth = 1.rpx;
|
||||
|
||||
// Paint thresholdPaint = Paint()
|
||||
// ..color = themeController.currentColor.sc9
|
||||
// ..strokeWidth = 1.rpx;
|
||||
|
||||
// // 1. 阈值虚线(红色)
|
||||
// if (threshold != null && threshold! >= 0 && threshold! <= yMax) {
|
||||
// double yThreshold = chartHeight * (1 - threshold! / yMax);
|
||||
// drawDashedLine(
|
||||
// canvas,
|
||||
// Offset(xStart, yThreshold),
|
||||
// Offset(xEnd, yThreshold),
|
||||
// thresholdPaint,
|
||||
// dashWidth: 8.rpx,
|
||||
// dashSpace: 6.rpx,
|
||||
// );
|
||||
// }
|
||||
|
||||
// // 2. 绘制数据线段和圆点
|
||||
// for (var item in data) {
|
||||
// int start = item['startTime'];
|
||||
// int end = item['endTime'];
|
||||
// int times = item['times'];
|
||||
|
||||
// double startX = xStart +
|
||||
// chartWidth * (start - minTime.millisecondsSinceEpoch) / totalDuration;
|
||||
// double endX = xStart +
|
||||
// chartWidth * (end - minTime.millisecondsSinceEpoch) / totalDuration;
|
||||
// double y = chartHeight * (1 - times / yMax);
|
||||
|
||||
// // 设置颜色(根据 threshold 判断)
|
||||
// Color pointColor;
|
||||
// if (threshold != null && times >= threshold!) {
|
||||
// pointColor = themeController.currentColor.sc9;
|
||||
// } else {
|
||||
// pointColor = stringToColor("#00C1AA");
|
||||
// }
|
||||
|
||||
// Paint dynamicLinePaint = Paint()
|
||||
// ..style = PaintingStyle.stroke
|
||||
// ..strokeWidth = 3.rpx
|
||||
// ..color = pointColor
|
||||
// ..strokeCap = StrokeCap.round;
|
||||
|
||||
// Paint dynamicCirclePaint = Paint()
|
||||
// ..style = PaintingStyle.fill
|
||||
// ..color = pointColor;
|
||||
|
||||
// // 画线段
|
||||
// canvas.drawLine(Offset(startX, y), Offset(endX, y), dynamicLinePaint);
|
||||
|
||||
// // 画起点和终点圆点
|
||||
// canvas.drawCircle(Offset(startX, y), 6.rpx, dynamicCirclePaint);
|
||||
// canvas.drawCircle(Offset(endX, y), 6.rpx, dynamicCirclePaint);
|
||||
// }
|
||||
|
||||
// // 3. Y轴辅助线和文字
|
||||
// for (int i = 0; i <= 6; i++) {
|
||||
// double y = chartHeight * i / 6;
|
||||
|
||||
// if (i == 6) {
|
||||
// canvas.drawLine(Offset(xStart, y), Offset(xEnd, y), axisPaint);
|
||||
// } else {
|
||||
// drawDashedLine(
|
||||
// canvas,
|
||||
// Offset(xStart, y),
|
||||
// Offset(xEnd, y),
|
||||
// axisPaint,
|
||||
// dashWidth: 8.rpx,
|
||||
// dashSpace: 6.rpx,
|
||||
// );
|
||||
// }
|
||||
|
||||
// TextPainter tp = TextPainter(
|
||||
// text: TextSpan(
|
||||
// text: '${yMax - (yMax * i / 6).round()}',
|
||||
// style: TextStyle(
|
||||
// fontSize: 18.rpx,
|
||||
// color: themeController.currentColor.sc4,
|
||||
// ),
|
||||
// ),
|
||||
// textDirection: ui.TextDirection.ltr,
|
||||
// );
|
||||
// tp.layout();
|
||||
// tp.paint(canvas, Offset(0, y - tp.height / 2));
|
||||
// }
|
||||
|
||||
// // 4. X轴主线
|
||||
// canvas.drawLine(
|
||||
// Offset(xStart, chartHeight),
|
||||
// Offset(xEnd, chartHeight),
|
||||
// axisPaint,
|
||||
// );
|
||||
|
||||
// // 5. X轴时间文字(左右两侧)
|
||||
// String leftLabel = DateFormat('HH:mm').format(minTime);
|
||||
// TextPainter leftTp = TextPainter(
|
||||
// text: TextSpan(
|
||||
// text: leftLabel,
|
||||
// style: TextStyle(
|
||||
// fontSize: 18.rpx,
|
||||
// color: themeController.currentColor.sc4,
|
||||
// ),
|
||||
// ),
|
||||
// textDirection: ui.TextDirection.ltr,
|
||||
// );
|
||||
// leftTp.layout();
|
||||
// leftTp.paint(canvas,
|
||||
// Offset(padding + labelInset - leftTp.width / 2, chartHeight + 8.rpx));
|
||||
|
||||
// String rightLabel = DateFormat('HH:mm').format(maxTime);
|
||||
// TextPainter rightTp = TextPainter(
|
||||
// text: TextSpan(
|
||||
// text: rightLabel,
|
||||
// style: TextStyle(
|
||||
// fontSize: 18.rpx,
|
||||
// color: themeController.currentColor.sc4,
|
||||
// ),
|
||||
// ),
|
||||
// textDirection: ui.TextDirection.ltr,
|
||||
// );
|
||||
// rightTp.layout();
|
||||
// rightTp.paint(
|
||||
// canvas,
|
||||
// Offset(size.width - padding - labelInset - rightTp.width / 2,
|
||||
// chartHeight + 8.rpx));
|
||||
|
||||
// // 6. 中间小时刻度
|
||||
// int totalHours = maxTime.difference(minTime).inHours + 1;
|
||||
// int startHour = minTime.hour;
|
||||
|
||||
// for (int i = 1; i < totalHours; i++) {
|
||||
// double x = xStart + chartWidth * i / totalHours;
|
||||
|
||||
// int hourLabelNum = (startHour + i) % 24;
|
||||
// String hourLabel = '$hourLabelNum';
|
||||
|
||||
// TextPainter tp = TextPainter(
|
||||
// text: TextSpan(
|
||||
// text: hourLabel,
|
||||
// style: TextStyle(
|
||||
// fontSize: 18.rpx,
|
||||
// color: themeController.currentColor.sc4,
|
||||
// ),
|
||||
// ),
|
||||
// textDirection: ui.TextDirection.ltr,
|
||||
// );
|
||||
// tp.layout();
|
||||
// tp.paint(canvas, Offset(x - tp.width / 2, chartHeight + 8.rpx));
|
||||
// }
|
||||
// }
|
||||
|
||||
// @override
|
||||
// bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||
|
||||
// void drawDashedLine(
|
||||
// Canvas canvas,
|
||||
// Offset start,
|
||||
// Offset end,
|
||||
// Paint paint, {
|
||||
// required double dashWidth,
|
||||
// required double dashSpace,
|
||||
// }) {
|
||||
// final dx = end.dx - start.dx;
|
||||
// final dy = end.dy - start.dy;
|
||||
// final distance = sqrt(dx * dx + dy * dy);
|
||||
// final direction = Offset(dx / distance, dy / distance);
|
||||
|
||||
// double drawn = 0;
|
||||
// while (drawn < distance) {
|
||||
// final from = start + direction * drawn;
|
||||
// final to = start + direction * (drawn + dashWidth).clamp(0, distance);
|
||||
// canvas.drawLine(from, to, paint);
|
||||
// drawn += dashWidth + dashSpace;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
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;
|
||||
import 'dart:math';
|
||||
|
||||
class LineChartByRange extends StatefulWidget {
|
||||
final List<Map<String, dynamic>> showLabel;
|
||||
final int startTime;
|
||||
final int endTime;
|
||||
final int? threshold;
|
||||
|
||||
/// 新增外部指定的 Y 轴最大值
|
||||
final int maxY;
|
||||
|
||||
/// Y 轴分段数,默认6段
|
||||
final int ySegments;
|
||||
|
||||
const LineChartByRange({
|
||||
Key? key,
|
||||
required this.showLabel,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.maxY,
|
||||
this.threshold,
|
||||
this.ySegments = 6,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<LineChartByRange> createState() => _LineChartByRangeState();
|
||||
}
|
||||
|
||||
class _LineChartByRangeState extends State<LineChartByRange> {
|
||||
Offset? selectedOffset;
|
||||
Map<String, dynamic>? selectedData;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (showLabel.isEmpty) return const SizedBox();
|
||||
if (widget.showLabel.isEmpty) return const SizedBox();
|
||||
|
||||
int maxTimes =
|
||||
showLabel.map((e) => e['times'] ?? 0).reduce((a, b) => a > b ? a : b);
|
||||
int yMax = (maxTimes / 10).ceil() * 10;
|
||||
if (yMax == 0) yMax = 10;
|
||||
DateTime minTime = DateTime.fromMillisecondsSinceEpoch(widget.startTime);
|
||||
DateTime maxTime = DateTime.fromMillisecondsSinceEpoch(widget.endTime);
|
||||
|
||||
DateTime minTime = DateTime.fromMillisecondsSinceEpoch(startTime);
|
||||
DateTime maxTime = DateTime.fromMillisecondsSinceEpoch(endTime);
|
||||
return GestureDetector(
|
||||
onTapDown: (details) {
|
||||
RenderBox box = context.findRenderObject() as RenderBox;
|
||||
final localPosition = box.globalToLocal(details.globalPosition);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 500.rpx,
|
||||
child: CustomPaint(
|
||||
size: Size(double.infinity, 500.rpx),
|
||||
painter: _LineChartByRangePainter(
|
||||
data: showLabel,
|
||||
yMax: yMax,
|
||||
minTime: minTime,
|
||||
maxTime: maxTime,
|
||||
// 查找是否点击到某个点
|
||||
for (var item in widget.showLabel) {
|
||||
int start = item['startTime'];
|
||||
int end = item['endTime'];
|
||||
int times = item['times'];
|
||||
|
||||
double chartWidth = box.size.width - 40.rpx; // 与 painter 内一致处理
|
||||
double chartHeight = box.size.height - 30.rpx;
|
||||
double xStart = 20.rpx + 12.rpx;
|
||||
|
||||
int totalDuration =
|
||||
maxTime.millisecondsSinceEpoch - minTime.millisecondsSinceEpoch;
|
||||
|
||||
double startX = xStart +
|
||||
chartWidth *
|
||||
(start - minTime.millisecondsSinceEpoch) /
|
||||
totalDuration;
|
||||
double y = chartHeight * (1 - times / widget.maxY);
|
||||
|
||||
// 判断点击范围(圆点半径±6.rpx范围)
|
||||
if ((localPosition - Offset(startX, y)).distance < 10.rpx) {
|
||||
setState(() {
|
||||
selectedOffset = Offset(startX, y);
|
||||
selectedData = item;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
double endX = xStart +
|
||||
chartWidth *
|
||||
(end - minTime.millisecondsSinceEpoch) /
|
||||
totalDuration;
|
||||
if ((localPosition - Offset(endX, y)).distance < 10.rpx) {
|
||||
setState(() {
|
||||
selectedOffset = Offset(endX, y);
|
||||
selectedData = item;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 没点到,清除选中
|
||||
setState(() {
|
||||
selectedOffset = null;
|
||||
selectedData = null;
|
||||
});
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 500.rpx,
|
||||
child: CustomPaint(
|
||||
size: Size(double.infinity, 500.rpx),
|
||||
painter: _LineChartByRangePainter(
|
||||
data: widget.showLabel,
|
||||
maxY: widget.maxY,
|
||||
minTime: minTime,
|
||||
maxTime: maxTime,
|
||||
threshold: widget.threshold,
|
||||
ySegments: widget.ySegments,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (selectedOffset != null && selectedData != null)
|
||||
Positioned(
|
||||
left: selectedOffset!.dx - 60.rpx,
|
||||
top: selectedOffset!.dy - 50.rpx,
|
||||
child: Container(
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 12.rpx, vertical: 8.rpx),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(10.rpx),
|
||||
),
|
||||
child: Text(
|
||||
'${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(selectedData!['startTime']))} - '
|
||||
'${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(selectedData!['endTime']))}\n'
|
||||
'次数: ${selectedData!['times']}',
|
||||
style: TextStyle(
|
||||
fontSize: 18.rpx,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LineChartByRangePainter extends CustomPainter {
|
||||
final List<Map<String, dynamic>> data;
|
||||
final int yMax;
|
||||
final int maxY;
|
||||
final DateTime minTime;
|
||||
final DateTime maxTime;
|
||||
final int? threshold;
|
||||
final int ySegments;
|
||||
|
||||
_LineChartByRangePainter({
|
||||
required this.data,
|
||||
required this.yMax,
|
||||
required this.maxY,
|
||||
required this.minTime,
|
||||
required this.maxTime,
|
||||
this.threshold,
|
||||
this.ySegments = 6,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
double padding = 20.rpx;
|
||||
double labelInset = 12.rpx; // X轴标签缩进距离
|
||||
double labelInset = 12.rpx;
|
||||
|
||||
// 绘图X轴起止点,考虑内缩labelInset
|
||||
final double xStart = padding + labelInset;
|
||||
final double xEnd = size.width - padding - labelInset;
|
||||
final double chartWidth = xEnd - xStart;
|
||||
@@ -78,17 +526,28 @@ class _LineChartByRangePainter extends CustomPainter {
|
||||
maxTime.millisecondsSinceEpoch - minTime.millisecondsSinceEpoch;
|
||||
if (totalDuration <= 0) return;
|
||||
|
||||
Paint linePaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3.rpx
|
||||
..color = stringToColor("#00C1AA")
|
||||
..strokeCap = StrokeCap.round;
|
||||
Paint axisPaint = Paint()
|
||||
..color = Colors.grey.withOpacity(0.4)
|
||||
..strokeWidth = 1.rpx;
|
||||
|
||||
Paint fillCirclePaint = Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = stringToColor("#00C1AA");
|
||||
Paint thresholdPaint = Paint()
|
||||
..color = themeController.currentColor.sc9
|
||||
..strokeWidth = 1.rpx;
|
||||
|
||||
// 1. 先绘制数据线段及起止点圆点
|
||||
// 阈值虚线(红色)
|
||||
if (threshold != null && threshold! >= 0 && threshold! <= maxY) {
|
||||
double yThreshold = chartHeight * (1 - threshold! / maxY);
|
||||
drawDashedLine(
|
||||
canvas,
|
||||
Offset(xStart, yThreshold),
|
||||
Offset(xEnd, yThreshold),
|
||||
thresholdPaint,
|
||||
dashWidth: 8.rpx,
|
||||
dashSpace: 6.rpx,
|
||||
);
|
||||
}
|
||||
|
||||
// 绘制数据线段和圆点
|
||||
for (var item in data) {
|
||||
int start = item['startTime'];
|
||||
int end = item['endTime'];
|
||||
@@ -98,31 +557,41 @@ class _LineChartByRangePainter extends CustomPainter {
|
||||
chartWidth * (start - minTime.millisecondsSinceEpoch) / totalDuration;
|
||||
double endX = xStart +
|
||||
chartWidth * (end - minTime.millisecondsSinceEpoch) / totalDuration;
|
||||
double y = chartHeight * (1 - times / yMax);
|
||||
double y = chartHeight * (1 - times / maxY);
|
||||
|
||||
// 设置颜色(根据 threshold 判断)
|
||||
Color pointColor;
|
||||
if (threshold != null && times >= threshold!) {
|
||||
pointColor = themeController.currentColor.sc9;
|
||||
} else {
|
||||
pointColor = stringToColor("#00C1AA");
|
||||
}
|
||||
|
||||
Paint dynamicLinePaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3.rpx
|
||||
..color = pointColor
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
Paint dynamicCirclePaint = Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = pointColor;
|
||||
|
||||
// 画线段
|
||||
canvas.drawLine(Offset(startX, y), Offset(endX, y), linePaint);
|
||||
canvas.drawLine(Offset(startX, y), Offset(endX, y), dynamicLinePaint);
|
||||
|
||||
// 画起点圆点
|
||||
canvas.drawCircle(Offset(startX, y), 4.rpx, fillCirclePaint);
|
||||
|
||||
// 画终点圆点
|
||||
canvas.drawCircle(Offset(endX, y), 4.rpx, fillCirclePaint);
|
||||
// 画起点和终点圆点
|
||||
canvas.drawCircle(Offset(startX, y), 6.rpx, dynamicCirclePaint);
|
||||
canvas.drawCircle(Offset(endX, y), 6.rpx, dynamicCirclePaint);
|
||||
}
|
||||
|
||||
// 2. Y轴辅助线及文字
|
||||
Paint axisPaint = Paint()
|
||||
..color = Colors.grey.withOpacity(0.4)
|
||||
..strokeWidth = 1.rpx;
|
||||
// Y轴辅助线和文字
|
||||
for (int i = 0; i <= ySegments; i++) {
|
||||
double y = chartHeight * i / ySegments;
|
||||
|
||||
for (int i = 0; i <= 6; i++) {
|
||||
double y = chartHeight * i / 6;
|
||||
|
||||
if (i == 6) {
|
||||
// 实线
|
||||
if (i == ySegments) {
|
||||
canvas.drawLine(Offset(xStart, y), Offset(xEnd, y), axisPaint);
|
||||
} else {
|
||||
// 虚线
|
||||
drawDashedLine(
|
||||
canvas,
|
||||
Offset(xStart, y),
|
||||
@@ -133,12 +602,13 @@ class _LineChartByRangePainter extends CustomPainter {
|
||||
);
|
||||
}
|
||||
|
||||
// Y轴文字
|
||||
TextPainter tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: '${yMax - (yMax * i / 6).round()}',
|
||||
text: '${maxY - (maxY * i / ySegments).round()}',
|
||||
style: TextStyle(
|
||||
fontSize: 18.rpx, color: themeController.currentColor.sc4),
|
||||
fontSize: 18.rpx,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
textDirection: ui.TextDirection.ltr,
|
||||
);
|
||||
@@ -146,29 +616,14 @@ class _LineChartByRangePainter extends CustomPainter {
|
||||
tp.paint(canvas, Offset(0, y - tp.height / 2));
|
||||
}
|
||||
|
||||
// 3. X轴线
|
||||
// X轴主线
|
||||
canvas.drawLine(
|
||||
Offset(xStart, chartHeight), Offset(xEnd, chartHeight), axisPaint);
|
||||
Offset(xStart, chartHeight),
|
||||
Offset(xEnd, chartHeight),
|
||||
axisPaint,
|
||||
);
|
||||
|
||||
// 4. 画X轴时间点对应的垂直虚线辅助线
|
||||
int totalHours = maxTime.difference(minTime).inHours;
|
||||
int startHour = minTime.hour;
|
||||
|
||||
// for (int i = 1; i < totalHours; i++) {
|
||||
// double x = xStart + chartWidth * i / totalHours;
|
||||
|
||||
// // 垂直虚线
|
||||
// drawDashedLine(
|
||||
// canvas,
|
||||
// Offset(x, 0),
|
||||
// Offset(x, chartHeight),
|
||||
// axisPaint,
|
||||
// dashWidth: 4.rpx,
|
||||
// dashSpace: 4.rpx,
|
||||
// );
|
||||
// }
|
||||
|
||||
// 5. 画左侧完整时分 (HH:mm),往内缩 labelInset
|
||||
// X轴时间文字(左右两侧)
|
||||
String leftLabel = DateFormat('HH:mm').format(minTime);
|
||||
TextPainter leftTp = TextPainter(
|
||||
text: TextSpan(
|
||||
@@ -184,7 +639,6 @@ class _LineChartByRangePainter extends CustomPainter {
|
||||
leftTp.paint(canvas,
|
||||
Offset(padding + labelInset - leftTp.width / 2, chartHeight + 8.rpx));
|
||||
|
||||
// 6. 画右侧完整时分 (HH:mm),往内缩 labelInset
|
||||
String rightLabel = DateFormat('HH:mm').format(maxTime);
|
||||
TextPainter rightTp = TextPainter(
|
||||
text: TextSpan(
|
||||
@@ -202,7 +656,10 @@ class _LineChartByRangePainter extends CustomPainter {
|
||||
Offset(size.width - padding - labelInset - rightTp.width / 2,
|
||||
chartHeight + 8.rpx));
|
||||
|
||||
// 7. 中间小时数字(23, 0, 1, 2, ...)
|
||||
// 中间小时刻度
|
||||
int totalHours = maxTime.difference(minTime).inHours + 1;
|
||||
int startHour = minTime.hour;
|
||||
|
||||
for (int i = 1; i < totalHours; i++) {
|
||||
double x = xStart + chartWidth * i / totalHours;
|
||||
|
||||
@@ -220,7 +677,6 @@ class _LineChartByRangePainter extends CustomPainter {
|
||||
textDirection: ui.TextDirection.ltr,
|
||||
);
|
||||
tp.layout();
|
||||
|
||||
tp.paint(canvas, Offset(x - tp.width / 2, chartHeight + 8.rpx));
|
||||
}
|
||||
}
|
||||
|
||||
199
lib/pages/sleep_report/chart/SnoreChart.dart
Normal file
199
lib/pages/sleep_report/chart/SnoreChart.dart
Normal file
@@ -0,0 +1,199 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.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';
|
||||
|
||||
class BarData {
|
||||
final int st; // 起始时间(毫秒)
|
||||
final int et; // 结束时间(毫秒)
|
||||
final double value; // 柱子高度
|
||||
final int id;
|
||||
final String name;
|
||||
final Color color;
|
||||
|
||||
BarData({
|
||||
required this.st,
|
||||
required this.et,
|
||||
required this.value,
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.color,
|
||||
});
|
||||
}
|
||||
|
||||
class BarChartWidget extends StatelessWidget {
|
||||
final List<BarData> data;
|
||||
final int startTime; // 毫秒时间戳
|
||||
final int endTime; // 毫秒时间戳
|
||||
final double maxYValue; // Y轴最大值
|
||||
final int yStepCount; // Y轴分段数
|
||||
|
||||
const BarChartWidget({
|
||||
super.key,
|
||||
required this.data,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.maxYValue,
|
||||
this.yStepCount = 5,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomPaint(
|
||||
size: Size(double.infinity, 500.rpx),
|
||||
painter: BarChartPainter(
|
||||
data,
|
||||
startTime,
|
||||
endTime,
|
||||
maxYValue: maxYValue,
|
||||
yStepCount: yStepCount,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BarChartPainter extends CustomPainter {
|
||||
final List<BarData> data;
|
||||
final int startTime;
|
||||
final int endTime;
|
||||
final double maxYValue;
|
||||
final int yStepCount;
|
||||
|
||||
final double topPadding = 0; // 控制顶部间距
|
||||
final double bottomPadding = 0; // 控制底部间距
|
||||
final double leftPadding = 30.rpx;
|
||||
// final double labelHeight = 50.rpx;
|
||||
|
||||
BarChartPainter(
|
||||
this.data,
|
||||
this.startTime,
|
||||
this.endTime, {
|
||||
required this.maxYValue,
|
||||
this.yStepCount = 5,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final chartWidth = size.width - leftPadding;
|
||||
final chartHeight = size.height - topPadding - bottomPadding;
|
||||
final totalDuration = endTime - startTime;
|
||||
|
||||
final textPainter = TextPainter(textDirection: ui.TextDirection.ltr);
|
||||
final stepValue = maxYValue / yStepCount;
|
||||
|
||||
// 绘制 Y 轴刻度线和文字
|
||||
for (int i = 0; i <= yStepCount; i++) {
|
||||
final value = stepValue * i;
|
||||
final y = topPadding + chartHeight - (value / maxYValue) * chartHeight;
|
||||
|
||||
// 横线
|
||||
// canvas.drawLine(
|
||||
// Offset(leftPadding, y),
|
||||
// Offset(size.width, y),
|
||||
// Paint()
|
||||
// ..color = Colors.grey.withOpacity(0.3)
|
||||
// ..strokeWidth = 0.5,
|
||||
// );
|
||||
final dashPaint = Paint()
|
||||
..color = Colors.grey.withOpacity(0.5)
|
||||
..strokeWidth = 0.5;
|
||||
|
||||
drawDashedLine(
|
||||
canvas, Offset(leftPadding, y), Offset(size.width, y), dashPaint);
|
||||
|
||||
// Y轴刻度文字
|
||||
textPainter.text = TextSpan(
|
||||
text: value.toStringAsFixed(0),
|
||||
style: TextStyle(
|
||||
fontSize: 20.rpx,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
textPainter.layout();
|
||||
textPainter.paint(
|
||||
canvas,
|
||||
Offset(
|
||||
leftPadding - textPainter.width - 4, y - textPainter.height / 2));
|
||||
}
|
||||
|
||||
// X轴时间刻度
|
||||
final startDate = DateTime.fromMillisecondsSinceEpoch(startTime);
|
||||
final endDate = DateTime.fromMillisecondsSinceEpoch(endTime);
|
||||
final hourStep = const Duration(hours: 1);
|
||||
final xPaint = Paint()..color = Colors.grey;
|
||||
|
||||
final xAxisY = topPadding + chartHeight;
|
||||
|
||||
// 绘制整点小时刻度
|
||||
for (DateTime t = startDate; t.isBefore(endDate); t = t.add(hourStep)) {
|
||||
final x = ((t.millisecondsSinceEpoch - startTime) / totalDuration) *
|
||||
chartWidth +
|
||||
leftPadding;
|
||||
|
||||
final timeLabel = (t == startDate || t == endDate)
|
||||
? DateFormat('HH:mm').format(t)
|
||||
: DateFormat('h').format(t);
|
||||
|
||||
textPainter.text = TextSpan(
|
||||
text: timeLabel,
|
||||
style: TextStyle(
|
||||
fontSize: AppConstants().smaller_text_fontSize,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
textPainter.layout();
|
||||
textPainter.paint(canvas, Offset(x - textPainter.width / 2, xAxisY + 4));
|
||||
}
|
||||
|
||||
// ✅ 强制绘制结束时间刻度(确保显示)
|
||||
final endX =
|
||||
((endTime - startTime) / totalDuration) * chartWidth + leftPadding;
|
||||
final endLabel = DateFormat('HH:mm').format(endDate);
|
||||
textPainter.text = TextSpan(
|
||||
text: endLabel,
|
||||
style: TextStyle(
|
||||
fontSize: AppConstants().smaller_text_fontSize,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
textPainter.layout();
|
||||
textPainter.paint(canvas, Offset(endX - textPainter.width / 2, xAxisY + 4));
|
||||
|
||||
// 绘制柱子
|
||||
for (final d in data) {
|
||||
final left =
|
||||
((d.st - startTime) / totalDuration) * chartWidth + leftPadding;
|
||||
final right =
|
||||
((d.et - startTime) / totalDuration) * chartWidth + leftPadding;
|
||||
final barHeight = (d.value / maxYValue) * chartHeight;
|
||||
final top = topPadding + chartHeight - barHeight;
|
||||
|
||||
final barPaint = Paint()..color = d.color;
|
||||
|
||||
canvas.drawRect(
|
||||
Rect.fromLTRB(left, top, right, topPadding + chartHeight), barPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||
|
||||
void drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint,
|
||||
{double dashWidth = 5, double dashSpace = 3}) {
|
||||
double totalLength = (end.dx - start.dx).abs();
|
||||
double dashCount = (totalLength / (dashWidth + dashSpace)).floorToDouble();
|
||||
|
||||
double dx = start.dx;
|
||||
final dy = start.dy;
|
||||
|
||||
for (int i = 0; i < dashCount; i++) {
|
||||
final from = Offset(dx, dy);
|
||||
final to = Offset(dx + dashWidth, dy);
|
||||
canvas.drawLine(from, to, paint);
|
||||
dx += dashWidth + dashSpace;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,6 +164,114 @@ class SnoreWaveform extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// class SnoreWaveformPainter extends CustomPainter {
|
||||
// final List<dynamic> snoreValues;
|
||||
// final int startTime;
|
||||
// final int endTime;
|
||||
|
||||
// SnoreWaveformPainter({
|
||||
// required this.snoreValues,
|
||||
// required this.startTime,
|
||||
// required this.endTime,
|
||||
// });
|
||||
|
||||
// @override
|
||||
// void paint(Canvas canvas, Size size) {
|
||||
// final double width = size.width;
|
||||
// final double height = size.height;
|
||||
// final double centerY = height / 2;
|
||||
// final double totalDuration = (endTime - startTime).toDouble();
|
||||
// final double pixelPerMs = width / totalDuration;
|
||||
|
||||
// final Paint wavePaint = Paint()
|
||||
// ..color = stringToColor("#8E7DEF")
|
||||
// ..strokeWidth = 1.5
|
||||
// ..style = PaintingStyle.stroke;
|
||||
|
||||
// final Path upperPath = Path();
|
||||
// final Path lowerPath = Path();
|
||||
// const double scaleY = 0.5; //波形图比例
|
||||
|
||||
// for (int i = 0; i < snoreValues.length; i++) {
|
||||
// final timestamp = snoreValues[i]["st"];
|
||||
// final value = snoreValues[i]["value"]?.toDouble() ?? 0;
|
||||
|
||||
// final x = (timestamp - startTime) * pixelPerMs;
|
||||
// final y = centerY - value * scaleY;
|
||||
// final yMirror = centerY + value * scaleY;
|
||||
|
||||
// if (i == 0) {
|
||||
// upperPath.moveTo(x, y);
|
||||
// lowerPath.moveTo(x, yMirror);
|
||||
// } else {
|
||||
// upperPath.lineTo(x, y);
|
||||
// lowerPath.lineTo(x, yMirror);
|
||||
// }
|
||||
// }
|
||||
|
||||
// canvas.drawPath(upperPath, wavePaint);
|
||||
// canvas.drawPath(lowerPath, wavePaint);
|
||||
|
||||
// final Paint axisPaint = Paint()
|
||||
// ..color = Colors.grey
|
||||
// ..strokeWidth = 0.5;
|
||||
|
||||
// // 画中心线
|
||||
// canvas.drawLine(Offset(0, centerY), Offset(width, centerY), axisPaint);
|
||||
|
||||
// // 时间刻度绘制
|
||||
// final textPainter = TextPainter(
|
||||
// textAlign: TextAlign.center,
|
||||
// textDirection: ui.TextDirection.ltr,
|
||||
// );
|
||||
|
||||
// final int hourMs = 60 * 60 * 1000;
|
||||
|
||||
// // 循环绘制整点小时标签(不包含终点)
|
||||
// for (int t = startTime; t < endTime; t += hourMs) {
|
||||
// double x = (t - startTime) * pixelPerMs;
|
||||
|
||||
// DateTime dt = DateTime.fromMillisecondsSinceEpoch(t);
|
||||
// String label;
|
||||
// if (t == startTime) {
|
||||
// label = DateFormat('HH:mm').format(dt); // 起点显示 HH:mm
|
||||
// } else {
|
||||
// label = DateFormat('h').format(dt); // 中间显示小时,不带前导0
|
||||
// }
|
||||
|
||||
// textPainter.text = TextSpan(
|
||||
// text: label,
|
||||
// style: TextStyle(fontSize: 10, color: Colors.grey),
|
||||
// );
|
||||
// textPainter.layout();
|
||||
// textPainter.paint(
|
||||
// canvas,
|
||||
// Offset(x - textPainter.width / 2, height + 20.rpx),
|
||||
// );
|
||||
// }
|
||||
|
||||
// // 单独绘制终点时间标签,确保显示具体时分
|
||||
// {
|
||||
// double x = (endTime - startTime) * pixelPerMs;
|
||||
// DateTime dt = DateTime.fromMillisecondsSinceEpoch(endTime);
|
||||
// String label = DateFormat('HH:mm').format(dt);
|
||||
|
||||
// textPainter.text = TextSpan(
|
||||
// text: label,
|
||||
// style: TextStyle(fontSize: 10, color: Colors.grey),
|
||||
// );
|
||||
// textPainter.layout();
|
||||
// textPainter.paint(
|
||||
// canvas,
|
||||
// Offset(x - textPainter.width / 2, height + 20.rpx),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// @override
|
||||
// bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||
// }
|
||||
|
||||
class SnoreWaveformPainter extends CustomPainter {
|
||||
final List<dynamic> snoreValues;
|
||||
final int startTime;
|
||||
@@ -184,13 +292,22 @@ class SnoreWaveformPainter extends CustomPainter {
|
||||
final double pixelPerMs = width / totalDuration;
|
||||
|
||||
final Paint wavePaint = Paint()
|
||||
..color = stringToColor("#8E7DEF")
|
||||
..color = stringToColor("#8E7DEF").withOpacity(0.8)
|
||||
..strokeWidth = 1.5
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
final Path upperPath = Path();
|
||||
final Path lowerPath = Path();
|
||||
const double scaleY = 0.5; //波形图比例
|
||||
|
||||
// ✅ 获取最大值用于自适应比例
|
||||
double maxValue = snoreValues.fold<double>(0, (prev, e) {
|
||||
final value = e["value"]?.toDouble() ?? 0;
|
||||
return value > prev ? value : prev;
|
||||
});
|
||||
|
||||
// ✅ 自适应缩放比例,限制波形最大高度为 height * 0.45
|
||||
final double maxWaveHeight = height * 1;
|
||||
final double scaleY = maxValue > 0 ? (maxWaveHeight / maxValue) : 1;
|
||||
|
||||
for (int i = 0; i < snoreValues.length; i++) {
|
||||
final timestamp = snoreValues[i]["st"];
|
||||
@@ -212,32 +329,26 @@ class SnoreWaveformPainter extends CustomPainter {
|
||||
canvas.drawPath(upperPath, wavePaint);
|
||||
canvas.drawPath(lowerPath, wavePaint);
|
||||
|
||||
// ✅ 最后绘制中心线,防止被覆盖
|
||||
final Paint axisPaint = Paint()
|
||||
..color = Colors.grey
|
||||
..color = Colors.grey.withOpacity(0.6)
|
||||
..strokeWidth = 0.5;
|
||||
|
||||
// 画中心线
|
||||
canvas.drawLine(Offset(0, centerY), Offset(width, centerY), axisPaint);
|
||||
|
||||
// 时间刻度绘制
|
||||
// ✅ 时间刻度绘制
|
||||
final textPainter = TextPainter(
|
||||
textAlign: TextAlign.center,
|
||||
textDirection: ui.TextDirection.ltr,
|
||||
);
|
||||
|
||||
final int hourMs = 60 * 60 * 1000;
|
||||
|
||||
// 循环绘制整点小时标签(不包含终点)
|
||||
for (int t = startTime; t < endTime; t += hourMs) {
|
||||
double x = (t - startTime) * pixelPerMs;
|
||||
|
||||
DateTime dt = DateTime.fromMillisecondsSinceEpoch(t);
|
||||
String label;
|
||||
if (t == startTime) {
|
||||
label = DateFormat('HH:mm').format(dt); // 起点显示 HH:mm
|
||||
} else {
|
||||
label = DateFormat('h').format(dt); // 中间显示小时,不带前导0
|
||||
}
|
||||
String label = t == startTime
|
||||
? DateFormat('HH:mm').format(dt)
|
||||
: DateFormat('h').format(dt); // 12小时制
|
||||
|
||||
textPainter.text = TextSpan(
|
||||
text: label,
|
||||
@@ -246,11 +357,11 @@ class SnoreWaveformPainter extends CustomPainter {
|
||||
textPainter.layout();
|
||||
textPainter.paint(
|
||||
canvas,
|
||||
Offset(x - textPainter.width / 2, height + 20.rpx),
|
||||
Offset(x - textPainter.width / 2, height + 2), // 标签显示在底部
|
||||
);
|
||||
}
|
||||
|
||||
// 单独绘制终点时间标签,确保显示具体时分
|
||||
// ✅ 画终点时间
|
||||
{
|
||||
double x = (endTime - startTime) * pixelPerMs;
|
||||
DateTime dt = DateTime.fromMillisecondsSinceEpoch(endTime);
|
||||
@@ -263,7 +374,7 @@ class SnoreWaveformPainter extends CustomPainter {
|
||||
textPainter.layout();
|
||||
textPainter.paint(
|
||||
canvas,
|
||||
Offset(x - textPainter.width / 2, height + 20.rpx),
|
||||
Offset(x - textPainter.width / 2, height + 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/AdviceComponnetWidget.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class AIAdviceWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -34,88 +35,94 @@ class _AIAdviceWidgetState extends State<AIAdviceWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['sugges'] == null ||
|
||||
widget.sleepReport['sugges'].isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
List advices = widget.sleepReport['sugges'];
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['sugges'] == null ||
|
||||
widget.sleepReport['sugges'].isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
List advices = widget.sleepReport['sugges'];
|
||||
|
||||
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(
|
||||
"AI分析".tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().title_text_fontSize),
|
||||
),
|
||||
ClickableContainer(
|
||||
backgroundColor: Colors.transparent,
|
||||
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"AI分析介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
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(
|
||||
"AI分析".tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().title_text_fontSize),
|
||||
),
|
||||
ClickableContainer(
|
||||
backgroundColor: Colors.transparent,
|
||||
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"AI分析介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
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: advices.map<Widget>((advice) {
|
||||
return AdviceComponnetWidget(
|
||||
title: advice["q"],
|
||||
description: advice["s"],
|
||||
).paddingOnly(bottom: 0.rpx); // 在每个组件下方添加间隔
|
||||
}).toList(),
|
||||
SizedBox(
|
||||
height: 31.rpx,
|
||||
),
|
||||
)
|
||||
],
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: Column(
|
||||
children: advices.map<Widget>((advice) {
|
||||
return AdviceComponnetWidget(
|
||||
title: advice["q"],
|
||||
description: advice["s"],
|
||||
).paddingOnly(bottom: 0.rpx); // 在每个组件下方添加间隔
|
||||
}).toList(),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/DotBarChart.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class BreathPauseWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -34,107 +35,100 @@ class _BreathPauseWidgetState extends State<BreathPauseWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['asp'] == null ||
|
||||
widget.sleepReport['asp'].isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['asp'] == null ||
|
||||
widget.sleepReport['asp'].isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
// List data = widget.sleepReport['asp'];
|
||||
// var showLabel = [
|
||||
// {"time": 1744547251000, "times": 25},
|
||||
// {"time": 1744550851000, "times": 27},
|
||||
// {"time": 1744554451000, "times": 40},
|
||||
// {"time": 1744558051000, "times": 28},
|
||||
// {"time": 1744561651000, "times": 15},
|
||||
// {"time": 1744565251000, "times": 48},
|
||||
// {"time": 1744568851000, "times": 25},
|
||||
// {"time": 1744572451000, "times": 17},
|
||||
// {"time": 1744583251000, "times": 35},
|
||||
// {"time": 1744586851000, "times": 40},
|
||||
// ];
|
||||
List data = widget.sleepReport['asp'];
|
||||
var showLabel = convertAspData(data);
|
||||
var threshold = 30;
|
||||
var startTime = widget.sleepReport['startTime'];
|
||||
var endTime = widget.sleepReport['endTime'];
|
||||
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, 0.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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"呼吸暂停监测介绍。",
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
List data = widget.sleepReport['asp'];
|
||||
var showLabel = convertAspData(data);
|
||||
var threshold = 30;
|
||||
var startTime = widget.sleepReport['startTime'];
|
||||
var endTime = widget.sleepReport['endTime'];
|
||||
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, 0.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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"呼吸暂停监测介绍。",
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 32.rpx,
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 0.rpx, 0.rpx),
|
||||
child: DotBarChart(
|
||||
showLabel: showLabel,
|
||||
threshold: threshold,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
SizedBox(
|
||||
height: 32.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 52.rpx,
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 0.rpx, 0.rpx),
|
||||
child: DotBarChart(
|
||||
showLabel: showLabel,
|
||||
threshold: threshold,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 52.rpx,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> convertAspData(List data) {
|
||||
|
||||
@@ -3,68 +3,137 @@ 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/home_page/SleepDataModuleWidget.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class BreatheCard extends StatefulWidget {
|
||||
var sleepReport;
|
||||
BreatheCard({super.key, required this.sleepReport});
|
||||
final int? highlightItem;
|
||||
BreatheCard({super.key, required this.sleepReport, this.highlightItem});
|
||||
|
||||
@override
|
||||
State<BreatheCard> createState() => _BreatheCardState();
|
||||
}
|
||||
|
||||
class _BreatheCardState extends State<BreatheCard> {
|
||||
@override
|
||||
void setState(VoidCallback callback) {
|
||||
super.setState(callback);
|
||||
}
|
||||
class _BreatheCardState extends State<BreatheCard>
|
||||
with TickerProviderStateMixin {
|
||||
final GlobalKey _highlightKey = GlobalKey();
|
||||
AnimationController? _animationController;
|
||||
bool _shouldAnimate = false;
|
||||
int? _highlightedId;
|
||||
int _flashCount = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.highlightItem != null) {
|
||||
_highlightedId = widget.highlightItem;
|
||||
_shouldAnimate = true;
|
||||
_initAnimation();
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.highlightItem != null &&
|
||||
_highlightKey.currentContext != null) {
|
||||
Scrollable.ensureVisible(
|
||||
_highlightKey.currentContext!,
|
||||
duration: Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOut,
|
||||
alignment: 0.3,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _initAnimation() {
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: Duration(milliseconds: 300),
|
||||
)..addStatusListener((status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
_animationController!.reverse();
|
||||
} else if (status == AnimationStatus.dismissed) {
|
||||
_flashCount++;
|
||||
if (_flashCount >= 3) {
|
||||
_animationController!.dispose();
|
||||
setState(() {
|
||||
_shouldAnimate = false;
|
||||
_highlightedId = null;
|
||||
});
|
||||
} else {
|
||||
_animationController!.forward();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_animationController!.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
List data = widget.sleepReport['brs'] ?? [];
|
||||
|
||||
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: Wrap(
|
||||
spacing: 23.rpx,
|
||||
runSpacing: 25.rpx,
|
||||
children: List.generate(data.length, (index) {
|
||||
final item = data[index];
|
||||
item['showTip'] = true;
|
||||
final bool isHighlighted =
|
||||
_shouldAnimate && item['id'] == _highlightedId;
|
||||
|
||||
return SizedBox(
|
||||
width: (MediaQuery.of(context).size.width - 160.rpx) / 3,
|
||||
child: AnimatedBuilder(
|
||||
animation: _animationController ?? AlwaysStoppedAnimation(0),
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
key: isHighlighted ? _highlightKey : null,
|
||||
decoration: isHighlighted
|
||||
? BoxDecoration(
|
||||
border: Border.all(
|
||||
color: themeController.currentColor.sc2
|
||||
.withOpacity(
|
||||
_animationController?.value ?? 0),
|
||||
width: 1.rpx,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
)
|
||||
: null,
|
||||
child: SleepDataModuleWidget(data: item),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("呼吸监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
|
||||
List data = widget.sleepReport['brs'] ?? [];
|
||||
|
||||
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: Wrap(
|
||||
spacing: 23.rpx, // 横向间距(左右间距如需加可设置)
|
||||
runSpacing: 25.rpx, // 每行之间的垂直间距
|
||||
children: List.generate(data.length, (index) {
|
||||
final item = data[index];
|
||||
item['showTip'] = true;
|
||||
return SizedBox(
|
||||
width: (MediaQuery.of(context).size.width - 160.rpx) / 3,
|
||||
child: SleepDataModuleWidget(data: item),
|
||||
// child: Container(
|
||||
// width: 20,
|
||||
// height: 20,
|
||||
// color: Colors.red,
|
||||
// ),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/LineChartByRange.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class BreathePauseNewWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -34,102 +35,127 @@ class _SnoreViewWidgetWidgetState extends State<BreathePauseNewWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> data =
|
||||
(widget.sleepReport['asp'] as List).cast<Map<String, dynamic>>();
|
||||
List<Map<String, dynamic>> showLabel = convertToShowLabel(data);
|
||||
var startTime = widget.sleepReport['startTime'];
|
||||
var endTime = widget.sleepReport['endTime'];
|
||||
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, 0.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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"呼吸暂停监测介绍。",
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
List standard = widget.sleepReport['brs'] ?? [];
|
||||
final Map? result = standard.cast<Map>().firstWhere(
|
||||
(element) => element['id'] == 302,
|
||||
orElse: () => <dynamic, dynamic>{},
|
||||
);
|
||||
|
||||
int threshold = 0;
|
||||
if (result != null && result.isNotEmpty) {
|
||||
final rangeValue = result['range'];
|
||||
if (rangeValue is int) {
|
||||
threshold = rangeValue;
|
||||
} else if (rangeValue is String) {
|
||||
threshold = int.tryParse(rangeValue) ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> data =
|
||||
(widget.sleepReport['asp'] as List).cast<Map<String, dynamic>>();
|
||||
List<Map<String, dynamic>> showLabel = convertToShowLabel(data);
|
||||
var startTime = widget.sleepReport['startTime'];
|
||||
var endTime = widget.sleepReport['endTime'];
|
||||
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, 0.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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"呼吸暂停监测介绍。",
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 32.rpx,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"秒".tr,
|
||||
style: TextStyle(
|
||||
color: stringToColor("#FFFFFF"), fontSize: 18.rpx),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 32.rpx,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"秒".tr,
|
||||
style: TextStyle(
|
||||
color: stringToColor("#FFFFFF"), fontSize: 18.rpx),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(0.rpx, 40.rpx, 0.rpx, 0.rpx),
|
||||
child: LineChartByRange(
|
||||
showLabel: showLabel,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
threshold: threshold != 0 ? threshold : null,
|
||||
maxY: threshold == 0 ? threshold + 10 : 70,
|
||||
ySegments: 7,
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(0.rpx, 40.rpx, 0.rpx, 0.rpx),
|
||||
child: LineChartByRange(
|
||||
showLabel: showLabel,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 52.rpx,
|
||||
),
|
||||
],
|
||||
SizedBox(
|
||||
height: 52.rpx,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> convertToShowLabel(
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.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;
|
||||
@@ -35,338 +36,336 @@ class _BreatheStandardWidgetState extends State<BreatheStandardWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
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>>();
|
||||
// final dataPoints = [
|
||||
// TimeSeriesPoint(12121, 50),
|
||||
// TimeSeriesPoint(1212, 120),
|
||||
// TimeSeriesPoint(121, 80),
|
||||
// TimeSeriesPoint(1212, 180),
|
||||
// TimeSeriesPoint(1212, 30),
|
||||
// TimeSeriesPoint(1212, 150),
|
||||
// ];
|
||||
final dataPoints = data.map((item) {
|
||||
final x = item['st'] as int;
|
||||
final y = (item['value'] as num).toDouble(); // 安全地转换为 double
|
||||
return TimeSeriesPoint(x, y);
|
||||
}).toList();
|
||||
final startTime = widget.sleepReport['startTime'];
|
||||
final endTime = widget.sleepReport['endTime'];
|
||||
List<Map<String, dynamic>> data =
|
||||
(widget.sleepReport['brbc'] as List).cast<Map<String, dynamic>>();
|
||||
final dataPoints = data.map((item) {
|
||||
final x = item['st'] as int;
|
||||
final y = (item['value'] as num).toDouble(); // 安全地转换为 double
|
||||
return TimeSeriesPoint(x, y);
|
||||
}).toList();
|
||||
|
||||
List<Map<String, dynamic>> brs =
|
||||
(widget.sleepReport['brs'] as List).cast<Map<String, dynamic>>();
|
||||
//307 平均呼吸
|
||||
//305 基准呼吸
|
||||
//308 最低呼吸
|
||||
//309 最高呼吸
|
||||
// 307 平均呼吸
|
||||
Map<String, dynamic>? avgBreath = brs.firstWhere(
|
||||
(element) => element['id'] == 307,
|
||||
orElse: () => {},
|
||||
);
|
||||
List<Map<String, dynamic>> brs =
|
||||
(widget.sleepReport['brs'] as List).cast<Map<String, dynamic>>();
|
||||
//307 平均呼吸
|
||||
//305 基准呼吸
|
||||
//308 最低呼吸
|
||||
//309 最高呼吸
|
||||
// 307 平均呼吸
|
||||
Map<String, dynamic>? avgBreath = brs.firstWhere(
|
||||
(element) => element['id'] == 307,
|
||||
orElse: () => {},
|
||||
);
|
||||
|
||||
// 305 基准呼吸
|
||||
Map<String, dynamic>? baseBreath = brs.firstWhere(
|
||||
(element) => element['id'] == 305,
|
||||
orElse: () => {},
|
||||
);
|
||||
Map<String, dynamic>? baseBreath = brs.firstWhere(
|
||||
(element) => element['id'] == 305,
|
||||
orElse: () => {},
|
||||
);
|
||||
|
||||
// 308 最低呼吸
|
||||
Map<String, dynamic>? minBreath = brs.firstWhere(
|
||||
(element) => element['id'] == 308,
|
||||
orElse: () => {},
|
||||
);
|
||||
Map<String, dynamic>? minBreath = brs.firstWhere(
|
||||
(element) => element['id'] == 308,
|
||||
orElse: () => {},
|
||||
);
|
||||
|
||||
// 309 最高呼吸
|
||||
Map<String, dynamic>? maxBreath = brs.firstWhere(
|
||||
(element) => element['id'] == 309,
|
||||
orElse: () => {},
|
||||
);
|
||||
Map<String, dynamic>? maxBreath = brs.firstWhere(
|
||||
(element) => element['id'] == 309,
|
||||
orElse: () => {},
|
||||
);
|
||||
|
||||
String range = baseBreath['range'] ?? '';
|
||||
int min = 0;
|
||||
int max = 0;
|
||||
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;
|
||||
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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"呼吸数据介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"呼吸数据介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
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(
|
||||
// color: Colors.red,
|
||||
width: double.infinity,
|
||||
// height: 300.rpx,
|
||||
child: TimeSeriesChart(
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
yMin: 50,
|
||||
yMax: 150,
|
||||
dataPoints: dataPoints,
|
||||
actYMax: max.toDouble(),
|
||||
actYMin: min.toDouble(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 0.rpx, 0.rpx),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
SizedBox(
|
||||
height: 31.rpx,
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
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,
|
||||
)),
|
||||
),
|
||||
],
|
||||
// 圆形小球容器
|
||||
Container(
|
||||
width: 14.rpx, // 圆球的直径
|
||||
height: 14.rpx,
|
||||
decoration: BoxDecoration(
|
||||
color: themeController.currentColor.sc2, // 小球的颜色
|
||||
shape: BoxShape.circle, // 设置为圆形
|
||||
),
|
||||
),
|
||||
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,
|
||||
)),
|
||||
),
|
||||
],
|
||||
SizedBox(width: 15.rpx), // 圆球和文字之间的间隔
|
||||
// 文字
|
||||
Text(
|
||||
'正常范围'.tr + "${range}",
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
AppConstants().smaller_text_fontSize, // 文字的大小
|
||||
color: themeController.currentColor.sc3, // 文字颜色
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
].divide(SizedBox(
|
||||
height: 18.rpx,
|
||||
)),
|
||||
Container(
|
||||
// color: Colors.red,
|
||||
width: double.infinity,
|
||||
// height: 300.rpx,
|
||||
child: TimeSeriesChart(
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
yMin: 50,
|
||||
yMax: 150,
|
||||
dataPoints: dataPoints,
|
||||
actYMax: max.toDouble(),
|
||||
actYMin: min.toDouble(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.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,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
].divide(SizedBox(
|
||||
height: 18.rpx,
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/SleepRadarChart.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class CompareSleepWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -35,29 +36,15 @@ class _CompareSleepWidgetState extends State<CompareSleepWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['yc'] == null ||
|
||||
widget.sleepReport['yc'].isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
List<Map<String, dynamic>> data = (widget.sleepReport['yc'] as List)
|
||||
.map((e) => e as Map<String, dynamic>)
|
||||
.toList();
|
||||
|
||||
// var today = {
|
||||
// "type1": 40.0,
|
||||
// "type2": 80.0,
|
||||
// "type3": 60.0,
|
||||
// "type4": 70.0,
|
||||
// "type5": 100.0
|
||||
// };
|
||||
// var yesterday = {
|
||||
// "type1": 40.0,
|
||||
// "type2": 90.0,
|
||||
// "type3": 50.0,
|
||||
// "type4": 70.0,
|
||||
// "type5": 30.0
|
||||
// };
|
||||
.toList();
|
||||
Map<String, double> today = {};
|
||||
Map<String, double> yesterday = {};
|
||||
|
||||
@@ -210,5 +197,10 @@ class _CompareSleepWidgetState extends State<CompareSleepWidget> {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/HorizontalBarChart.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class DiseasePercentsWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -17,51 +18,7 @@ class DiseasePercentsWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DiseasePercentsWidgetState extends State<DiseasePercentsWidget> {
|
||||
// var showLabel = [
|
||||
// {
|
||||
// "key": 1,
|
||||
// "name": "心脏病",
|
||||
// "color": stringToColor("#00C1AA"),
|
||||
// "percent": 45,
|
||||
// "explain": "心脏病是指心脏的结构或功能异常,可能导致心脏无法有效地泵血。"
|
||||
// },
|
||||
// {
|
||||
// "key": 2,
|
||||
// "name": "高血压",
|
||||
// "color": stringToColor("#00C1AA"),
|
||||
// "percent": 32,
|
||||
// "explain": "高血压是指血液在动脉中流动时对血管壁施加的压力过高。"
|
||||
// },
|
||||
// {
|
||||
// "key": 3,
|
||||
// "name": "糖尿病",
|
||||
// "color": stringToColor("#00C1AA"),
|
||||
// "percent": 50,
|
||||
// "explain": "糖尿病是一种代谢性疾病,导致血糖水平异常升高。"
|
||||
// },
|
||||
// {
|
||||
// "key": 4,
|
||||
// "name": "甲亢",
|
||||
// "color": stringToColor("#FF7159"),
|
||||
// "percent": 80,
|
||||
// "explain": "甲亢是指甲状腺分泌过多的甲状腺激素,导致新陈代谢加速。"
|
||||
// },
|
||||
// {
|
||||
// "key": 5,
|
||||
// "name": "消化系统",
|
||||
// "color": stringToColor("#00C1AA"),
|
||||
// "percent": 12,
|
||||
// "explain": "消化系统是身体中处理食物的机构,是造成疾病和疾病症状的来源。",
|
||||
// },
|
||||
// {
|
||||
// "key": 6,
|
||||
// "name": "呼吸系统",
|
||||
// "color": stringToColor("#00C1AA"),
|
||||
// "percent": 62,
|
||||
// "explain": "呼吸系统是负责气体交换的器官系统,包括鼻、喉、气管和肺等。",
|
||||
// },
|
||||
// ];
|
||||
|
||||
|
||||
@override
|
||||
void setState(VoidCallback callback) {
|
||||
super.setState(callback);
|
||||
@@ -79,7 +36,8 @@ class _DiseasePercentsWidgetState extends State<DiseasePercentsWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['cdri'] == null ||
|
||||
widget.sleepReport['cdri'].isEmpty) {
|
||||
return Container();
|
||||
@@ -157,6 +115,11 @@ class _DiseasePercentsWidgetState extends State<DiseasePercentsWidget> {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> convertDiseaseData(List data) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/DataShowWidget.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class HeartChangeWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -34,293 +35,235 @@ class _HeartChangeWidgetState extends State<HeartChangeWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['hrvs'] == null ||
|
||||
widget.sleepReport['hrvs'].isEmpty) {
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['hrvs'] == null ||
|
||||
widget.sleepReport['hrvs'].isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
List dataList = widget.sleepReport['hrvs'];
|
||||
|
||||
List<Map<String, dynamic>> data = transformHrvData(dataList);
|
||||
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(
|
||||
"心率变异性(HRV)".tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().title_text_fontSize),
|
||||
),
|
||||
ClickableContainer(
|
||||
backgroundColor: Colors.transparent,
|
||||
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"心率变异性(HRV)介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
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, 0.rpx, 0.rpx),
|
||||
child: Column(
|
||||
children: [
|
||||
DataShowWidget(
|
||||
alignment: MainAxisAlignment.center,
|
||||
widget1: Text(
|
||||
"名称",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
widget2: Text(
|
||||
"测量值",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
widget3: Text(
|
||||
"参考范围",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
widget4: Text(
|
||||
"趋势",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: data.map<Widget>((data) {
|
||||
return DataShowWidget(
|
||||
alignment: MainAxisAlignment.center,
|
||||
widget1: Row(
|
||||
children: [
|
||||
Text(
|
||||
'${data['name']}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
ClickableContainer(
|
||||
backgroundColor: Colors.transparent,
|
||||
highlightColor: Colors.white,
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
14.rpx, 14.rpx, 14.rpx, 14.rpx),
|
||||
borderRadius: 0.rpx,
|
||||
onTap: () {
|
||||
// Get.toNamed("/deviceShareListPage", arguments: explain);
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
'${data['desc']}',
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color:
|
||||
themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 17.rpx,
|
||||
height: 17.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
widget2: Text(
|
||||
'${data['value']}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
widget3: Text(
|
||||
'${data['range']}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
widget4: data['change'] == 0
|
||||
? Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0, 0),
|
||||
child: Container(
|
||||
width: 22.rpx,
|
||||
height: 22.rpx,
|
||||
decoration: BoxDecoration(),
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/score_up.svg',
|
||||
// fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)
|
||||
: data['change'] == 1
|
||||
? Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0, 0),
|
||||
child: Container(
|
||||
width: 22.rpx,
|
||||
height: 22.rpx,
|
||||
decoration: BoxDecoration(),
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/score_down.svg',
|
||||
// fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0, 0),
|
||||
child: Container(
|
||||
width: 22.rpx,
|
||||
height: 22.rpx,
|
||||
decoration: BoxDecoration(),
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/score_equal.svg',
|
||||
// fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
).paddingOnly(bottom: 0.rpx); // 在每个组件下方添加间隔
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
|
||||
List dataList = widget.sleepReport['hrvs'];
|
||||
//0上升 1下降 2持平
|
||||
// List data = [
|
||||
// {
|
||||
// "name": "心脏总能量",
|
||||
// "value": 5262,
|
||||
// "range": "2055-6000",
|
||||
// "change": 0,
|
||||
// "desc": "心脏总能量介绍"
|
||||
// },
|
||||
// {
|
||||
// "name": "心率减速力",
|
||||
// "value": 5262,
|
||||
// "range": "2055-6000",
|
||||
// "change": 1,
|
||||
// "desc": "心率减速力介绍"
|
||||
// },
|
||||
// {
|
||||
// "name": "迷走神经张力指数",
|
||||
// "value": 5262,
|
||||
// "range": "2055-6000",
|
||||
// "change": 2,
|
||||
// "desc": "迷走神经张力指数介绍"
|
||||
// },
|
||||
// {
|
||||
// "name": "交感神经张力指数",
|
||||
// "value": 5262,
|
||||
// "range": "2055-6000",
|
||||
// "change": 0,
|
||||
// "desc": "交感神经张力指数介绍"
|
||||
// },
|
||||
// {
|
||||
// "name": "自主神经张力指数",
|
||||
// "value": 5262,
|
||||
// "range": "2055-6000",
|
||||
// "change": 2,
|
||||
// "desc": "自主神经张力指数介绍"
|
||||
// },
|
||||
// {
|
||||
// "name": "血管舒张指数",
|
||||
// "value": 5262,
|
||||
// "range": "2055-6000",
|
||||
// "change": 1,
|
||||
// "desc": "血管舒张指数介绍"
|
||||
// },
|
||||
// {
|
||||
// "name": "SDNN",
|
||||
// "value": 5262,
|
||||
// "range": "2055-6000",
|
||||
// "change": 0,
|
||||
// "desc": "SDNN介绍"
|
||||
// },
|
||||
// {
|
||||
// "name": "PNN50",
|
||||
// "value": 5262,
|
||||
// "range": "2055-6000",
|
||||
// "change": 1,
|
||||
// "desc": "PNN50介绍"
|
||||
// },
|
||||
// {
|
||||
// "name": "RMSSD",
|
||||
// "value": 5262,
|
||||
// "range": "2055-6000",
|
||||
// "change": 2,
|
||||
// "desc": "RMSSD介绍"
|
||||
// },
|
||||
// ];
|
||||
List<Map<String, dynamic>> data = transformHrvData(dataList);
|
||||
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(
|
||||
"心率变异性(HRV)".tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().title_text_fontSize),
|
||||
),
|
||||
ClickableContainer(
|
||||
backgroundColor: Colors.transparent,
|
||||
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"心率变异性(HRV)介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
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, 0.rpx, 0.rpx),
|
||||
child: Column(
|
||||
children: [
|
||||
DataShowWidget(
|
||||
alignment: MainAxisAlignment.center,
|
||||
widget1: Text(
|
||||
"名称",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
widget2: Text(
|
||||
"测量值",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
widget3: Text(
|
||||
"参考范围",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
widget4: Text(
|
||||
"趋势",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: data.map<Widget>((data) {
|
||||
return DataShowWidget(
|
||||
alignment: MainAxisAlignment.center,
|
||||
widget1: Row(
|
||||
children: [
|
||||
Text(
|
||||
'${data['name']}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
ClickableContainer(
|
||||
backgroundColor: Colors.transparent,
|
||||
highlightColor: Colors.white,
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
14.rpx, 14.rpx, 14.rpx, 14.rpx),
|
||||
borderRadius: 0.rpx,
|
||||
onTap: () {
|
||||
// Get.toNamed("/deviceShareListPage", arguments: explain);
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
'${data['desc']}',
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 17.rpx,
|
||||
height: 17.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
widget2: Text(
|
||||
'${data['value']}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
widget3: Text(
|
||||
'${data['range']}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
widget4: data['change'] == 0
|
||||
? Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0, 0),
|
||||
child: Container(
|
||||
width: 22.rpx,
|
||||
height: 22.rpx,
|
||||
decoration: BoxDecoration(),
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/score_up.svg',
|
||||
// fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)
|
||||
: data['change'] == 1
|
||||
? Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0, 0),
|
||||
child: Container(
|
||||
width: 22.rpx,
|
||||
height: 22.rpx,
|
||||
decoration: BoxDecoration(),
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/score_down.svg',
|
||||
// fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0, 0),
|
||||
child: Container(
|
||||
width: 22.rpx,
|
||||
height: 22.rpx,
|
||||
decoration: BoxDecoration(),
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/score_equal.svg',
|
||||
// fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
).paddingOnly(bottom: 0.rpx); // 在每个组件下方添加间隔
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> transformHrvData(List<dynamic> originalData) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/FatigueCircleIndicator.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class HeartHealthWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -35,98 +36,104 @@ class _HeartHealthWidgetState extends State<HeartHealthWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['mha'] == null ||
|
||||
widget.sleepReport['mha'].isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['mha'] == null ||
|
||||
widget.sleepReport['mha'].isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
List data = widget.sleepReport['mha'];
|
||||
var showLabel = convertMentalHealthData(data);
|
||||
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, 0.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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"心理健康评估介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
List data = widget.sleepReport['mha'];
|
||||
var showLabel = convertMentalHealthData(data);
|
||||
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, 0.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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"心理健康评估介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 104.rpx,
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FatigueCircleIndicator(
|
||||
data: showLabel[0],
|
||||
),
|
||||
FatigueCircleIndicator(
|
||||
data: showLabel[1],
|
||||
),
|
||||
].divide(SizedBox(
|
||||
width: 110.rpx,
|
||||
)),
|
||||
SizedBox(
|
||||
height: 104.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 72.rpx,
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FatigueCircleIndicator(
|
||||
data: showLabel[0],
|
||||
),
|
||||
FatigueCircleIndicator(
|
||||
data: showLabel[1],
|
||||
),
|
||||
].divide(SizedBox(
|
||||
width: 110.rpx,
|
||||
)),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 72.rpx,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> convertMentalHealthData(List data) {
|
||||
@@ -136,7 +143,7 @@ class _HeartHealthWidgetState extends State<HeartHealthWidget> {
|
||||
final String explain =
|
||||
(item['tips'] != null && (item['tips'] as String).trim().isNotEmpty)
|
||||
? item['tips']
|
||||
: '高风险';
|
||||
: '未知数据'.tr;
|
||||
|
||||
return {
|
||||
'name': item['name'],
|
||||
@@ -144,7 +151,7 @@ class _HeartHealthWidgetState extends State<HeartHealthWidget> {
|
||||
? stringToColor(colorStr)
|
||||
: stringToColor("#00C1AA"), // 默认红色
|
||||
'percent': value,
|
||||
'explain': explain,
|
||||
'explain': item['level'],
|
||||
"bottomColor": Colors.grey,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/ScatterPlotChart.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class HeartPointWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -37,125 +38,120 @@ class _HeartPointWidgetState extends State<HeartPointWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['hrsp'] == null ||
|
||||
widget.sleepReport['hrsp'].isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
// List rawData = widget.sleepReport['hrsp'];
|
||||
// List<ScatterSpot> data = List.generate(200, (index) {
|
||||
// // 随机生成 x 和 y 值,范围都在 0-1400 之间
|
||||
// double x = Random().nextDouble() * 1400; // x 值在 0-1400 范围
|
||||
// double y = Random().nextDouble() * 1400; // y 值也在 0-1400 范围
|
||||
|
||||
// // 返回 ScatterSpot,使用圆点绘制器自定义大小和颜色
|
||||
// return ScatterSpot(
|
||||
// x,
|
||||
// y,
|
||||
// );
|
||||
// });
|
||||
double maxX = 0;
|
||||
double maxY = 0;
|
||||
|
||||
List<ScatterSpot> data = [];
|
||||
List rawData = widget.sleepReport['hrsp'];
|
||||
try {
|
||||
data = rawData.map<ScatterSpot>((item) {
|
||||
double x = (item['st'] ?? 0).toDouble();
|
||||
double y = (item['value'] ?? 0).toDouble();
|
||||
if (x > maxX) maxX = x;
|
||||
if (y > maxY) maxY = y;
|
||||
return ScatterSpot(x, y);
|
||||
}).toList();
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['hrsp'] == null ||
|
||||
widget.sleepReport['hrsp'].isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"心率散点图介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
double maxX = 0;
|
||||
double maxY = 0;
|
||||
|
||||
List<ScatterSpot> data = [];
|
||||
List rawData = widget.sleepReport['hrsp'];
|
||||
try {
|
||||
data = rawData.map<ScatterSpot>((item) {
|
||||
double x = (item['st'] ?? 0).toDouble();
|
||||
double y = (item['value'] ?? 0).toDouble();
|
||||
if (x > maxX) maxX = x;
|
||||
if (y > maxY) maxY = y;
|
||||
return ScatterSpot(x, y);
|
||||
}).toList();
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"心率散点图介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
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(
|
||||
30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.7,
|
||||
height: MediaQuery.of(context).size.width * 0.7,
|
||||
constraints: BoxConstraints(
|
||||
minWidth: 430.rpx,
|
||||
minHeight: 430.rpx,
|
||||
),
|
||||
child: ScatterPlotChart(
|
||||
points: data,
|
||||
xMax: maxX.toInt(), // x轴最大值
|
||||
yMax: maxY.toInt(), // y轴最大值
|
||||
pointColor: stringToColor("#00C1AA"), // 点的颜色
|
||||
divisions: 7, // 刻度分割数量
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 31.rpx,
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.7,
|
||||
height: MediaQuery.of(context).size.width * 0.7,
|
||||
constraints: BoxConstraints(
|
||||
minWidth: 430.rpx,
|
||||
minHeight: 430.rpx,
|
||||
),
|
||||
child: ScatterPlotChart(
|
||||
points: data,
|
||||
xMax: maxX.toInt(), // x轴最大值
|
||||
yMax: maxY.toInt(), // y轴最大值
|
||||
pointColor: stringToColor("#00C1AA"), // 点的颜色
|
||||
divisions: 7, // 刻度分割数量
|
||||
),
|
||||
),
|
||||
),
|
||||
// SizedBox(
|
||||
// height: 31.rpx,
|
||||
// ),
|
||||
],
|
||||
// SizedBox(
|
||||
// height: 31.rpx,
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,68 +3,138 @@ 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/home_page/SleepDataModuleWidget.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class HeartRateCard extends StatefulWidget {
|
||||
var sleepReport;
|
||||
HeartRateCard({super.key, required this.sleepReport});
|
||||
final int? highlightItem;
|
||||
HeartRateCard({super.key, required this.sleepReport, this.highlightItem});
|
||||
|
||||
@override
|
||||
State<HeartRateCard> createState() => _HeartRateCardState();
|
||||
}
|
||||
|
||||
class _HeartRateCardState extends State<HeartRateCard> {
|
||||
@override
|
||||
void setState(VoidCallback callback) {
|
||||
super.setState(callback);
|
||||
}
|
||||
class _HeartRateCardState extends State<HeartRateCard> with TickerProviderStateMixin {
|
||||
final GlobalKey _highlightKey = GlobalKey();
|
||||
AnimationController? _animationController;
|
||||
bool _shouldAnimate = false;
|
||||
int? _highlightedId;
|
||||
int _flashCount = 0; // 用于跟踪闪烁次数
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.highlightItem != null) {
|
||||
_highlightedId = widget.highlightItem;
|
||||
_shouldAnimate = true;
|
||||
_initAnimation();
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.highlightItem != null && _highlightKey.currentContext != null) {
|
||||
Scrollable.ensureVisible(
|
||||
_highlightKey.currentContext!,
|
||||
duration: Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOut,
|
||||
alignment: 0.3,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _initAnimation() {
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: Duration(milliseconds: 300),
|
||||
)..addStatusListener((status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
// 正向动画完成,开始反向动画
|
||||
_animationController!.reverse();
|
||||
} else if (status == AnimationStatus.dismissed) {
|
||||
// 反向动画完成,增加计数
|
||||
_flashCount++;
|
||||
// 闪烁3次后停止
|
||||
if (_flashCount >= 5) {
|
||||
_animationController!.dispose();
|
||||
setState(() {
|
||||
_shouldAnimate = false;
|
||||
_highlightedId = null;
|
||||
});
|
||||
} else {
|
||||
// 继续下一次闪烁
|
||||
_animationController!.forward();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_animationController!.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
List data = widget.sleepReport['hrs'] ?? [];
|
||||
|
||||
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: Wrap(
|
||||
spacing: 23.rpx,
|
||||
runSpacing: 25.rpx,
|
||||
children: List.generate(data.length, (index) {
|
||||
final item = data[index];
|
||||
item['showTip'] = true;
|
||||
final bool isHighlighted = _shouldAnimate &&
|
||||
item['id'] == _highlightedId;
|
||||
|
||||
return SizedBox(
|
||||
width: (MediaQuery.of(context).size.width - 160.rpx) / 3,
|
||||
child: AnimatedBuilder(
|
||||
animation: _animationController ?? AlwaysStoppedAnimation(0),
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
key: isHighlighted ? _highlightKey : null,
|
||||
decoration: isHighlighted
|
||||
? BoxDecoration(
|
||||
border: Border.all(
|
||||
color: themeController.currentColor.sc2
|
||||
.withOpacity(_animationController?.value ?? 0),
|
||||
width: 1.rpx,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
)
|
||||
: null,
|
||||
child: SleepDataModuleWidget(data: item),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("心率监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
|
||||
List data = widget.sleepReport['hrs'] ?? [];
|
||||
|
||||
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: Wrap(
|
||||
spacing: 23.rpx, // 横向间距(左右间距如需加可设置)
|
||||
runSpacing: 25.rpx, // 每行之间的垂直间距
|
||||
children: List.generate(data.length, (index) {
|
||||
final item = data[index];
|
||||
item['showTip'] = true;
|
||||
return SizedBox(
|
||||
width: (MediaQuery.of(context).size.width - 160.rpx) / 3,
|
||||
child: SleepDataModuleWidget(data: item),
|
||||
// child: Container(
|
||||
// width: 20,
|
||||
// height: 20,
|
||||
// color: Colors.red,
|
||||
// ),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.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 HeartRateStandardWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -36,340 +37,346 @@ class _HeartRateStandardWidgetState extends State<HeartRateStandardWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
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['hrbc'] as List).cast<Map<String, dynamic>>();
|
||||
final dataPoints = data.map((item) {
|
||||
final x = item['st'] as int;
|
||||
final y = (item['value'] as num).toDouble(); // 安全地转换为 double
|
||||
return TimeSeriesPoint(x, y);
|
||||
}).toList();
|
||||
final startTime = widget.sleepReport['startTime'];
|
||||
final endTime = widget.sleepReport['endTime'];
|
||||
List<Map<String, dynamic>> data =
|
||||
(widget.sleepReport['hrbc'] as List).cast<Map<String, dynamic>>();
|
||||
final dataPoints = data.map((item) {
|
||||
final x = item['st'] as int;
|
||||
final y = (item['value'] as num).toDouble(); // 安全地转换为 double
|
||||
return TimeSeriesPoint(x, y);
|
||||
}).toList();
|
||||
|
||||
List<Map<String, dynamic>> hrs =
|
||||
(widget.sleepReport['hrs'] as List).cast<Map<String, dynamic>>();
|
||||
//206 平均心率
|
||||
//202 基准心率
|
||||
//207 最低心率
|
||||
//208 最高心率
|
||||
// 找 id == 206 的元素(平均心率)
|
||||
Map<String, dynamic>? avgHeartRate = hrs.firstWhere(
|
||||
(element) => element['id'] == 206,
|
||||
orElse: () => {},
|
||||
);
|
||||
List<Map<String, dynamic>> hrs =
|
||||
(widget.sleepReport['hrs'] as List).cast<Map<String, dynamic>>();
|
||||
//206 平均心率
|
||||
//202 基准心率
|
||||
//207 最低心率
|
||||
//208 最高心率
|
||||
// 找 id == 206 的元素(平均心率)
|
||||
Map<String, dynamic>? avgHeartRate = hrs.firstWhere(
|
||||
(element) => element['id'] == 206,
|
||||
orElse: () => {},
|
||||
);
|
||||
|
||||
// 找 id == 202 的元素(基准心率)
|
||||
Map<String, dynamic>? baseHeartRate = hrs.firstWhere(
|
||||
(element) => element['id'] == 202,
|
||||
orElse: () => {},
|
||||
);
|
||||
Map<String, dynamic>? baseHeartRate = hrs.firstWhere(
|
||||
(element) => element['id'] == 202,
|
||||
orElse: () => {},
|
||||
);
|
||||
|
||||
// 找 id == 207 的元素(最低心率)
|
||||
Map<String, dynamic>? minHeartRate = hrs.firstWhere(
|
||||
(element) => element['id'] == 207,
|
||||
orElse: () => {},
|
||||
);
|
||||
Map<String, dynamic>? minHeartRate = hrs.firstWhere(
|
||||
(element) => element['id'] == 207,
|
||||
orElse: () => {},
|
||||
);
|
||||
|
||||
// 找 id == 208 的元素(最高心率)
|
||||
Map<String, dynamic>? maxHeartRate = hrs.firstWhere(
|
||||
(element) => element['id'] == 208,
|
||||
orElse: () => {},
|
||||
);
|
||||
String range = baseHeartRate['range'] ?? '';
|
||||
int min = 0;
|
||||
int max = 0;
|
||||
Map<String, dynamic>? maxHeartRate = hrs.firstWhere(
|
||||
(element) => element['id'] == 208,
|
||||
orElse: () => {},
|
||||
);
|
||||
String range = baseHeartRate['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;
|
||||
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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"心率数据介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"心率数据介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
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(
|
||||
// // color: Colors.red,
|
||||
// width: double.infinity,
|
||||
// // height: 300.rpx,
|
||||
// child: TimeSeriesChart(
|
||||
// startTime: startTime,
|
||||
// endTime: endTime,
|
||||
// yMin: min.toDouble(),
|
||||
// yMax: max.toDouble(),
|
||||
// dataPoints: dataPoints,
|
||||
// ),
|
||||
// ),
|
||||
Container(
|
||||
// color: Colors.red,
|
||||
width: double.infinity,
|
||||
// height: 300.rpx,
|
||||
child: TimeSeriesChart(
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
yMin: 50,
|
||||
yMax: 150,
|
||||
dataPoints: dataPoints,
|
||||
actYMin: min.toDouble(),
|
||||
actYMax: max.toDouble(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 0.rpx, 0.rpx),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
SizedBox(
|
||||
height: 31.rpx,
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
"${avgHeartRate['name']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"${avgHeartRate['value']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc2,
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize),
|
||||
),
|
||||
Text(
|
||||
"${avgHeartRate['unit']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().small_text_fontSize),
|
||||
),
|
||||
].divide(SizedBox(
|
||||
width: 6.rpx,
|
||||
)),
|
||||
),
|
||||
],
|
||||
// 圆形小球容器
|
||||
Container(
|
||||
width: 14.rpx, // 圆球的直径
|
||||
height: 14.rpx,
|
||||
decoration: BoxDecoration(
|
||||
color: themeController.currentColor.sc2, // 小球的颜色
|
||||
shape: BoxShape.circle, // 设置为圆形
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
"${baseHeartRate['name']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"${baseHeartRate['value']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc2,
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
"${baseHeartRate['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(
|
||||
"${minHeartRate['name']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"${minHeartRate['value']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc2,
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
"${minHeartRate['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(
|
||||
"${maxHeartRate['name']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"${maxHeartRate['value']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc2,
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
"${maxHeartRate['unit']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().small_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
].divide(SizedBox(
|
||||
width: 6.rpx,
|
||||
)),
|
||||
),
|
||||
],
|
||||
SizedBox(width: 15.rpx), // 圆球和文字之间的间隔
|
||||
// 文字
|
||||
Text(
|
||||
'正常范围'.tr + "${range}",
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
AppConstants().smaller_text_fontSize, // 文字的大小
|
||||
color: themeController.currentColor.sc3, // 文字颜色
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
].divide(SizedBox(
|
||||
height: 18.rpx,
|
||||
)),
|
||||
// Container(
|
||||
// // color: Colors.red,
|
||||
// width: double.infinity,
|
||||
// // height: 300.rpx,
|
||||
// child: TimeSeriesChart(
|
||||
// startTime: startTime,
|
||||
// endTime: endTime,
|
||||
// yMin: min.toDouble(),
|
||||
// yMax: max.toDouble(),
|
||||
// dataPoints: dataPoints,
|
||||
// ),
|
||||
// ),
|
||||
Container(
|
||||
// color: Colors.red,
|
||||
width: double.infinity,
|
||||
// height: 300.rpx,
|
||||
child: TimeSeriesChart(
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
yMin: 50,
|
||||
yMax: 150,
|
||||
dataPoints: dataPoints,
|
||||
actYMin: min.toDouble(),
|
||||
actYMax: max.toDouble(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 0.rpx, 0.rpx),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
"${avgHeartRate['name']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"${avgHeartRate['value']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc2,
|
||||
fontSize: AppConstants()
|
||||
.normal_text_fontSize),
|
||||
),
|
||||
Text(
|
||||
"${avgHeartRate['unit']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().small_text_fontSize),
|
||||
),
|
||||
].divide(SizedBox(
|
||||
width: 6.rpx,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
"${baseHeartRate['name']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"${baseHeartRate['value']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc2,
|
||||
fontSize: AppConstants()
|
||||
.normal_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
"${baseHeartRate['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(
|
||||
"${minHeartRate['name']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"${minHeartRate['value']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc2,
|
||||
fontSize: AppConstants()
|
||||
.normal_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
"${minHeartRate['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(
|
||||
"${maxHeartRate['name']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"${maxHeartRate['value']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc2,
|
||||
fontSize: AppConstants()
|
||||
.normal_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
"${maxHeartRate['unit']}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().small_text_fontSize),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
].divide(SizedBox(
|
||||
width: 6.rpx,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
].divide(SizedBox(
|
||||
height: 18.rpx,
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/StatusBarWithIndicator.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class HrvWidget extends StatefulWidget {
|
||||
HrvWidget({super.key});
|
||||
@@ -33,85 +34,91 @@ class _HrvWidgetState extends State<HrvWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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(
|
||||
"心率变异性(HRV)".tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().title_text_fontSize),
|
||||
),
|
||||
ClickableContainer(
|
||||
backgroundColor: Colors.transparent,
|
||||
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"心率变异性(HRV)介绍。",
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
try {
|
||||
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(
|
||||
"心率变异性(HRV)".tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().title_text_fontSize),
|
||||
),
|
||||
ClickableContainer(
|
||||
backgroundColor: Colors.transparent,
|
||||
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"心率变异性(HRV)介绍。",
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 83.rpx,
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: StatusBarWithIndicator(
|
||||
selectKey: 2,
|
||||
showLabel: [
|
||||
{"key": 1, "name": "正常", "color": Color(0xFF4CAF50)},
|
||||
{"key": 2, "name": "一般", "color": Color(0xFF8BC34A)},
|
||||
{"key": 3, "name": "注意", "color": Color(0xFFFFC107)},
|
||||
{"key": 4, "name": "警告", "color": Color(0xFFF44336)},
|
||||
],
|
||||
SizedBox(
|
||||
height: 83.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 56.rpx,
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: StatusBarWithIndicator(
|
||||
selectKey: 2,
|
||||
showLabel: [
|
||||
{"key": 1, "name": "正常", "color": Color(0xFF4CAF50)},
|
||||
{"key": 2, "name": "一般", "color": Color(0xFF8BC34A)},
|
||||
{"key": 3, "name": "注意", "color": Color(0xFFFFC107)},
|
||||
{"key": 4, "name": "警告", "color": Color(0xFFF44336)},
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 56.rpx,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/StatusBarWithIndicator.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class SkinPercentWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -34,7 +35,8 @@ class _SkinPercentWidgetState extends State<SkinPercentWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['sicp'] == null ||
|
||||
widget.sleepReport['sicp'].isEmpty) {
|
||||
return Container();
|
||||
@@ -134,5 +136,10 @@ class _SkinPercentWidgetState extends State<SkinPercentWidget> {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,68 +3,139 @@ 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/home_page/SleepDataModuleWidget.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class SleepCard extends StatefulWidget {
|
||||
var sleepReport;
|
||||
SleepCard({super.key, required this.sleepReport});
|
||||
final dynamic sleepReport;
|
||||
final int? highlightItem;
|
||||
|
||||
SleepCard({super.key, required this.sleepReport, this.highlightItem});
|
||||
|
||||
@override
|
||||
State<SleepCard> createState() => _SleepCardState();
|
||||
}
|
||||
|
||||
class _SleepCardState extends State<SleepCard> {
|
||||
@override
|
||||
void setState(VoidCallback callback) {
|
||||
super.setState(callback);
|
||||
}
|
||||
class _SleepCardState extends State<SleepCard> with TickerProviderStateMixin {
|
||||
final GlobalKey _highlightKey = GlobalKey();
|
||||
AnimationController? _animationController;
|
||||
bool _shouldAnimate = false;
|
||||
int? _highlightedId;
|
||||
int _flashCount = 0; // 用于跟踪闪烁次数
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.highlightItem != null) {
|
||||
_highlightedId = widget.highlightItem;
|
||||
_shouldAnimate = true;
|
||||
_initAnimation();
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.highlightItem != null && _highlightKey.currentContext != null) {
|
||||
Scrollable.ensureVisible(
|
||||
_highlightKey.currentContext!,
|
||||
duration: Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOut,
|
||||
alignment: 0.3,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _initAnimation() {
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: Duration(milliseconds: 300),
|
||||
)..addStatusListener((status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
// 正向动画完成,开始反向动画
|
||||
_animationController!.reverse();
|
||||
} else if (status == AnimationStatus.dismissed) {
|
||||
// 反向动画完成,增加计数
|
||||
_flashCount++;
|
||||
// 闪烁3次后停止
|
||||
if (_flashCount >= 5) {
|
||||
_animationController!.dispose();
|
||||
setState(() {
|
||||
_shouldAnimate = false;
|
||||
_highlightedId = null;
|
||||
});
|
||||
} else {
|
||||
// 继续下一次闪烁
|
||||
_animationController!.forward();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_animationController!.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
List data = widget.sleepReport['bs'] ?? [];
|
||||
|
||||
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: Wrap(
|
||||
spacing: 23.rpx,
|
||||
runSpacing: 25.rpx,
|
||||
children: List.generate(data.length, (index) {
|
||||
final item = data[index];
|
||||
item['showTip'] = true;
|
||||
final bool isHighlighted = _shouldAnimate &&
|
||||
item['id'] == _highlightedId;
|
||||
|
||||
return SizedBox(
|
||||
width: (MediaQuery.of(context).size.width - 160.rpx) / 3,
|
||||
child: AnimatedBuilder(
|
||||
animation: _animationController ?? AlwaysStoppedAnimation(0),
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
key: isHighlighted ? _highlightKey : null,
|
||||
decoration: isHighlighted
|
||||
? BoxDecoration(
|
||||
border: Border.all(
|
||||
color: themeController.currentColor.sc2
|
||||
.withOpacity(_animationController?.value ?? 0),
|
||||
width: 1.rpx,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
)
|
||||
: null,
|
||||
child: SleepDataModuleWidget(data: item),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
|
||||
List data = widget.sleepReport['bs'] ?? [];
|
||||
|
||||
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: Wrap(
|
||||
spacing: 23.rpx, // 横向间距(左右间距如需加可设置)
|
||||
runSpacing: 25.rpx, // 每行之间的垂直间距
|
||||
children: List.generate(data.length, (index) {
|
||||
final item = data[index];
|
||||
item['showTip'] = true;
|
||||
return SizedBox(
|
||||
width: (MediaQuery.of(context).size.width - 160.rpx) / 3,
|
||||
child: SleepDataModuleWidget(data: item),
|
||||
// child: Container(
|
||||
// width: 20,
|
||||
// height: 20,
|
||||
// color: Colors.red,
|
||||
// ),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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/pages/sleep_report/chart/SegmentedCirclePainter.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class SleepScoreWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -32,17 +33,13 @@ class _SleepScoreWidgetState extends State<SleepScoreWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
// List<Map<String, dynamic>> showLabel = [
|
||||
// {"level": 1, "name": "优秀(≥85)", "color": Color(0xFF4CAF50)},
|
||||
// {"level": 2, "name": "良好(75~84)", "color": Color(0xFF8BC34A)},
|
||||
// {"level": 3, "name": "合格(60~74)", "color": Color(0xFFFFC107)},
|
||||
// {"level": 4, "name": "注意(<60)", "color": Color(0xFFF44336)},
|
||||
// ];
|
||||
|
||||
List showLabel = widget.sleepReport['score']['type'];
|
||||
List stages = widget.sleepReport['score']['stages'];
|
||||
List<SegmentData> segments = parseSegments(widget.sleepReport);
|
||||
@@ -191,6 +188,11 @@ class _SleepScoreWidgetState extends State<SleepScoreWidget> {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
//获取睡眠等级
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/GradientLine.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/SnoreWaveform.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
//睡眠规律性
|
||||
class SleepViewWidget extends StatefulWidget {
|
||||
@@ -36,183 +37,126 @@ class _SleepViewWidgetState extends State<SleepViewWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
// List showLabel = widget.sleepReport['sleepData']['type'];
|
||||
List showLabel = widget.sleepReport['sleepData']['type']
|
||||
.where((item) => item['show'] != false)
|
||||
.toList();
|
||||
// List showLabel = widget.sleepReport['sleepData']['type'];
|
||||
List showLabel = widget.sleepReport['sleepData']['type']
|
||||
.where((item) => item['show'] != false)
|
||||
.toList();
|
||||
|
||||
|
||||
List snoreValues = widget.sleepReport['ssp'];
|
||||
Map time = MyUtils.diffHoursMinutesMap(
|
||||
widget.sleepReport['startTime'], widget.sleepReport['endTime']);
|
||||
int hour = time['hours'];
|
||||
int minutes = time['minutes'];
|
||||
|
||||
List stages = widget.sleepReport['sleepData']['stages'];
|
||||
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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
List snoreValues = [];
|
||||
List lightSnore = widget.sleepReport['ssp']['data'][0];
|
||||
List heavySnore = widget.sleepReport['ssp']['data'][1];
|
||||
|
||||
|
||||
snoreValues = [...lightSnore, ...heavySnore];
|
||||
|
||||
|
||||
snoreValues.sort((a, b) {
|
||||
return a['st'].compareTo(b['st']);
|
||||
});
|
||||
Map time = MyUtils.diffHoursMinutesMap(
|
||||
widget.sleepReport['startTime'], widget.sleepReport['endTime']);
|
||||
int hour = time['hours'];
|
||||
int minutes = time['minutes'];
|
||||
|
||||
List stages = widget.sleepReport['sleepData']['stages'];
|
||||
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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"睡眠规律性介绍。",
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 83.rpx,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
// 左侧 - 入睡时间
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset(
|
||||
"assets/img/moon.png",
|
||||
width: 43.rpx,
|
||||
height: 43.rpx,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
SizedBox(height: 33.rpx),
|
||||
Container(
|
||||
height: 40.rpx,
|
||||
child: Text(
|
||||
"睡眠规律性介绍。",
|
||||
"${MyUtils.formatToHHmm(widget.sleepReport['startTime'])}",
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 83.rpx,
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
// 左侧 - 入睡时间
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset(
|
||||
"assets/img/moon.png",
|
||||
width: 43.rpx,
|
||||
height: 43.rpx,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
SizedBox(height: 33.rpx),
|
||||
Container(
|
||||
height: 40.rpx,
|
||||
child: Text(
|
||||
"${MyUtils.formatToHHmm(widget.sleepReport['startTime'])}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20.rpx),
|
||||
Text(
|
||||
"入睡时间".tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// 中间 - 渐变线 + 时间
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
height: 43.rpx,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
GradientLine(
|
||||
height: 1,
|
||||
color: Colors.white60,
|
||||
),
|
||||
],
|
||||
)),
|
||||
SizedBox(height: 33.rpx), // 与图标对齐
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "$hour",
|
||||
style: TextStyle(
|
||||
color: themeController
|
||||
.currentColor.sc2, // 小时数字颜色
|
||||
fontSize: 36.rpx, // 小时数字字号
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: "小时",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().title_text_fontSize,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: "$minutes",
|
||||
style: TextStyle(
|
||||
color: themeController
|
||||
.currentColor.sc2, // 小时数字颜色
|
||||
fontSize: 36.rpx, // 分钟数字字号
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: "分钟",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().title_text_fontSize,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
SizedBox(height: 20.rpx),
|
||||
Text(
|
||||
"睡眠时长".tr,
|
||||
"入睡时间".tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
@@ -222,111 +166,170 @@ class _SleepViewWidgetState extends State<SleepViewWidget> {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 右侧 - 起床时间图标
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset(
|
||||
"assets/img/sun.png",
|
||||
width: 43.rpx,
|
||||
height: 43.rpx,
|
||||
fit: BoxFit.cover,
|
||||
// 中间 - 渐变线 + 时间
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
height: 43.rpx,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
GradientLine(
|
||||
height: 1,
|
||||
color: Colors.white60,
|
||||
),
|
||||
],
|
||||
)),
|
||||
SizedBox(height: 33.rpx), // 与图标对齐
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "$hour",
|
||||
style: TextStyle(
|
||||
color: themeController
|
||||
.currentColor.sc2, // 小时数字颜色
|
||||
fontSize: 36.rpx, // 小时数字字号
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: "小时",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().title_text_fontSize,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: "$minutes",
|
||||
style: TextStyle(
|
||||
color: themeController
|
||||
.currentColor.sc2, // 小时数字颜色
|
||||
fontSize: 36.rpx, // 分钟数字字号
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: "分钟",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize:
|
||||
AppConstants().title_text_fontSize,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
SizedBox(height: 20.rpx),
|
||||
Text(
|
||||
"睡眠时长".tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 33.rpx),
|
||||
Container(
|
||||
height: 40.rpx,
|
||||
child: Text(
|
||||
"${MyUtils.formatToHHmm(widget.sleepReport['endTime'])}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
|
||||
// 右侧 - 起床时间图标
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset(
|
||||
"assets/img/sun.png",
|
||||
width: 43.rpx,
|
||||
height: 43.rpx,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
SizedBox(height: 33.rpx),
|
||||
Container(
|
||||
height: 40.rpx,
|
||||
child: Text(
|
||||
"${MyUtils.formatToHHmm(widget.sleepReport['endTime'])}",
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20.rpx),
|
||||
Text(
|
||||
"起床时间".tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
SizedBox(height: 20.rpx),
|
||||
Text(
|
||||
"起床时间".tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc4,
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 49.rpx,
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(26.rpx, 0.rpx, 26.rpx, 0.rpx),
|
||||
child: SnoreChartContainer(
|
||||
snoreValues: snoreValues,
|
||||
barData: stages,
|
||||
startTime: widget.sleepReport['startTime'],
|
||||
endTime: widget.sleepReport['endTime'],
|
||||
showLabel: showLabel,
|
||||
SizedBox(
|
||||
height: 49.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 70.rpx,
|
||||
),
|
||||
Wrap(
|
||||
spacing: 55.rpx,
|
||||
runSpacing: 20.rpx,
|
||||
children: showLabel.map<Widget>((item) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(5.rpx),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 20.rpx,
|
||||
height: 20.rpx,
|
||||
decoration: BoxDecoration(
|
||||
color: item["color"] == null || item["color"] == ""
|
||||
? Colors.transparent
|
||||
: stringToColor(item["color"]),
|
||||
borderRadius: BorderRadius.circular(10.rpx),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
26.rpx, 0.rpx, 26.rpx, 0.rpx),
|
||||
child: SnoreChartContainer(
|
||||
snoreValues: snoreValues,
|
||||
barData: stages,
|
||||
startTime: widget.sleepReport['startTime'],
|
||||
endTime: widget.sleepReport['endTime'],
|
||||
showLabel: showLabel,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 70.rpx,
|
||||
),
|
||||
Wrap(
|
||||
spacing: 55.rpx,
|
||||
runSpacing: 20.rpx,
|
||||
children: showLabel.map<Widget>((item) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(5.rpx),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 20.rpx,
|
||||
height: 20.rpx,
|
||||
decoration: BoxDecoration(
|
||||
color: item["color"] == null || item["color"] == ""
|
||||
? Colors.transparent
|
||||
: stringToColor(item["color"]),
|
||||
borderRadius: BorderRadius.circular(10.rpx),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 17.rpx),
|
||||
Text(
|
||||
item["name"],
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24.rpx,
|
||||
SizedBox(width: 17.rpx),
|
||||
Text(
|
||||
item["name"],
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24.rpx,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> generateSnoreValues(int startTime, int endTime) {
|
||||
final int count = 100;
|
||||
final int interval = ((endTime - startTime) / count).floor();
|
||||
|
||||
return List.generate(count, (index) {
|
||||
final timestamp = startTime + interval * index;
|
||||
final value =
|
||||
(2 + 1 * (index % 7) / 6.0) * (index % 2 == 0 ? 1 : -1); // 示例上下对称波动
|
||||
return {
|
||||
"timestamp": timestamp,
|
||||
"value": double.parse(value.toStringAsFixed(2)),
|
||||
};
|
||||
});
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
import 'package:ef/ef.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
@@ -7,6 +10,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/LineChartByRange.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/SnoreChart.dart';
|
||||
|
||||
class SnoreViewWidgetWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -34,106 +38,243 @@ class _SnoreViewWidgetWidgetState extends State<SnoreViewWidgetWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport is! Map ||
|
||||
widget.sleepReport.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
double maxY = 60;
|
||||
var startTime = widget.sleepReport['startTime'];
|
||||
var endTime = widget.sleepReport['endTime'];
|
||||
List snoreValues = [];
|
||||
|
||||
List<Map<String, dynamic>> data =
|
||||
(widget.sleepReport['ssp'] as List).cast<Map<String, dynamic>>();
|
||||
List<Map<String, dynamic>> showLabel = convertToShowLabel(data);
|
||||
// List<Map<String, dynamic>> showLabel = [
|
||||
// // {'startTime': 1748275800344, "endTime": 1748283000000, "times": 4},
|
||||
// {'startTime': 1748293800000, "endTime": 1748294400000, "times": 7},
|
||||
// ];
|
||||
var startTime = widget.sleepReport['startTime'];
|
||||
var endTime = widget.sleepReport['endTime'];
|
||||
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, 0.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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"打鼾监测介绍。",
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
List type = widget.sleepReport['ssp']['type'];
|
||||
List lightSnore = widget.sleepReport['ssp']['data'][0];
|
||||
List heavySnore = widget.sleepReport['ssp']['data'][1];
|
||||
|
||||
List<Map> processedLightSnore = lightSnore.map((item) {
|
||||
return {
|
||||
...item,
|
||||
'id': type[0]['id'],
|
||||
'name': type[0]['name'],
|
||||
'color': type[0]['color'],
|
||||
};
|
||||
}).toList();
|
||||
|
||||
List<Map> processedHeavySnore = heavySnore.map((item) {
|
||||
return {
|
||||
...item,
|
||||
'id': type[1]['id'],
|
||||
'name': type[1]['name'],
|
||||
'color': type[1]['color'],
|
||||
};
|
||||
}).toList();
|
||||
|
||||
snoreValues = [...processedLightSnore, ...processedHeavySnore];
|
||||
snoreValues.sort((a, b) => a['st'].compareTo(b['st']));
|
||||
print(snoreValues);
|
||||
List<BarData> barDataList = snoreValues.map<BarData>((item) {
|
||||
return BarData(
|
||||
st: item['st'],
|
||||
et: item['et'],
|
||||
value: (item['value'] as num).toDouble() > maxY
|
||||
? maxY + 3
|
||||
: (item['value'] as num).toDouble(),
|
||||
id: item['id'],
|
||||
name: item['name'],
|
||||
color: (item['color'] == null || item['color'].isEmpty)
|
||||
? (item['id'] == 1 ? Colors.green : Colors.red)
|
||||
: stringToColor(item['color']),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
// List<Map<String, dynamic>> data =
|
||||
// (widget.sleepReport['ssp'] as List).cast<Map<String, dynamic>>();
|
||||
// List<Map<String, dynamic>> showLabel = convertToShowLabel(data);
|
||||
|
||||
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, 0.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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"打鼾监测介绍。",
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 32.rpx,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"次".tr,
|
||||
style: TextStyle(
|
||||
color: stringToColor("#FFFFFF"), fontSize: 18.rpx),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 32.rpx,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"次".tr,
|
||||
style: TextStyle(
|
||||
color: stringToColor("#FFFFFF"), fontSize: 18.rpx),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0.rpx, 40.rpx, 20.rpx, 0.rpx),
|
||||
// child: LineChartByRange(
|
||||
// showLabel: showLabel,
|
||||
// startTime: startTime,
|
||||
// endTime: endTime,
|
||||
// ),
|
||||
child: BarChartWidget(
|
||||
data: barDataList,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
maxYValue: maxY, // 最大值可自定义
|
||||
yStepCount: 6, // 分4段(0, 5, 10, 15, 20)
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(0.rpx, 40.rpx, 0.rpx, 0.rpx),
|
||||
child: LineChartByRange(
|
||||
showLabel: showLabel,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 52.rpx,
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(0.rpx, 52.rpx, 0.rpx, 0.rpx),
|
||||
child: Container(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center, // 左对齐
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// 小圆球
|
||||
Container(
|
||||
width: 14.rpx,
|
||||
height: 14.rpx,
|
||||
decoration: BoxDecoration(
|
||||
color: (type[0]?['color'] == null ||
|
||||
type[0]?['color'].isEmpty)
|
||||
? Colors.green
|
||||
: stringToColor(
|
||||
"${type[0]['color']}"), // 你想要的颜色
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12.rpx), // 小圆球和文字间距
|
||||
Text(
|
||||
'${type[0]?['name']}',
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize,
|
||||
color: themeController.currentColor.sc3),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.rpx), // 两行文字间距
|
||||
Text(
|
||||
'${(type[0]?['value'] == null || type[0]['value'].toString().isEmpty) ? '未知数据'.tr : '${type[0]?['value']}${(type[0]?['unit'] == null || type[0]['unit'].toString().isEmpty) ? '' : type[0]?['unit']}'}',
|
||||
style: TextStyle(
|
||||
fontSize: AppConstants().small_text_fontSize,
|
||||
color: themeController.currentColor.sc4),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center, // 左对齐
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// 小圆球
|
||||
Container(
|
||||
width: 14.rpx,
|
||||
height: 14.rpx,
|
||||
decoration: BoxDecoration(
|
||||
color: (type[1]?['color'] == null ||
|
||||
type[1]?['color'].isEmpty)
|
||||
? Colors.red
|
||||
: stringToColor(
|
||||
"${type[1]['color']}"), // 你想要的颜色
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12.rpx), // 小圆球和文字间距
|
||||
Text(
|
||||
'${type[1]?['name']}',
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
AppConstants().normal_text_fontSize,
|
||||
color: themeController.currentColor.sc3),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.rpx), // 两行文字间距
|
||||
Text(
|
||||
'${(type[1]?['value'] == null || type[1]['value'].toString().isEmpty) ? '未知数据'.tr : '${type[1]?['value']}${(type[1]?['unit'] == null || type[1]['unit'].toString().isEmpty) ? '' : type[1]?['unit']}'}',
|
||||
style: TextStyle(
|
||||
fontSize: AppConstants().small_text_fontSize,
|
||||
color: themeController.currentColor.sc4),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 52.rpx,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> convertToShowLabel(
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/chart/StatusBarWithIndicator.dart';
|
||||
import 'package:EasyDartModule/EasyDartModule.dart' as es;
|
||||
|
||||
class ZiZhuShenJingPercentWidget extends StatefulWidget {
|
||||
var sleepReport;
|
||||
@@ -36,106 +37,112 @@ class _ZiZhuShenJingPercentWidgetState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['sicp'] == null ||
|
||||
widget.sleepReport['sicp'].isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
int id = 100000;
|
||||
List data = widget.sleepReport['sicp'];
|
||||
final target = data.firstWhere(
|
||||
(element) => element['id'] == id,
|
||||
orElse: () => null, // 如果没有找到,返回 null(你也可以抛异常或用 {} 替代)
|
||||
);
|
||||
try {
|
||||
if (widget.sleepReport == null ||
|
||||
widget.sleepReport['sicp'] == null ||
|
||||
widget.sleepReport['sicp'].isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
int id = 100000;
|
||||
List data = widget.sleepReport['sicp'];
|
||||
final target = data.firstWhere(
|
||||
(element) => element['id'] == id,
|
||||
orElse: () => null, // 如果没有找到,返回 null(你也可以抛异常或用 {} 替代)
|
||||
);
|
||||
|
||||
if (target == null) {
|
||||
return Container();
|
||||
}
|
||||
List<Map<String, dynamic>> showLabel = [];
|
||||
if (target != null && target['type'] is List) {
|
||||
showLabel = (target['type'] as List).map<Map<String, dynamic>>((item) {
|
||||
return {
|
||||
'key': item['type'],
|
||||
'name': item['name'],
|
||||
'color': stringToColor(item['color']),
|
||||
};
|
||||
}).toList();
|
||||
}
|
||||
if (target == null) {
|
||||
return Container();
|
||||
}
|
||||
List<Map<String, dynamic>> showLabel = [];
|
||||
if (target != null && target['type'] is List) {
|
||||
showLabel = (target['type'] as List).map<Map<String, dynamic>>((item) {
|
||||
return {
|
||||
'key': item['type'],
|
||||
'name': item['name'],
|
||||
'color': stringToColor(item['color']),
|
||||
};
|
||||
}).toList();
|
||||
}
|
||||
|
||||
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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"自主神经平衡指数监测介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
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, 0.rpx, 14.rpx, 0), //
|
||||
borderRadius: 0.rpx, // 圆形点击区域
|
||||
onTap: () {
|
||||
showTipDialog(
|
||||
context,
|
||||
Container(
|
||||
child: Text(
|
||||
"自主神经平衡指数监测介绍".tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.rpx,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/explain.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 83.rpx,
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: StatusBarWithIndicator(
|
||||
selectKey: target['value'],
|
||||
showLabel: showLabel,
|
||||
SizedBox(
|
||||
height: 83.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 56.rpx,
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||
child: StatusBarWithIndicator(
|
||||
selectKey: target['value'],
|
||||
showLabel: showLabel,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 56.rpx,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} catch (e) {
|
||||
es.EasyDartModule.logger.error("打鼾监测绘制异常${e}");
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
937
lib/pages/sleep_report/new_sleep_report_page copy.dart
Normal file
937
lib/pages/sleep_report/new_sleep_report_page copy.dart
Normal file
@@ -0,0 +1,937 @@
|
||||
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/common/util/requestWithLog.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||
import 'package:vbvs_app/controller/date/CalendarController.dart';
|
||||
import 'package:vbvs_app/controller/sleep/sleep_report_controller.dart';
|
||||
import 'package:vbvs_app/language/AppLanguage.dart';
|
||||
import 'package:vbvs_app/pages/common/selectDialog.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/AIAdviceWidget.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/BreatheCard.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/BreathePauseNewWidget.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/BreatheStandardWidget.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/CompareSleepWidget.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/DiseasePercentsWidget.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/HeartChangeWidget.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/HeartHealthWidget.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/HeartPointWidget.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/HeartRateCard.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/HeartRateStandardWidget.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/SkinPercentWidget.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/SleepCard.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/SleepScoreWidget.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/SleepView.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/SnoreViewWidget.dart';
|
||||
import 'package:vbvs_app/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart';
|
||||
|
||||
class NewSleepReportPage extends StatefulWidget {
|
||||
var data;
|
||||
NewSleepReportPage({super.key, required this.data});
|
||||
|
||||
@override
|
||||
State<NewSleepReportPage> createState() => _NewSleepReportPageState();
|
||||
}
|
||||
|
||||
class _NewSleepReportPageState extends State<NewSleepReportPage> {
|
||||
SleepReportController sleepReportController = Get.find();
|
||||
CalendarController calendarController = Get.find();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (widget.data['date'] == null) {
|
||||
widget.data['date'] = DateTime.now();
|
||||
}
|
||||
calendarController.selectedDate.value =
|
||||
DateTime.fromMillisecondsSinceEpoch(widget.data['date']);
|
||||
sleepReportController.selectedDate.value =
|
||||
DateTime.fromMillisecondsSinceEpoch(widget.data['date']);
|
||||
if (widget.data['type'] != null) {
|
||||
sleepReportController.model.type = widget.data['type'];
|
||||
} else {
|
||||
sleepReportController.model.type = 1;
|
||||
}
|
||||
String date = MyUtils.formatToDate(widget.data['date']);
|
||||
// String date = '2025-5-27';
|
||||
requestWithLog(
|
||||
logTitle: "查询睡眠报告",
|
||||
method: MyHttpMethod.get,
|
||||
queryUrl:
|
||||
"https://sleepdata.he-info.com/api/analysis/sleep/analysis?mac=${widget.data['mac']}&time=${date}&type=${sleepReportController.model.type}",
|
||||
onSuccess: (res) {
|
||||
print(res);
|
||||
sleepReportController.sleepReport.value = res.data;
|
||||
sleepReportController.updateAll();
|
||||
},
|
||||
onFailure: (res) {
|
||||
TopSlideNotification.show(context,
|
||||
text: res.msg!, textColor: themeController.currentColor.sc9);
|
||||
sleepReportController.sleepReport.value = {};
|
||||
sleepReportController.updateAll();
|
||||
print(res);
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double lineWidth = 115.rpx;
|
||||
return LayoutBuilder(
|
||||
builder: (context, bodySize) => GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('assets/img/bgNoImg.png'), // 本地图片
|
||||
fit: BoxFit.fill, // 填满整个 Container
|
||||
),
|
||||
),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent, // 背景透明
|
||||
appBar: AppBar(
|
||||
backgroundColor: themeController.currentColor.sc17,
|
||||
automaticallyImplyLeading: false,
|
||||
iconTheme: IconThemeData(color: themeController.currentColor.sc3),
|
||||
titleSpacing: 0,
|
||||
title: Container(
|
||||
width: double.infinity,
|
||||
height: 180.rpx,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
/// 居中标题
|
||||
Text(
|
||||
'健康报告'.tr,
|
||||
style: FlutterFlowTheme.of(context).bodyMedium.override(
|
||||
fontFamily: 'Readex Pro',
|
||||
color: themeController.currentColor.sc3,
|
||||
letterSpacing: 0,
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
),
|
||||
|
||||
/// 左边返回按钮
|
||||
Positioned(
|
||||
left: 0,
|
||||
child: returnIconButtom,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
top: true,
|
||||
child: SingleChildScrollView(
|
||||
child: Obx(() {
|
||||
var sleepReport = sleepReportController.sleepReport;
|
||||
print(sleepReport);
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsetsDirectional.fromSTEB(0, 30.rpx, 0, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 90.rpx,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: themeController.currentColor.sc5),
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 15.rpx, 30.rpx, 15.rpx),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.bottomLeft,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
ClickableContainer(
|
||||
backgroundColor: Colors.transparent,
|
||||
highlightColor:
|
||||
themeController.currentColor.sc3,
|
||||
borderRadius: 8.rpx,
|
||||
padding: EdgeInsets.all(0),
|
||||
onTap: () async {
|
||||
sleepReportController.model.type =
|
||||
1;
|
||||
sleepReportController.updateAll();
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
width: 115.rpx, // 固定宽度为 160.rpx
|
||||
alignment:
|
||||
Alignment.center, // 文字居中
|
||||
child: Text(
|
||||
'日报'.tr,
|
||||
style: FlutterFlowTheme.of(
|
||||
context)
|
||||
.bodyMedium
|
||||
.override(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: AppConstants()
|
||||
.title_text_fontSize,
|
||||
letterSpacing: 0.0,
|
||||
color:
|
||||
sleepReportController
|
||||
.model
|
||||
.type ==
|
||||
1
|
||||
? themeController
|
||||
.currentColor
|
||||
.sc2
|
||||
: themeController
|
||||
.currentColor
|
||||
.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10.rpx),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Obx(() {
|
||||
// return ClickableContainer(
|
||||
// backgroundColor: Colors.transparent,
|
||||
// highlightColor:
|
||||
// themeController.currentColor.sc3,
|
||||
// borderRadius: 8.rpx,
|
||||
// padding: EdgeInsets.all(0),
|
||||
// onTap: () async {
|
||||
// sleepReportController.model.type =
|
||||
// 2;
|
||||
// sleepReportController.updateAll();
|
||||
// },
|
||||
// child: Column(
|
||||
// mainAxisSize: MainAxisSize.max,
|
||||
// children: [
|
||||
// Container(
|
||||
// width: 115.rpx, // 固定宽度为 160.rpx
|
||||
// alignment:
|
||||
// Alignment.center, // 文字居中
|
||||
// child: Text(
|
||||
// '周报'.tr,
|
||||
// style: FlutterFlowTheme.of(
|
||||
// context)
|
||||
// .bodyMedium
|
||||
// .override(
|
||||
// fontFamily: 'Inter',
|
||||
// fontSize: AppConstants()
|
||||
// .title_text_fontSize,
|
||||
// letterSpacing: 0.0,
|
||||
// color:
|
||||
// sleepReportController
|
||||
// .model
|
||||
// .type ==
|
||||
// 2
|
||||
// ? themeController
|
||||
// .currentColor
|
||||
// .sc2
|
||||
// : themeController
|
||||
// .currentColor
|
||||
// .sc3,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(height: 10.rpx),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }),
|
||||
// Obx(() {
|
||||
// return ClickableContainer(
|
||||
// backgroundColor: Colors.transparent,
|
||||
// highlightColor:
|
||||
// themeController.currentColor.sc3,
|
||||
// borderRadius: 8.rpx,
|
||||
// padding: EdgeInsets.all(0),
|
||||
// onTap: () async {
|
||||
// sleepReportController.model.type =
|
||||
// 3;
|
||||
// sleepReportController.updateAll();
|
||||
// },
|
||||
// child: Column(
|
||||
// mainAxisSize: MainAxisSize.max,
|
||||
// children: [
|
||||
// Container(
|
||||
// width: 115.rpx, // 固定宽度为 160.rpx
|
||||
// alignment:
|
||||
// Alignment.center, // 文字居中
|
||||
// child: Text(
|
||||
// '月报'.tr,
|
||||
// style: FlutterFlowTheme.of(
|
||||
// context)
|
||||
// .bodyMedium
|
||||
// .override(
|
||||
// fontFamily: 'Inter',
|
||||
// fontSize: AppConstants()
|
||||
// .title_text_fontSize,
|
||||
// letterSpacing: 0.0,
|
||||
// color:
|
||||
// sleepReportController
|
||||
// .model
|
||||
// .type ==
|
||||
// 3
|
||||
// ? themeController
|
||||
// .currentColor
|
||||
// .sc2
|
||||
// : themeController
|
||||
// .currentColor
|
||||
// .sc3,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(height: 10.rpx),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }),
|
||||
],
|
||||
),
|
||||
AnimatedPositioned(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
bottom: 0,
|
||||
left: sleepReportController.model.type ==
|
||||
1
|
||||
? 0
|
||||
: sleepReportController.model.type ==
|
||||
2
|
||||
? 115.rpx
|
||||
: 230.rpx,
|
||||
child: Container(
|
||||
width: lineWidth,
|
||||
height: 4.rpx,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
themeController.currentColor.sc2,
|
||||
borderRadius:
|
||||
BorderRadius.circular(2.rpx),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Padding(
|
||||
// padding: EdgeInsetsDirectional.fromSTEB(
|
||||
// 0, 0.rpx, 0.rpx, 0),
|
||||
// child: Container(
|
||||
// width: 28.rpx,
|
||||
// height: 28.rpx,
|
||||
// // width: double.infinity,
|
||||
// decoration: BoxDecoration(),
|
||||
// child: SvgPicture.asset(
|
||||
// 'assets/img/icon/share.svg',
|
||||
// fit: BoxFit.cover,
|
||||
// color: themeController.currentColor.sc3,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 32.rpx, 30.rpx, 32.rpx),
|
||||
child: getTimeWidget(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 58.rpx),
|
||||
child: ClickableContainer(
|
||||
backgroundColor: themeController.currentColor.sc5,
|
||||
highlightColor:
|
||||
themeController.currentColor.sc5, // 或你希望的点击水波纹颜色
|
||||
borderRadius: AppConstants()
|
||||
.normal_container_radius, // 如果你想加圆角可以设置 eg. 12.rpx
|
||||
padding: EdgeInsets.zero,
|
||||
onTap: () {},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'实时体征.姓名'.tr,
|
||||
style:
|
||||
FlutterFlowTheme.of(context)
|
||||
.bodyMedium
|
||||
.override(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 26.rpx,
|
||||
letterSpacing: 0.0,
|
||||
color: themeController
|
||||
.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'实时体征.年龄'.tr,
|
||||
style:
|
||||
FlutterFlowTheme.of(context)
|
||||
.bodyMedium
|
||||
.override(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 26.rpx,
|
||||
letterSpacing: 0.0,
|
||||
color: themeController
|
||||
.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
].divide(SizedBox(height: 34.rpx)),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'传感器1号',
|
||||
style:
|
||||
FlutterFlowTheme.of(context)
|
||||
.bodyMedium
|
||||
.override(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 26.rpx,
|
||||
letterSpacing: 0.0,
|
||||
color: themeController
|
||||
.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'69',
|
||||
style:
|
||||
FlutterFlowTheme.of(context)
|
||||
.bodyMedium
|
||||
.override(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 26.rpx,
|
||||
letterSpacing: 0.0,
|
||||
color: themeController
|
||||
.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
].divide(SizedBox(height: 34.rpx)),
|
||||
),
|
||||
]
|
||||
.divide(SizedBox(width: 33.rpx))
|
||||
.addToStart(SizedBox(width: 37.rpx)),
|
||||
),
|
||||
]
|
||||
.addToStart(SizedBox(height: 36.rpx))
|
||||
.addToEnd(SizedBox(height: 36.rpx)),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'实时体征.设备ID'.tr,
|
||||
style:
|
||||
FlutterFlowTheme.of(context)
|
||||
.bodyMedium
|
||||
.override(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 26.rpx,
|
||||
letterSpacing: 0.0,
|
||||
color: themeController
|
||||
.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'实时体征.体重'.tr,
|
||||
style:
|
||||
FlutterFlowTheme.of(context)
|
||||
.bodyMedium
|
||||
.override(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 26.rpx,
|
||||
letterSpacing: 0.0,
|
||||
color: themeController
|
||||
.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
].divide(SizedBox(height: 34.rpx)),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'1231212',
|
||||
// "D11250300003",
|
||||
style:
|
||||
FlutterFlowTheme.of(context)
|
||||
.bodyMedium
|
||||
.override(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 26.rpx,
|
||||
letterSpacing: 0.0,
|
||||
color: themeController
|
||||
.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'55kg',
|
||||
style:
|
||||
FlutterFlowTheme.of(context)
|
||||
.bodyMedium
|
||||
.override(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 26.rpx,
|
||||
letterSpacing: 0.0,
|
||||
color: themeController
|
||||
.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
].divide(SizedBox(height: 34.rpx)),
|
||||
),
|
||||
]
|
||||
.divide(SizedBox(width: 33.rpx))
|
||||
.addToStart(SizedBox(width: 37.rpx)),
|
||||
),
|
||||
]
|
||||
.addToStart(SizedBox(height: 36.rpx))
|
||||
.addToEnd(SizedBox(height: 36.rpx)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child:
|
||||
SleepScoreWidget(sleepReport: sleepReport.value),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: SleepViewWidget(
|
||||
sleepReport: sleepReport,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: SleepCard(
|
||||
sleepReport: sleepReport,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: CompareSleepWidget(sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: HeartPointWidget(
|
||||
sleepReport: sleepReport,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: AIAdviceWidget(sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child:
|
||||
HeartRateStandardWidget(sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: HeartRateCard(sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: HeartChangeWidget(sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child:
|
||||
BreatheStandardWidget(sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: BreatheCard(sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child:
|
||||
SnoreViewWidgetWidget(sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child:
|
||||
BreathePauseNewWidget(sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: HeartHealthWidget(sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child:
|
||||
DiseasePercentsWidget(sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: SkinPercentWidget(sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: ZiZhuShenJingPercentWidget(
|
||||
sleepReport: sleepReport),
|
||||
),
|
||||
),
|
||||
].divide(SizedBox(
|
||||
height: 25.rpx,
|
||||
)),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget getTimeWidget() {
|
||||
final selectedDate = sleepReportController.selectedDate.value!;
|
||||
final type = sleepReportController.model.type;
|
||||
bool isChinese = AppLanguage().isChinese();
|
||||
String displayText = '';
|
||||
if (isChinese) {
|
||||
if (type == 1) {
|
||||
// 日报
|
||||
displayText =
|
||||
MyUtils.getFormatChineseTime(selectedDate.millisecondsSinceEpoch);
|
||||
} else if (type == 2) {
|
||||
// 周报
|
||||
final startOfWeek =
|
||||
selectedDate.subtract(Duration(days: selectedDate.weekday - 1));
|
||||
final endOfWeek = startOfWeek.add(const Duration(days: 6));
|
||||
displayText =
|
||||
'${MyUtils.getFormatChineseTime(startOfWeek.millisecondsSinceEpoch, showWeekday: false)}-${MyUtils.getFormatChineseTime(endOfWeek.millisecondsSinceEpoch, showWeekday: false)}';
|
||||
} else if (type == 3) {
|
||||
// 月报
|
||||
displayText =
|
||||
'${selectedDate.year}年${selectedDate.month.toString().padLeft(2, '0')}月';
|
||||
}
|
||||
} else {
|
||||
if (type == 1) {
|
||||
// Daily Report
|
||||
displayText =
|
||||
MyUtils.getFormatEnglishDate(selectedDate.millisecondsSinceEpoch);
|
||||
} else if (type == 2) {
|
||||
// Weekly Report
|
||||
final startOfWeek =
|
||||
selectedDate.subtract(Duration(days: selectedDate.weekday - 1));
|
||||
final endOfWeek = startOfWeek.add(const Duration(days: 6));
|
||||
displayText =
|
||||
'${MyUtils.getFormatEnglishDate(startOfWeek.millisecondsSinceEpoch)} - ${MyUtils.getFormatEnglishDate(endOfWeek.millisecondsSinceEpoch)}';
|
||||
} else if (type == 3) {
|
||||
// Monthly Report
|
||||
displayText =
|
||||
'${_getEnglishMonthName(selectedDate.month)} ${selectedDate.year}';
|
||||
}
|
||||
}
|
||||
|
||||
void onLeftArrowTap() {
|
||||
if (type == 1) {
|
||||
sleepReportController.selectedDate.value =
|
||||
selectedDate.subtract(const Duration(days: 1));
|
||||
} else if (type == 2) {
|
||||
sleepReportController.selectedDate.value =
|
||||
selectedDate.subtract(const Duration(days: 7));
|
||||
} else if (type == 3) {
|
||||
sleepReportController.selectedDate.value = DateTime(
|
||||
selectedDate.year,
|
||||
selectedDate.month - 1,
|
||||
selectedDate.day,
|
||||
);
|
||||
}
|
||||
calendarController.selectedDate.value =
|
||||
sleepReportController.selectedDate.value;
|
||||
// String date = MyUtils.formatToDate(widget.data['date']);
|
||||
String data = MyUtils.formatDate(calendarController.selectedDate.value!);
|
||||
requestWithLog(
|
||||
logTitle: "查询睡眠报告",
|
||||
method: MyHttpMethod.get,
|
||||
queryUrl:
|
||||
"https://sleepdata.he-info.com/api/analysis/sleep/analysis?mac=${widget.data['mac']}&time=${data}&type=${sleepReportController.model.type}",
|
||||
onSuccess: (res) {
|
||||
print(res);
|
||||
sleepReportController.sleepReport.value = res.data;
|
||||
sleepReportController.updateAll();
|
||||
},
|
||||
onFailure: (res) {
|
||||
TopSlideNotification.show(context,
|
||||
text: res.msg!, textColor: themeController.currentColor.sc9);
|
||||
sleepReportController.sleepReport.value = {};
|
||||
sleepReportController.updateAll();
|
||||
print(res);
|
||||
});
|
||||
|
||||
sleepReportController.updateAll();
|
||||
calendarController.updateAll();
|
||||
}
|
||||
|
||||
void onRightArrowTap() {
|
||||
if (type == 1) {
|
||||
sleepReportController.selectedDate.value =
|
||||
selectedDate.add(const Duration(days: 1));
|
||||
} else if (type == 2) {
|
||||
sleepReportController.selectedDate.value =
|
||||
selectedDate.add(const Duration(days: 7));
|
||||
} else if (type == 3) {
|
||||
sleepReportController.selectedDate.value = DateTime(
|
||||
selectedDate.year,
|
||||
selectedDate.month + 1,
|
||||
selectedDate.day,
|
||||
);
|
||||
}
|
||||
calendarController.selectedDate.value =
|
||||
sleepReportController.selectedDate.value;
|
||||
String data = MyUtils.formatDate(calendarController.selectedDate.value!);
|
||||
requestWithLog(
|
||||
logTitle: "查询睡眠报告",
|
||||
method: MyHttpMethod.get,
|
||||
queryUrl:
|
||||
"https://sleepdata.he-info.com/api/analysis/sleep/analysis?mac=${widget.data['mac']}&time=${data}&type=${sleepReportController.model.type}",
|
||||
onSuccess: (res) {
|
||||
print(res);
|
||||
sleepReportController.sleepReport.value = res.data;
|
||||
sleepReportController.updateAll();
|
||||
},
|
||||
onFailure: (res) {
|
||||
TopSlideNotification.show(context,
|
||||
text: res.msg!, textColor: themeController.currentColor.sc9);
|
||||
sleepReportController.sleepReport.value = {};
|
||||
sleepReportController.updateAll();
|
||||
print(res);
|
||||
});
|
||||
sleepReportController.updateAll();
|
||||
calendarController.updateAll();
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(width: 28.rpx, height: 28.rpx), // 占位
|
||||
Row(
|
||||
children: [
|
||||
ClickableContainer(
|
||||
backgroundColor: Colors.transparent,
|
||||
highlightColor: themeController.currentColor.sc3,
|
||||
padding: EdgeInsets.all(10.rpx),
|
||||
borderRadius: 8.rpx,
|
||||
onTap: onLeftArrowTap,
|
||||
child: SizedBox(
|
||||
width: 9.rpx,
|
||||
height: 14.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/arrow_left.svg',
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 26.rpx),
|
||||
child: Text(
|
||||
displayText,
|
||||
style: TextStyle(
|
||||
fontSize: AppConstants().normal_text_fontSize,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
ClickableContainer(
|
||||
backgroundColor: Colors.transparent,
|
||||
highlightColor: themeController.currentColor.sc3,
|
||||
padding: EdgeInsets.all(10.rpx),
|
||||
borderRadius: 8.rpx,
|
||||
onTap: onRightArrowTap,
|
||||
child: SizedBox(
|
||||
width: 9.rpx,
|
||||
height: 14.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/arrow_right.svg',
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
ClickableContainer(
|
||||
backgroundColor: Colors.transparent,
|
||||
highlightColor: themeController.currentColor.sc3,
|
||||
padding: EdgeInsets.zero,
|
||||
borderRadius: 8,
|
||||
onTap: () {
|
||||
showSleepCalendarBottomSheet(
|
||||
type: sleepReportController.model.type,
|
||||
timestamp: selectedDate.millisecondsSinceEpoch,
|
||||
context: context,
|
||||
onDateSelected: (newDate) {
|
||||
sleepReportController.selectedDate.value = newDate;
|
||||
calendarController.selectedDate.value = newDate;
|
||||
String data =
|
||||
MyUtils.formatDate(calendarController.selectedDate.value!);
|
||||
requestWithLog(
|
||||
logTitle: "查询睡眠报告",
|
||||
method: MyHttpMethod.get,
|
||||
queryUrl:
|
||||
"https://sleepdata.he-info.com/api/analysis/sleep/analysis?mac=${widget.data['mac']}&time=${data}&type=${sleepReportController.model.type}",
|
||||
onSuccess: (res) {
|
||||
print(res);
|
||||
sleepReportController.sleepReport.value = res.data;
|
||||
sleepReportController.updateAll();
|
||||
},
|
||||
onFailure: (res) {
|
||||
TopSlideNotification.show(context,
|
||||
text: res.msg!,
|
||||
textColor: themeController.currentColor.sc9);
|
||||
sleepReportController.sleepReport.value = {};
|
||||
sleepReportController.updateAll();
|
||||
print(res);
|
||||
});
|
||||
sleepReportController.updateAll();
|
||||
calendarController.updateAll();
|
||||
},
|
||||
);
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 28.rpx,
|
||||
height: 28.rpx,
|
||||
child: SvgPicture.asset(
|
||||
'assets/img/icon/calendar.svg',
|
||||
fit: BoxFit.cover,
|
||||
color: themeController.currentColor.sc3,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static String _getEnglishMonthName(int month) {
|
||||
const monthNames = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
];
|
||||
return monthNames[month - 1];
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ 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/common/util/requestWithLog.dart';
|
||||
import 'package:vbvs_app/component/NullDataComponentWidget.dart';
|
||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||
import 'package:vbvs_app/controller/date/CalendarController.dart';
|
||||
@@ -43,8 +42,23 @@ class _NewSleepReportPageState extends State<NewSleepReportPage> {
|
||||
SleepReportController sleepReportController = Get.find();
|
||||
CalendarController calendarController = Get.find();
|
||||
|
||||
final GlobalKey sleepCardKey = GlobalKey();
|
||||
final GlobalKey heartRateCardKey = GlobalKey();
|
||||
final GlobalKey breatheCardKey = GlobalKey();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void didUpdateWidget(NewSleepReportPage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.data['name'] != oldWidget.data['name'] ||
|
||||
widget.data['itemId'] != oldWidget.data['itemId']) {
|
||||
_scrollToTargetComponent(sleepReportController.sleepReport);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
sleepReportController.sleepReport.value = {};
|
||||
if (widget.data['date'] == null) {
|
||||
widget.data['date'] = DateTime.now();
|
||||
}
|
||||
@@ -68,6 +82,7 @@ class _NewSleepReportPageState extends State<NewSleepReportPage> {
|
||||
print(res);
|
||||
sleepReportController.sleepReport.value = res.data;
|
||||
sleepReportController.updateAll();
|
||||
_scrollToTargetComponent(sleepReportController.sleepReport);
|
||||
},
|
||||
onFailure: (res) {
|
||||
TopSlideNotification.show(context,
|
||||
@@ -560,9 +575,11 @@ class _NewSleepReportPageState extends State<NewSleepReportPage> {
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
key: sleepCardKey,
|
||||
width: double.infinity,
|
||||
child: SleepCard(
|
||||
sleepReport: sleepReport,
|
||||
highlightItem: widget.data['itemName'] ?? null,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -605,8 +622,12 @@ class _NewSleepReportPageState extends State<NewSleepReportPage> {
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
key: heartRateCardKey,
|
||||
width: double.infinity,
|
||||
child: HeartRateCard(sleepReport: sleepReport),
|
||||
child: HeartRateCard(
|
||||
sleepReport: sleepReport,
|
||||
highlightItem: widget.data['itemName'] ?? null,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
@@ -630,8 +651,12 @@ class _NewSleepReportPageState extends State<NewSleepReportPage> {
|
||||
padding: EdgeInsetsDirectional.fromSTEB(
|
||||
30.rpx, 0.rpx, 30.rpx, 0),
|
||||
child: Container(
|
||||
key: breatheCardKey,
|
||||
width: double.infinity,
|
||||
child: BreatheCard(sleepReport: sleepReport),
|
||||
child: BreatheCard(
|
||||
sleepReport: sleepReport,
|
||||
highlightItem: widget.data['itemName'] ?? null,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
@@ -935,4 +960,38 @@ class _NewSleepReportPageState extends State<NewSleepReportPage> {
|
||||
];
|
||||
return monthNames[month - 1];
|
||||
}
|
||||
|
||||
void _scrollToTargetComponent(RxMap sleepReport) {
|
||||
if (sleepReport.isEmpty) {
|
||||
return;
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final targetName = widget.data['name'];
|
||||
final targetID = widget.data['itemName'];
|
||||
GlobalKey? targetKey;
|
||||
|
||||
List sleepdata = sleepReport['bs'] ?? []; // 睡眠数据
|
||||
List heartdata = sleepReport['hrs'] ?? []; // 心率数据
|
||||
List breathedata = sleepReport['brs'] ?? []; // 呼吸数据
|
||||
|
||||
if (sleepdata.any((e) => e['id'] == targetID)) {
|
||||
targetKey = sleepCardKey;
|
||||
} else if (heartdata.any((e) => e['id'] == targetID)) {
|
||||
targetKey = heartRateCardKey;
|
||||
} else if (breathedata.any((e) => e['id'] == targetID)) {
|
||||
targetKey = breatheCardKey;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetKey?.currentContext != null) {
|
||||
Scrollable.ensureVisible(
|
||||
targetKey!.currentContext!,
|
||||
duration: Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOut,
|
||||
alignment: 0.1,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user