diff --git a/assets/img/icon/triangle.svg b/assets/img/icon/triangle.svg new file mode 100644 index 0000000..bfd6690 --- /dev/null +++ b/assets/img/icon/triangle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/img/xiaoe.png b/assets/img/xiaoe.png new file mode 100644 index 0000000..4254142 Binary files /dev/null and b/assets/img/xiaoe.png differ diff --git a/assets/langs/en_US.json b/assets/langs/en_US.json index 43ac062..4399497 100644 --- a/assets/langs/en_US.json +++ b/assets/langs/en_US.json @@ -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", diff --git a/assets/langs/zh_CN.json b/assets/langs/zh_CN.json index 5d6ea38..4a86f2f 100644 --- a/assets/langs/zh_CN.json +++ b/assets/langs/zh_CN.json @@ -421,7 +421,8 @@ "心率散点图介绍":"心电散点图是用非线性的图形方法描记的连续心冲击图的RR间期图,因图形由散点组成,又称散点图。", "今日数据":"今日数据", "昨日数据":"昨日数据", - "次":"次", - "秒":"秒" + "次":"频次", + "秒":"秒", + "当前暂无数据":"当前暂无数据" } \ No newline at end of file diff --git a/lib/component/home_page/DynamicReportDetailWidget.dart b/lib/component/home_page/DynamicReportDetailWidget.dart index 54336c8..4cb025f 100644 --- a/lib/component/home_page/DynamicReportDetailWidget.dart +++ b/lib/component/home_page/DynamicReportDetailWidget.dart @@ -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 { 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 { 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( diff --git a/lib/component/home_page/SleepDataModuleWidget.dart b/lib/component/home_page/SleepDataModuleWidget.dart index e3a0505..5414058 100644 --- a/lib/component/home_page/SleepDataModuleWidget.dart +++ b/lib/component/home_page/SleepDataModuleWidget.dart @@ -79,156 +79,181 @@ class _SleepDataModuleWidgetState extends State { 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((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((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 { 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 || diff --git a/lib/controller/device/blueteeth_bind_controller.dart b/lib/controller/device/blueteeth_bind_controller.dart index f7886ec..4b27a5f 100644 --- a/lib/controller/device/blueteeth_bind_controller.dart +++ b/lib/controller/device/blueteeth_bind_controller.dart @@ -71,7 +71,8 @@ class BlueteethBindController extends GetControllerEx { 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信息 diff --git a/lib/controller/weather/weather_controller.dart b/lib/controller/weather/weather_controller.dart index b5d91e3..6fa1397 100644 --- a/lib/controller/weather/weather_controller.dart +++ b/lib/controller/weather/weather_controller.dart @@ -256,6 +256,9 @@ class WeatherModelController extends GetControllerEx { Future _getCurrentLocation() async { try { Position position = await _determinePosition(); + if (position == null) { + throw Exception("获取位置失败"); + } String? language = "zh_CN"; if (languageController.selectLanguage != null) { diff --git a/lib/main.dart b/lib/main.dart index 43503b0..a2be13c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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"); } }); } diff --git a/lib/pages/device/component/DeviceDataComponentWidget.dart b/lib/pages/device/component/DeviceDataComponentWidget.dart index ce61e34..1109440 100644 --- a/lib/pages/device/component/DeviceDataComponentWidget.dart +++ b/lib/pages/device/component/DeviceDataComponentWidget.dart @@ -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 device; @@ -1040,7 +1034,7 @@ class _DeviceDataComponentWidgetState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Expanded( + Expanded( child: CustomCard( borderRadius: AppConstants().button_container_radius, onTap: () async { @@ -1052,11 +1046,17 @@ class _DeviceDataComponentWidgetState extends State { 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 { 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 { } } } - -_showBluetoothNotEnabledDialog() async { - await showDialog( - context: Get.context!, - builder: (_) => AlertDialog( - title: Text("蓝牙未开启"), - content: Text("请先打开蓝牙再进行WIFI配置"), - actions: [ - TextButton( - onPressed: () => Navigator.of(Get.context!).pop(), - child: Text("知道了"), - ), - ], - ), - ); -} diff --git a/lib/pages/device/instant_body_page.dart b/lib/pages/device/instant_body_page.dart index f280997..5f35174 100644 --- a/lib/pages/device/instant_body_page.dart +++ b/lib/pages/device/instant_body_page.dart @@ -325,6 +325,7 @@ class _InstantBodyPageState extends State { ), ].divide(SizedBox(height: 34.rpx)), ), + Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -342,6 +343,8 @@ class _InstantBodyPageState extends State { color: themeController .currentColor.sc3, ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), Text( '${device['person']?['weight'] ?? '未知数据'.tr}kg', @@ -358,6 +361,7 @@ class _InstantBodyPageState extends State { ), ].divide(SizedBox(height: 34.rpx)), ), + ] .divide(SizedBox(width: 33.rpx)) .addToStart(SizedBox(width: 37.rpx)), diff --git a/lib/pages/device_bind/blueteeth_device_page.dart b/lib/pages/device_bind/blueteeth_device_page.dart index ab7f719..3e95083 100644 --- a/lib/pages/device_bind/blueteeth_device_page.dart +++ b/lib/pages/device_bind/blueteeth_device_page.dart @@ -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 { } _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("知道了"), + // ), + // ], + // ), + // ); } } diff --git a/lib/pages/device_bind/componnet/bind_dialog.dart b/lib/pages/device_bind/componnet/bind_dialog.dart index 786020d..07f4e8d 100644 --- a/lib/pages/device_bind/componnet/bind_dialog.dart +++ b/lib/pages/device_bind/componnet/bind_dialog.dart @@ -860,7 +860,7 @@ void showWifiDialog( ); } -void showTipDialog( +String showTipDialog( BuildContext context, Widget widget, { Color? backgroundColor, // 新增可选参数 @@ -990,4 +990,5 @@ void showTipDialog( ); }, ); + return ""; } diff --git a/lib/pages/device_bind/wifi_page.dart b/lib/pages/device_bind/wifi_page.dart index ef96d7d..e4b6a82 100644 --- a/lib/pages/device_bind/wifi_page.dart +++ b/lib/pages/device_bind/wifi_page.dart @@ -62,6 +62,7 @@ class _WifiPageState extends State { // textColor: themeController.currentColor.sc2, // ); // }); + blueteethBindController.blueConnectFlag.value = 2; blueteethBindController.currentDevice = bledevice; if (lisObj != null) { lisObj!.cancel(); @@ -116,6 +117,8 @@ class _WifiPageState extends State { } }); } else { + blueteethBindController.blueConnectFlag.value = 0; + blueteethBindController.updateAll(); dealWifi(widget.type['mac']).then((aa) { print("object"); }); @@ -175,21 +178,50 @@ class _WifiPageState extends State { SizedBox( width: 14.rpx, ), - Obx(() { - if (blueteethBindController.connectStatus.value == - 0) { - return SizedBox( - width: 24.rpx, - height: 24.rpx, - child: CircularProgressIndicator( - strokeWidth: 1, - valueColor: - AlwaysStoppedAnimation(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( + 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( + 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( + Colors.white), + ), + ); + } + return Container(); + }), ], ), @@ -717,6 +749,20 @@ class _WifiPageState extends State { 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 { 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 { var res2 = bledevice.isConnected; if (res2) { // Navigator.pop(context); + blueteethBindController.blueConnectFlag.value = 2; TopSlideNotification.show( context, text: "蓝牙绑定.连接成功".tr, diff --git a/lib/pages/main_bottom/home_page.dart b/lib/pages/main_bottom/home_page.dart index 5c32355..e60b380 100644 --- a/lib/pages/main_bottom/home_page.dart +++ b/lib/pages/main_bottom/home_page.dart @@ -1013,7 +1013,7 @@ class _HomePageState extends State { (device) => device['mac'] == mac, ); List stateModule = []; - + String currentTime = ""; return DynamicReportDetailWidget( targetDevice: targetDevice!, sleepDateWidgets: List.generate( @@ -1030,6 +1030,7 @@ class _HomePageState extends State { dayData['selected'] == true && dayData['state'] != null) { stateModule = dayData['state']; + currentTime = dayData['time']; } return SleepDateWidget( mac: mac, @@ -1053,9 +1054,13 @@ class _HomePageState extends State { 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], + ); + }, ) : [], ); diff --git a/lib/pages/main_bottom/mine_page.dart b/lib/pages/main_bottom/mine_page.dart index 916074d..5e0a95d 100644 --- a/lib/pages/main_bottom/mine_page.dart +++ b/lib/pages/main_bottom/mine_page.dart @@ -693,7 +693,7 @@ class _MinePageState extends State { mainAxisSize: MainAxisSize.max, children: [ Text( - 'V1.0.2505.28', + 'V1.0.2505.29', style: FlutterFlowTheme.of(context) .bodyMedium diff --git a/lib/pages/sleep_report/chart/FatigueCircleIndicator.dart b/lib/pages/sleep_report/chart/FatigueCircleIndicator.dart index 73778b5..b713d07 100644 --- a/lib/pages/sleep_report/chart/FatigueCircleIndicator.dart +++ b/lib/pages/sleep_report/chart/FatigueCircleIndicator.dart @@ -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, ), ), ], diff --git a/lib/pages/sleep_report/chart/LineChartByRange.dart b/lib/pages/sleep_report/chart/LineChartByRange.dart index 024e608..37ffa44 100644 --- a/lib/pages/sleep_report/chart/LineChartByRange.dart +++ b/lib/pages/sleep_report/chart/LineChartByRange.dart @@ -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> 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 createState() => _LineChartByRangeState(); +// } + +// class _LineChartByRangeState extends State { +// Offset? selectedOffset; +// Map? 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> 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> 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 createState() => _LineChartByRangeState(); +} + +class _LineChartByRangeState extends State { + Offset? selectedOffset; + Map? 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> 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)); } } diff --git a/lib/pages/sleep_report/chart/SnoreChart.dart b/lib/pages/sleep_report/chart/SnoreChart.dart new file mode 100644 index 0000000..f6295ce --- /dev/null +++ b/lib/pages/sleep_report/chart/SnoreChart.dart @@ -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 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 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; + } + } +} diff --git a/lib/pages/sleep_report/chart/SnoreWaveform.dart b/lib/pages/sleep_report/chart/SnoreWaveform.dart index 8320ea6..999e08f 100644 --- a/lib/pages/sleep_report/chart/SnoreWaveform.dart +++ b/lib/pages/sleep_report/chart/SnoreWaveform.dart @@ -164,6 +164,114 @@ class SnoreWaveform extends StatelessWidget { } } +// class SnoreWaveformPainter extends CustomPainter { +// final List 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 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(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), ); } } diff --git a/lib/pages/sleep_report/component/AIAdviceWidget.dart b/lib/pages/sleep_report/component/AIAdviceWidget.dart index 1e5d83d..4aef03d 100644 --- a/lib/pages/sleep_report/component/AIAdviceWidget.dart +++ b/lib/pages/sleep_report/component/AIAdviceWidget.dart @@ -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 { @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((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((advice) { + return AdviceComponnetWidget( + title: advice["q"], + description: advice["s"], + ).paddingOnly(bottom: 0.rpx); // 在每个组件下方添加间隔 + }).toList(), + ), + ) + ], + ), ), - ), - ); + ); + } catch (e) { + es.EasyDartModule.logger.error("打鼾监测绘制异常${e}"); + return Container(); + } } } diff --git a/lib/pages/sleep_report/component/BreathPauseWidget.dart b/lib/pages/sleep_report/component/BreathPauseWidget.dart index 6231075..03a6b9d 100644 --- a/lib/pages/sleep_report/component/BreathPauseWidget.dart +++ b/lib/pages/sleep_report/component/BreathPauseWidget.dart @@ -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 { @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> convertAspData(List data) { diff --git a/lib/pages/sleep_report/component/BreatheCard.dart b/lib/pages/sleep_report/component/BreatheCard.dart index cdd6144..5b640f2 100644 --- a/lib/pages/sleep_report/component/BreatheCard.dart +++ b/lib/pages/sleep_report/component/BreatheCard.dart @@ -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 createState() => _BreatheCardState(); } -class _BreatheCardState extends State { - @override - void setState(VoidCallback callback) { - super.setState(callback); - } +class _BreatheCardState extends State + 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, - // ), - ); - }), - ), - ), - ); } } diff --git a/lib/pages/sleep_report/component/BreathePauseNewWidget.dart b/lib/pages/sleep_report/component/BreathePauseNewWidget.dart index abc8f07..6c3d597 100644 --- a/lib/pages/sleep_report/component/BreathePauseNewWidget.dart +++ b/lib/pages/sleep_report/component/BreathePauseNewWidget.dart @@ -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 { @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> data = - (widget.sleepReport['asp'] as List).cast>(); - List> 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().firstWhere( + (element) => element['id'] == 302, + orElse: () => {}, + ); + + 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> data = + (widget.sleepReport['asp'] as List).cast>(); + List> 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> convertToShowLabel( diff --git a/lib/pages/sleep_report/component/BreatheStandardWidget.dart b/lib/pages/sleep_report/component/BreatheStandardWidget.dart index 2eaa3c0..32ffadb 100644 --- a/lib/pages/sleep_report/component/BreatheStandardWidget.dart +++ b/lib/pages/sleep_report/component/BreatheStandardWidget.dart @@ -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 { @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> data = - (widget.sleepReport['brbc'] as List).cast>(); - // 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> data = + (widget.sleepReport['brbc'] as List).cast>(); + 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> brs = - (widget.sleepReport['brs'] as List).cast>(); - //307 平均呼吸 - //305 基准呼吸 - //308 最低呼吸 - //309 最高呼吸 - // 307 平均呼吸 - Map? avgBreath = brs.firstWhere( - (element) => element['id'] == 307, - orElse: () => {}, - ); + List> brs = + (widget.sleepReport['brs'] as List).cast>(); + //307 平均呼吸 + //305 基准呼吸 + //308 最低呼吸 + //309 最高呼吸 + // 307 平均呼吸 + Map? avgBreath = brs.firstWhere( + (element) => element['id'] == 307, + orElse: () => {}, + ); // 305 基准呼吸 - Map? baseBreath = brs.firstWhere( - (element) => element['id'] == 305, - orElse: () => {}, - ); + Map? baseBreath = brs.firstWhere( + (element) => element['id'] == 305, + orElse: () => {}, + ); // 308 最低呼吸 - Map? minBreath = brs.firstWhere( - (element) => element['id'] == 308, - orElse: () => {}, - ); + Map? minBreath = brs.firstWhere( + (element) => element['id'] == 308, + orElse: () => {}, + ); // 309 最高呼吸 - Map? maxBreath = brs.firstWhere( - (element) => element['id'] == 309, - orElse: () => {}, - ); + Map? 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 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 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(); + } } } diff --git a/lib/pages/sleep_report/component/CompareSleepWidget.dart b/lib/pages/sleep_report/component/CompareSleepWidget.dart index 455ac6d..f53aa91 100644 --- a/lib/pages/sleep_report/component/CompareSleepWidget.dart +++ b/lib/pages/sleep_report/component/CompareSleepWidget.dart @@ -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 { @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> data = (widget.sleepReport['yc'] as List) .map((e) => e as Map) - .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 today = {}; Map yesterday = {}; @@ -210,5 +197,10 @@ class _CompareSleepWidgetState extends State { ), ), ); + + } catch (e) { + es.EasyDartModule.logger.error("打鼾监测绘制异常${e}"); + return Container(); + } } } diff --git a/lib/pages/sleep_report/component/DiseasePercentsWidget.dart b/lib/pages/sleep_report/component/DiseasePercentsWidget.dart index d8ac881..1f51c0c 100644 --- a/lib/pages/sleep_report/component/DiseasePercentsWidget.dart +++ b/lib/pages/sleep_report/component/DiseasePercentsWidget.dart @@ -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 { - // 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 { @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 { ), ), ); + + } catch (e) { + es.EasyDartModule.logger.error("打鼾监测绘制异常${e}"); + return Container(); + } } List> convertDiseaseData(List data) { diff --git a/lib/pages/sleep_report/component/HeartChangeWidget.dart b/lib/pages/sleep_report/component/HeartChangeWidget.dart index 57a0423..7e4b92c 100644 --- a/lib/pages/sleep_report/component/HeartChangeWidget.dart +++ b/lib/pages/sleep_report/component/HeartChangeWidget.dart @@ -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 { @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> 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((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> 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((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> transformHrvData(List originalData) { diff --git a/lib/pages/sleep_report/component/HeartHealthWidget.dart b/lib/pages/sleep_report/component/HeartHealthWidget.dart index 8e2b42c..59cbda0 100644 --- a/lib/pages/sleep_report/component/HeartHealthWidget.dart +++ b/lib/pages/sleep_report/component/HeartHealthWidget.dart @@ -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 { @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> convertMentalHealthData(List data) { @@ -136,7 +143,7 @@ class _HeartHealthWidgetState extends State { 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 { ? stringToColor(colorStr) : stringToColor("#00C1AA"), // 默认红色 'percent': value, - 'explain': explain, + 'explain': item['level'], "bottomColor": Colors.grey, }; }).toList(); diff --git a/lib/pages/sleep_report/component/HeartPointWidget.dart b/lib/pages/sleep_report/component/HeartPointWidget.dart index f381056..64e620f 100644 --- a/lib/pages/sleep_report/component/HeartPointWidget.dart +++ b/lib/pages/sleep_report/component/HeartPointWidget.dart @@ -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 { @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 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 data = []; - List rawData = widget.sleepReport['hrsp']; try { - data = rawData.map((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 data = []; + List rawData = widget.sleepReport['hrsp']; + try { + data = rawData.map((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(); + } } } diff --git a/lib/pages/sleep_report/component/HeartRateCard.dart b/lib/pages/sleep_report/component/HeartRateCard.dart index 753190e..2da6d13 100644 --- a/lib/pages/sleep_report/component/HeartRateCard.dart +++ b/lib/pages/sleep_report/component/HeartRateCard.dart @@ -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 createState() => _HeartRateCardState(); } -class _HeartRateCardState extends State { - @override - void setState(VoidCallback callback) { - super.setState(callback); - } +class _HeartRateCardState extends State 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, - // ), - ); - }), - ), - ), - ); } -} +} \ No newline at end of file diff --git a/lib/pages/sleep_report/component/HeartRateStandardWidget.dart b/lib/pages/sleep_report/component/HeartRateStandardWidget.dart index daa38d1..159fc8a 100644 --- a/lib/pages/sleep_report/component/HeartRateStandardWidget.dart +++ b/lib/pages/sleep_report/component/HeartRateStandardWidget.dart @@ -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 { @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> data = - (widget.sleepReport['hrbc'] as List).cast>(); - 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> data = + (widget.sleepReport['hrbc'] as List).cast>(); + 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> hrs = - (widget.sleepReport['hrs'] as List).cast>(); - //206 平均心率 - //202 基准心率 - //207 最低心率 - //208 最高心率 - // 找 id == 206 的元素(平均心率) - Map? avgHeartRate = hrs.firstWhere( - (element) => element['id'] == 206, - orElse: () => {}, - ); + List> hrs = + (widget.sleepReport['hrs'] as List).cast>(); + //206 平均心率 + //202 基准心率 + //207 最低心率 + //208 最高心率 + // 找 id == 206 的元素(平均心率) + Map? avgHeartRate = hrs.firstWhere( + (element) => element['id'] == 206, + orElse: () => {}, + ); // 找 id == 202 的元素(基准心率) - Map? baseHeartRate = hrs.firstWhere( - (element) => element['id'] == 202, - orElse: () => {}, - ); + Map? baseHeartRate = hrs.firstWhere( + (element) => element['id'] == 202, + orElse: () => {}, + ); // 找 id == 207 的元素(最低心率) - Map? minHeartRate = hrs.firstWhere( - (element) => element['id'] == 207, - orElse: () => {}, - ); + Map? minHeartRate = hrs.firstWhere( + (element) => element['id'] == 207, + orElse: () => {}, + ); // 找 id == 208 的元素(最高心率) - Map? maxHeartRate = hrs.firstWhere( - (element) => element['id'] == 208, - orElse: () => {}, - ); - String range = baseHeartRate['range'] ?? ''; - int min = 0; - int max = 0; + Map? maxHeartRate = hrs.firstWhere( + (element) => element['id'] == 208, + orElse: () => {}, + ); + String range = baseHeartRate['range'] ?? ''; + int min = 0; + int max = 0; - if (range.isNotEmpty && range.contains('~')) { - List 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 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(); + } } } diff --git a/lib/pages/sleep_report/component/HrvWidget.dart b/lib/pages/sleep_report/component/HrvWidget.dart index 338768f..020f893 100644 --- a/lib/pages/sleep_report/component/HrvWidget.dart +++ b/lib/pages/sleep_report/component/HrvWidget.dart @@ -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 { @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(); + } } } diff --git a/lib/pages/sleep_report/component/SkinPercentWidget.dart b/lib/pages/sleep_report/component/SkinPercentWidget.dart index 1196741..f7b3fee 100644 --- a/lib/pages/sleep_report/component/SkinPercentWidget.dart +++ b/lib/pages/sleep_report/component/SkinPercentWidget.dart @@ -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 { @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 { ), ), ); + + } catch (e) { + es.EasyDartModule.logger.error("打鼾监测绘制异常${e}"); + return Container(); + } } } diff --git a/lib/pages/sleep_report/component/SleepCard.dart b/lib/pages/sleep_report/component/SleepCard.dart index 1dc905b..05bff0a 100644 --- a/lib/pages/sleep_report/component/SleepCard.dart +++ b/lib/pages/sleep_report/component/SleepCard.dart @@ -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 createState() => _SleepCardState(); } -class _SleepCardState extends State { - @override - void setState(VoidCallback callback) { - super.setState(callback); - } +class _SleepCardState extends State 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, - // ), - ); - }), - ), - ), - ); } -} +} \ No newline at end of file diff --git a/lib/pages/sleep_report/component/SleepScoreWidget.dart b/lib/pages/sleep_report/component/SleepScoreWidget.dart index dbf9d6c..32c4479 100644 --- a/lib/pages/sleep_report/component/SleepScoreWidget.dart +++ b/lib/pages/sleep_report/component/SleepScoreWidget.dart @@ -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 { @override Widget build(BuildContext context) { - if (widget.sleepReport == null || + try { + if (widget.sleepReport == null || widget.sleepReport is! Map || widget.sleepReport.isEmpty) { return Container(); } - // List> 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 segments = parseSegments(widget.sleepReport); @@ -191,6 +188,11 @@ class _SleepScoreWidgetState extends State { ), ), ); + + } catch (e) { + es.EasyDartModule.logger.error("打鼾监测绘制异常${e}"); + return Container(); + } } //获取睡眠等级 diff --git a/lib/pages/sleep_report/component/SleepView.dart b/lib/pages/sleep_report/component/SleepView.dart index 6b97e9b..c71e906 100644 --- a/lib/pages/sleep_report/component/SleepView.dart +++ b/lib/pages/sleep_report/component/SleepView.dart @@ -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 { @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 { ), ], ), - ), - // 右侧 - 起床时间图标 - 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((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((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> 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(); + } } } diff --git a/lib/pages/sleep_report/component/SnoreViewWidget.dart b/lib/pages/sleep_report/component/SnoreViewWidget.dart index 3943370..e38d2ab 100644 --- a/lib/pages/sleep_report/component/SnoreViewWidget.dart +++ b/lib/pages/sleep_report/component/SnoreViewWidget.dart @@ -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 { @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> data = - (widget.sleepReport['ssp'] as List).cast>(); - List> showLabel = convertToShowLabel(data); - // List> 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 processedLightSnore = lightSnore.map((item) { + return { + ...item, + 'id': type[0]['id'], + 'name': type[0]['name'], + 'color': type[0]['color'], + }; + }).toList(); + + List 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 barDataList = snoreValues.map((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> data = + // (widget.sleepReport['ssp'] as List).cast>(); + // List> 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> convertToShowLabel( diff --git a/lib/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart b/lib/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart index 59d652c..3815908 100644 --- a/lib/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart +++ b/lib/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart @@ -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> showLabel = []; - if (target != null && target['type'] is List) { - showLabel = (target['type'] as List).map>((item) { - return { - 'key': item['type'], - 'name': item['name'], - 'color': stringToColor(item['color']), - }; - }).toList(); - } + if (target == null) { + return Container(); + } + List> showLabel = []; + if (target != null && target['type'] is List) { + showLabel = (target['type'] as List).map>((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(); + } } } diff --git a/lib/pages/sleep_report/new_sleep_report_page copy.dart b/lib/pages/sleep_report/new_sleep_report_page copy.dart new file mode 100644 index 0000000..ae490df --- /dev/null +++ b/lib/pages/sleep_report/new_sleep_report_page copy.dart @@ -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 createState() => _NewSleepReportPageState(); +} + +class _NewSleepReportPageState extends State { + 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]; + } +} diff --git a/lib/pages/sleep_report/new_sleep_report_page.dart b/lib/pages/sleep_report/new_sleep_report_page.dart index 3e53689..353d50c 100644 --- a/lib/pages/sleep_report/new_sleep_report_page.dart +++ b/lib/pages/sleep_report/new_sleep_report_page.dart @@ -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 { 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 { 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 { 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 { 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 { 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 { ]; 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, + ); + } + }); + } }