diff --git a/assets/miniapp/mhtControl_1.0.94.zip b/assets/miniapp/mhtControl_1.0.94.zip index ce0746e..ec60a25 100644 Binary files a/assets/miniapp/mhtControl_1.0.94.zip and b/assets/miniapp/mhtControl_1.0.94.zip differ diff --git a/lib/common/color/appConstants.dart b/lib/common/color/appConstants.dart index ce0abe0..ffe3f2b 100644 --- a/lib/common/color/appConstants.dart +++ b/lib/common/color/appConstants.dart @@ -8,8 +8,8 @@ import 'package:vbvs_app/enum/APPPackageType.dart'; class AppConstants { // App-related constants - static const String zhmht_app_version = "SWES_1.2026.3.30"; //眠花糖 - static const String theh_app_version = "1.2603.30"; //太和 + static const String zhmht_app_version = "SWES_1.2026.04.02"; //眠花糖 + static const String theh_app_version = "1.2604.02"; //太和 // 1. 纯字符串列表格式 static const List integerTimeZones = [ @@ -91,8 +91,8 @@ class AppConstants { //系统参数 //运行打包APP模式 - // int ent_type = APPPackageType.MHT.code; //1.默认太和 2.欢睡 3.眠花糖 - int ent_type = APPPackageType.TH.code; //1.默认太和 2.欢睡 3.眠花糖 + int ent_type = APPPackageType.MHT.code; //1.默认太和 2.欢睡 3.眠花糖 + // int ent_type = APPPackageType.TH.code; //1.默认太和 2.欢睡 3.眠花糖 // int ent_type = APPPackageType.HUANSHUI.code; //1.默认太和 2.欢睡 3.眠花糖 // int ent_type = APPPackageType.DONGHUA.code; //1.默认太和 2.欢睡 3.眠花糖 4.东华 // int ent_type = APPPackageType.HAIER.code; //1.默认太和 2.欢睡 3.眠花糖 4.东华 5.海尔沃棣 diff --git a/lib/controller/device/device_type_controller.dart b/lib/controller/device/device_type_controller.dart index b564267..d092c94 100644 --- a/lib/controller/device/device_type_controller.dart +++ b/lib/controller/device/device_type_controller.dart @@ -106,27 +106,31 @@ class DeviceTypeController extends GetControllerEx { } Future checkReportStatus(String mac) async { - String serviceAddress = ServiceConstant.qc_service_address; - String serviceApi = ServiceConstant.checkQuickStatus; - String queryUrl = "$serviceAddress$serviceApi?mac=${mac}"; bool flag = false; - await requestWithLog( - logTitle: "查询快检状态", - method: MyHttpMethod.get, - queryUrl: queryUrl, - onSuccess: (res) { - flag = res.data['status'] != 200; - experience_status.value = res.data['status']; - experience_percent.value = res.data['per'] ?? 0; - experience_id.value = res.data['id'] ?? ""; - own.value = res.data['own'] ?? false; - updateAll(); - }, - onFailure: (res) { - flag = false; - experience_status.value = -1; - }, - ); + try { + String serviceAddress = ServiceConstant.qc_service_address; + String serviceApi = ServiceConstant.checkQuickStatus; + String queryUrl = "$serviceAddress$serviceApi?mac=${mac}"; + await requestWithLog( + logTitle: "查询快检状态", + method: MyHttpMethod.get, + queryUrl: queryUrl, + onSuccess: (res) { + flag = res.data['status'] != 200; + experience_status.value = res.data['status']; + experience_percent.value = res.data['per'] ?? 0; + experience_id.value = res.data['id'] ?? ""; + own.value = res.data['own'] ?? false; + updateAll(); + }, + onFailure: (res) { + flag = false; + experience_status.value = -1; + }, + ); + } catch (e) { + print("查询快检状态出错: $e"); + } return flag; } @@ -299,6 +303,9 @@ class DeviceTypeController extends GetControllerEx { flag = true; }, onFailure: (res) { + NewTopSlideNotification.show( + text:res.msg ?? "请求失败".tr, + textColor: themeController.currentColor.sc9); flag = false; }, ); diff --git a/lib/main.dart b/lib/main.dart index 5e739d9..cb823c6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -698,7 +698,7 @@ void initEasyDartModule() { EasyDartModule.init( loggerConfig: LoggerConfig( host: "https://zhmht.swes.com.cn:27020/vsbs_log", - serviceName: "太和e护test2026-03-30"), + serviceName: "太和e护test2026-04-01"), webSocketConfig: WebSocketConfig(ServiceConstant.webSocketService, (data) { // 接收到服务消息 diff --git a/lib/pages/device/component/SpeedControlledGif.dart b/lib/pages/device/component/SpeedControlledGif.dart new file mode 100644 index 0000000..66ca937 --- /dev/null +++ b/lib/pages/device/component/SpeedControlledGif.dart @@ -0,0 +1,92 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'dart:ui' as ui; + +import 'package:flutter/services.dart'; + +class SpeedControlledGif extends StatefulWidget { + final String assetPath; + final double speedFactor; + final BoxFit? fit; + + /// [speedFactor] 播放速度倍数,默认1.0, + /// 大于1表示加速播放,小于1表示减速播放 + const SpeedControlledGif( + this.assetPath, { + Key? key, + this.speedFactor = 1.0, + this.fit, + }) : super(key: key); + + @override + _SpeedControlledGifState createState() => _SpeedControlledGifState(); +} + +class _SpeedControlledGifState extends State { + ui.Codec? _codec; + ui.FrameInfo? _currentFrame; + Timer? _timer; + bool _isDisposed = false; + + @override + void initState() { + super.initState(); + _loadGif(); + } + + Future _loadGif() async { + final data = await rootBundle.load(widget.assetPath); + _codec = await ui.instantiateImageCodec(data.buffer.asUint8List()); + + if (_isDisposed) return; + + _showNextFrame(); + } + + Future _showNextFrame() async { + if (_isDisposed || _codec == null) return; + + _currentFrame = await _codec!.getNextFrame(); + + if (_isDisposed) return; + + if (mounted) { + setState(() {}); + } + + // 取当前帧持续时间,单位毫秒 + final baseDuration = _currentFrame?.duration.inMilliseconds ?? 100; + + // 限制最小帧间隔,防止刷新过快 + const minFrameDuration = 50; + + // 计算实际播放帧间隔,speedFactor越大速度越快 + final adjustedDuration = (baseDuration / widget.speedFactor) + .round() + .clamp(minFrameDuration, 10000); + + _timer = Timer(Duration(milliseconds: adjustedDuration), _showNextFrame); + } + + @override + void dispose() { + _isDisposed = true; + _timer?.cancel(); + _codec?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_currentFrame == null) { + // 加载中或无帧时显示空容器或占位 + return Container(); + } + return RawImage( + image: _currentFrame!.image, + fit: widget.fit, + ); + } +} diff --git a/lib/pages/device/component/health_experience_tool.dart b/lib/pages/device/component/health_experience_tool.dart new file mode 100644 index 0000000..ac3eb41 --- /dev/null +++ b/lib/pages/device/component/health_experience_tool.dart @@ -0,0 +1,63 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:vbvs_app/controller/device/device_type_controller.dart'; +import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; + +DeviceTypeController deviceTypeController = Get.find(); +String getGenderText(dynamic gender) { + var genderMap = { + '1': '男'.tr, + '2': '女'.tr, + }; + + String genderStr = gender.toString().trim(); + return genderMap[genderStr] ?? '-'.tr; +} + +// 显示解绑确认对话框 +void showCancelConfirmDialog(BuildContext context, personInfo) { + showConfirmDialog( + context, + Container(), + "是否确认结束?".tr, + onConfirm: () async { + bool opRes = await deviceTypeController.qcCheckControl(personInfo, 2); + if (!opRes) { + return; + } + deviceTypeController.experience_status.value = 404; + deviceTypeController.updateAll(); + }, + onCancel: () {}, + ); +} + + + String calculateAge(String birthdayStr) { + try { + // 解析生日字符串 (格式: yyyy/MM/dd) + List parts = birthdayStr.trim().split('/'); + if (parts.length != 3) return '-'.tr; + + int year = int.parse(parts[0]); + int month = int.parse(parts[1]); + int day = int.parse(parts[2]); + + DateTime birthDate = DateTime(year, month, day); + DateTime today = DateTime.now(); + + // 计算年龄 + int age = today.year - birthDate.year; + + // 如果今年还没过生日,年龄减1 + if (today.month < birthDate.month || + (today.month == birthDate.month && today.day < birthDate.day)) { + age--; + } + + return age.toString(); + } catch (e) { + return '-'.tr; + } + } diff --git a/lib/pages/device/health_experience.dart b/lib/pages/device/health_experience.dart index 8ab5bd6..ebecf01 100644 --- a/lib/pages/device/health_experience.dart +++ b/lib/pages/device/health_experience.dart @@ -1,3 +1,1271 @@ +// import 'dart:async'; +// import 'dart:ui' as ui; + +// import 'package:EasyDartModule/EasyDartModule.dart' as edm; +// import 'package:ef/ef.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter/services.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/CommonVariables.dart'; +// import 'package:vbvs_app/common/util/DailyLogUtils.dart'; +// import 'package:vbvs_app/common/util/FitTool.dart'; +// import 'package:vbvs_app/common/util/MyUtils.dart'; +// import 'package:vbvs_app/component/tool/ClickableContainer.dart'; +// import 'package:vbvs_app/component/tool/CustomCard.dart'; +// import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart'; +// import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; +// import 'package:vbvs_app/controller/device/device_type_controller.dart'; +// import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; +// import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; +// import 'package:vbvs_app/controller/user_info_controller.dart'; +// import 'package:vbvs_app/enum/APPQuickCheckStatus.dart'; +// import 'package:vbvs_app/model/WebSocketMessage.dart'; +// import 'package:vbvs_app/pages/device/component/DeviceStatusInfoWidget.dart'; +// import 'package:vbvs_app/pages/device/component/health_experience_tool.dart'; +// import 'package:vbvs_app/pages/device_bind/componnet/CalibrationProgressWidget.dart'; +// import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; +// import 'package:vibration/vibration.dart'; + +// import 'component/SpeedControlledGif.dart'; + +// class HealthCheckPage extends StatefulWidget { +// var personInfo; +// HealthCheckPage({super.key, required this.personInfo}); + +// @override +// State createState() => _HealthCheckPageState(); +// } + +// class _HealthCheckPageState extends State +// with WidgetsBindingObserver { +// GlobalController globalController = Get.find(); +// UserInfoController userInfoController = Get.find(); +// BlueteethBindController blueteethBindController = Get.find(); +// ThemeController themeController = Get.find(); +// DeviceTypeController deviceTypeController = Get.find(); + +// int maxBodyMotion = 1; +// String breathState = "-"; +// String inBed = "-"; +// String onlineState = "离线".tr; +// Timer? _onlineTimer; // 添加 Timer 引用 +// int bodyMotion = -1; +// int breathrate = -1; +// String snores = "-"; +// int heartrate = -1; + +// final ValueNotifier progressNotifier = ValueNotifier(0.0); +// final ValueNotifier failureNotifier = ValueNotifier(false); + +// Timer? _checkStatusTimer; // 添加状态查询定时器 +// bool _isInitialized = false; // 添加初始化标志 + +// @override +// void initState() { +// WidgetsBinding.instance.addObserver(this); // 添加生命周期观察者 +// try { +// deviceTypeController +// .checkReportStatus(widget.personInfo['mac']) +// .then((_) { +// setState(() { +// _isInitialized = true; +// }); +// // 如果当前状态是体验中,启动定时器 +// if (deviceTypeController.experience_status.value == 200 && +// deviceTypeController.own.value) { +// _startCheckStatusTimer(); +// } +// }); +// } catch (e) { +// ef.log("快检初始化数据失败"); +// } + +// _initWebSocket(); + +// super.initState(); +// } + +// Future _initWebSocket() async { +// // 发送WebSocket请求 +// try { +// Future.delayed(Duration(seconds: 0), () { +// CommonVariables.callMap["/vsbs/web/rt/marttress"] = (data) { +// edm.EasyDartModule.logger.info("[websocket]实时体征页面数据-->${data}]"); +// ef.log("[websocket]实时体征页面数据-->${data}]"); +// if (data['status'] == "离线") { +// inBed = "-"; +// bodyMotion = -1; +// heartrate = -1; +// snores = "-"; +// breathrate = -1; +// breathState = "-"; +// onlineState = "离线".tr; +// return; +// } +// inBed = data["inBed"]; +// // 心率 呼吸 体动 呼吸暂停 +// if ("离床".tr == inBed) { +// breathState = "否".tr; +// bodyMotion = 0; +// breathrate = 0; +// heartrate = 0; +// snores = "否".tr; +// } else { +// onlineState = "在线".tr; // 接收到数据,设置为在线 +// breathState = +// data["breathState"] == null || data["breathState"] == "" +// ? "-" +// : data["breathState"].toString().tr; + +// bodyMotion = data['bodyMotion'] == null ? -1 : data['bodyMotion']; +// breathrate = data["breathRate"] == null ? -1 : data["breathRate"]; +// heartrate = data['heartRate'] == null ? -1 : data['heartRate']; +// snores = data['snores'] == null || +// data['snores'] == "" || +// data['snores'] == "否".tr +// ? "否".tr +// : "${data['snores']}".tr; +// } + +// if (mounted) { +// setState(() { +// onlineState = "在线".tr; // 接收到数据,设置为在线 +// }); +// } +// _startOnlineTimer(); // 重置定时器 +// }; +// }); +// } catch (e) { +// print(e); +// edm.EasyDartModule.logger +// .error("[webscoekt]格式化数据错误-->${{"mac": widget.personInfo['mac']}}"); +// } +// if (widget.personInfo['status'] != null) { +// try { +// onlineState = +// widget.personInfo['status']['status'] == 1 ? "在线".tr : "离线".tr; +// if (widget.personInfo['status']['status'] != 0) { +// inBed = widget.personInfo['status']['inBed'] == 1 ? "在床".tr : "离床".tr; +// } +// } catch (e) { +// edm.EasyDartModule.logger +// .error("[webscoekt]格式化数据错误-->${{"mac": widget.personInfo['mac']}}"); +// } +// } +// edm.EasyDartModule.logger +// .info("[webscoekt]发送请求:数据-->${{"mac": widget.personInfo['mac']}}"); +// DailyLogUtils.writeLog( +// "[webscoekt]发送请求:数据-->${{"mac": widget.personInfo['mac']}}"); +// edm.EasyDartModule.websocket.sendData(jsonEncode(WebSocketMessage( +// path: "/vsbs/web/rt/marttress", +// type: 1, +// data: {"mac": widget.personInfo['mac']}))); +// await Future.delayed(Duration(seconds: 3)); +// edm.EasyDartModule.websocket.sendData(jsonEncode(WebSocketMessage( +// path: "/vsbs/web/rt/marttress", +// type: 1, +// data: {"mac": widget.personInfo['mac']}))); +// _startOnlineTimer(); +// } +// //y + +// @override +// void dispose() { +// WidgetsBinding.instance.removeObserver(this); // 移除生命周期观察者 +// _onlineTimer?.cancel(); +// _checkStatusTimer?.cancel(); // 取消状态查询定时器 +// _closeWebSocket(); +// CommonVariables.callMap.remove("/vsbs/web/rt/marttress"); +// super.dispose(); +// } + +// // 监听应用生命周期变化 +// @override +// void didChangeAppLifecycleState(AppLifecycleState state) { +// if (state == AppLifecycleState.resumed) { +// // 应用回到前台时重新连接WebSocket +// edm.EasyDartModule.logger.info("app切回页面,重连websocket"); +// _initWebSocket(); + +// _initWebSocket(); +// // 重新查询状态,然后根据状态决定是否启动定时器 +// deviceTypeController +// .checkReportStatus(widget.personInfo['mac']) +// .then((_) { +// // 如果当前状态是体验中,重新启动定时器 +// if (deviceTypeController.experience_status.value != 404 && +// deviceTypeController.own.value) { +// _startCheckStatusTimer(); +// } +// }); +// } else if (state == AppLifecycleState.paused) { +// // 应用进入后台时关闭WebSocket和定时器 +// _closeWebSocket(); +// _checkStatusTimer?.cancel(); +// } +// } + +// void _startOnlineTimer() { +// _onlineTimer?.cancel(); // 取消之前的定时器 +// _onlineTimer = Timer.periodic(Duration(seconds: 60), (timer) { +// if (mounted) { +// setState(() { +// edm.EasyDartModule.logger.info("60 秒内没有接收到数据,设置为离线"); +// onlineState = "离线".tr; // 30 秒内没有接收到数据,设置为离线 +// inBed = "-"; +// bodyMotion = -1; +// heartrate = -1; +// snores = "-"; +// breathrate = -1; +// breathState = "-"; +// }); +// } +// }); +// } + +// @override +// Widget build(BuildContext context) { +// Map device = widget.personInfo; + +// return LayoutBuilder( +// builder: (context, bodySize) => GestureDetector( +// // onTap: () => FocusScope.of(context).unfocus(),, +// child: Container( +// decoration: BoxDecoration( +// image: DecorationImage( +// image: AssetImage(getBackgroundImageNoImage()), +// fit: BoxFit.fill, +// ), +// ), +// child: Scaffold( +// backgroundColor: Colors.transparent, +// appBar: AppBar( +// backgroundColor: themeController.currentColor.sc17, +// automaticallyImplyLeading: false, +// iconTheme: IconThemeData(color: themeController.currentColor.sc3), +// titleSpacing: 0.rpx, +// title: Container( +// width: double.infinity, +// height: 180.rpx, +// child: Stack( +// alignment: Alignment.center, +// children: [ +// RichText( +// text: TextSpan( +// style: TextStyle( +// fontFamily: 'Readex Pro', +// color: themeController.currentColor.sc3, +// fontSize: 30.rpx, +// letterSpacing: 0.0, +// ), +// children: [ +// TextSpan( +// text: '健康快检'.tr, +// ), +// ], +// ), +// ), +// Positioned( +// left: 0.rpx, +// child: returnIconButtom, +// ), +// Obx(() { +// return deviceTypeController.experience_status.value != +// 200 || +// !deviceTypeController.own.value +// ? Positioned( +// right: 0.rpx, +// child: ClickableContainer( +// backgroundColor: Colors.transparent, +// highlightColor: Colors.transparent, +// padding: EdgeInsets.fromLTRB( +// 20.rpx, 20.rpx, 20.rpx, 20.rpx), +// onTap: () async { +// Get.toNamed('/healthExperienceHistory', +// arguments: widget.personInfo); +// }, +// child: SvgPicture.asset( +// 'assets/img/icon/history.svg', +// width: 35.rpx, +// height: 35.rpx, +// color: themeController.currentColor.sc3, +// ), +// ), +// ) +// : Container(); +// }) +// ], +// ), +// ), +// actions: [], +// centerTitle: false, +// ), +// body: SafeArea( +// top: true, +// child: Obx(() { +// return Stack( +// children: [ +// Align( +// alignment: Alignment.center, +// child: FractionallySizedBox( +// heightFactor: 0.5, +// widthFactor: 0.5, +// child: Opacity( +// opacity: 0.7, +// child: (onlineState == "离线".tr || inBed == '离床'.tr) +// ? Image.asset( +// 'assets/img/black_body_still.png', +// fit: BoxFit.contain, +// ) +// : SpeedControlledGif( +// 'assets/img/body_black.gif', +// speedFactor: +// 2, // 2.0 for 2x speed, 0.5 for half speed +// fit: BoxFit.contain, +// ), +// ), +// ), +// ), +// // 其余内容放在上层 +// Positioned.fill( +// child: Padding( +// padding: EdgeInsetsDirectional.fromSTEB( +// 0.rpx, 29.rpx, 0.rpx, 0.rpx), +// child: Column( +// mainAxisSize: MainAxisSize.max, +// children: [ +// if (deviceTypeController +// .experience_status.value != +// 200 || +// !deviceTypeController.own.value) +// Padding( +// padding: EdgeInsetsDirectional.fromSTEB( +// 30.rpx, 0.rpx, 30.rpx, 100.rpx), +// child: ClickableContainer( +// backgroundColor: +// themeController.currentColor.sc5, +// highlightColor: +// Colors.transparent, // 点击涟漪颜色 +// borderRadius: AppConstants() +// .normal_container_radius, // 如果你想加圆角可以设置 eg. 12.rpx +// padding: EdgeInsets.zero, +// onTap: () { +// print('点击了体征卡片'); +// }, +// child: Column( +// children: [ +// Container( +// padding: EdgeInsets.fromLTRB( +// 37.rpx, 37.rpx, 20.rpx, 37.rpx), +// child: Row( +// mainAxisSize: MainAxisSize.max, +// crossAxisAlignment: +// CrossAxisAlignment.start, +// children: [ +// // 左侧列 - 标签和值 +// Expanded( +// flex: 3, +// child: Column( +// crossAxisAlignment: +// CrossAxisAlignment.start, +// children: [ +// // 姓名行 +// Row( +// crossAxisAlignment: +// CrossAxisAlignment +// .center, +// children: [ +// // 标签 - 也不能换行,超出显示... +// Expanded( +// flex: 3, +// child: Text( +// '实时体征.姓名'.tr, +// style: TextStyle( +// fontFamily: +// 'Inter', +// fontSize: 26.rpx, +// color: themeController +// .currentColor +// .sc4, +// ), +// maxLines: 1, +// overflow: +// TextOverflow +// .ellipsis, +// ), +// ), +// SizedBox( +// width: 20 +// .rpx), // 标签和值之间的间距 +// // 值 +// Expanded( +// flex: 4, +// child: Text( +// device['person'] != +// null && +// device['person'][ +// 'name'] != +// null && +// device['person'] +// [ +// 'name'] +// .toString() +// .trim() +// .isNotEmpty +// ? device['person'] +// ['name'] +// .toString() +// : '体征检测设备'.tr, +// style: TextStyle( +// fontFamily: +// 'Inter', +// fontSize: 26.rpx, +// color: themeController +// .currentColor +// .sc3, +// ), +// maxLines: 1, +// overflow: +// TextOverflow +// .ellipsis, +// ), +// ), +// ], +// ), +// SizedBox(height: 36.rpx), + +// // 性别行 +// Row( +// crossAxisAlignment: +// CrossAxisAlignment +// .center, +// children: [ +// Expanded( +// flex: 3, +// child: Text( +// '性别'.tr, +// style: TextStyle( +// fontFamily: +// 'Inter', +// fontSize: 26.rpx, +// color: themeController +// .currentColor +// .sc4, +// ), +// maxLines: 1, +// overflow: +// TextOverflow +// .ellipsis, +// ), +// ), +// SizedBox(width: 20.rpx), +// Expanded( +// flex: 4, +// child: Text( +// device['person'] != +// null && +// device['person'] +// [ +// 'gender'] != +// null && +// device['person'] +// [ +// 'gender'] +// .toString() +// .trim() +// .isNotEmpty +// ? getGenderText( +// device['person'] +// [ +// 'gender']) +// : '-'.tr, +// style: TextStyle( +// fontFamily: +// 'Inter', +// fontSize: 26.rpx, +// color: themeController +// .currentColor +// .sc3, +// ), +// maxLines: 1, +// overflow: +// TextOverflow +// .ellipsis, +// ), +// ), +// ], +// ), +// SizedBox(height: 36.rpx), + +// // 身高行 +// Row( +// crossAxisAlignment: +// CrossAxisAlignment +// .center, +// children: [ +// Expanded( +// flex: 3, +// child: Text( +// '身高'.tr, +// style: TextStyle( +// fontFamily: +// 'Inter', +// fontSize: 26.rpx, +// color: themeController +// .currentColor +// .sc4, +// ), +// maxLines: 1, +// overflow: +// TextOverflow +// .ellipsis, +// ), +// ), +// SizedBox(width: 20.rpx), +// Expanded( +// flex: 4, +// child: Text( +// device['person'] != +// null && +// device['person'] +// [ +// 'height'] != +// null && +// device['person'] +// [ +// 'height'] +// .toString() +// .trim() +// .isNotEmpty +// ? '${device['person']['height']}cm' +// : '-'.tr + "cm", +// style: TextStyle( +// fontFamily: +// 'Inter', +// fontSize: 26.rpx, +// color: themeController +// .currentColor +// .sc3, +// ), +// maxLines: 1, +// overflow: +// TextOverflow +// .ellipsis, +// ), +// ), +// ], +// ), +// ], +// ), +// ), + +// SizedBox( +// width: 30.rpx), // 左右两列之间的间距 + +// // 右侧列 - 标签和值 +// Expanded( +// flex: 4, +// child: Column( +// crossAxisAlignment: +// CrossAxisAlignment.start, +// children: [ +// // 设备ID行 +// Row( +// crossAxisAlignment: +// CrossAxisAlignment +// .center, +// children: [ +// Expanded( +// flex: 1, +// child: Text( +// '实时体征.设备ID'.tr, +// style: TextStyle( +// fontFamily: +// 'Inter', +// fontSize: 26.rpx, +// color: themeController +// .currentColor +// .sc4, +// ), +// maxLines: 1, +// overflow: +// TextOverflow +// .ellipsis, +// ), +// ), +// SizedBox(width: 20.rpx), +// Expanded( +// flex: 2, +// child: Text( +// '${device['code'] ?? '未知数据'.tr}', +// style: TextStyle( +// fontFamily: +// 'Inter', +// fontSize: 26.rpx, +// color: themeController +// .currentColor +// .sc3, +// ), +// maxLines: 1, +// overflow: +// TextOverflow +// .ellipsis, +// ), +// ), +// ], +// ), +// SizedBox(height: 36.rpx), + +// // 年龄行 +// Row( +// crossAxisAlignment: +// CrossAxisAlignment +// .center, +// children: [ +// Expanded( +// flex: 1, +// child: Text( +// '年龄'.tr, +// style: TextStyle( +// fontFamily: +// 'Inter', +// fontSize: 26.rpx, +// color: themeController +// .currentColor +// .sc4, +// ), +// maxLines: 1, +// overflow: +// TextOverflow +// .ellipsis, +// ), +// ), +// SizedBox(width: 20.rpx), +// Expanded( +// flex: 2, +// child: Text( +// device['person'] != +// null && +// device['person'] +// [ +// 'birthday'] != +// null && +// device['person'] +// [ +// 'birthday'] +// .toString() +// .trim() +// .isNotEmpty +// ? calculateAge(device[ +// 'person'] +// [ +// 'birthday'] +// .toString()) +// : '-'.tr, +// style: TextStyle( +// fontFamily: +// 'Inter', +// fontSize: 26.rpx, +// color: themeController +// .currentColor +// .sc3, +// ), +// maxLines: 1, +// overflow: +// TextOverflow +// .ellipsis, +// ), +// ), +// ], +// ), +// SizedBox(height: 36.rpx), + +// // 体重行 +// Row( +// crossAxisAlignment: +// CrossAxisAlignment +// .center, +// children: [ +// Expanded( +// flex: 1, +// child: Text( +// '体重'.tr, +// style: TextStyle( +// fontFamily: +// 'Inter', +// fontSize: 26.rpx, +// color: themeController +// .currentColor +// .sc4, +// ), +// maxLines: 1, +// overflow: +// TextOverflow +// .ellipsis, +// ), +// ), +// SizedBox(width: 20.rpx), +// Expanded( +// flex: 2, +// child: Text( +// device['person'] != +// null && +// device['person'] +// [ +// 'weight'] != +// null && +// device['person'] +// [ +// 'weight'] +// .toString() +// .trim() +// .isNotEmpty +// ? '${device['person']['weight']}kg' +// : '-'.tr + "kg", +// style: TextStyle( +// fontFamily: +// 'Inter', +// fontSize: 26.rpx, +// color: themeController +// .currentColor +// .sc3, +// ), +// maxLines: 1, +// overflow: +// TextOverflow +// .ellipsis, +// ), +// ), +// ], +// ), +// ], +// ), +// ), +// ], +// ), +// ), +// ], +// ), +// ), +// ), +// if (deviceTypeController +// .experience_status.value == +// 200 && +// deviceTypeController.own.value) +// Padding( +// padding: +// EdgeInsets.fromLTRB(46.rpx, 0, 46.rpx, 0), +// child: Column( +// children: [ +// SizedBox( +// height: 103.rpx, +// ), +// Text( +// "快检中...".tr, +// style: TextStyle( +// color: +// themeController.currentColor.sc1, +// fontSize: AppConstants() +// .title_text_fontSize, +// ), +// ), +// SizedBox( +// height: 30.rpx, +// ), +// Text( +// "MAC号".tr + +// ": ${widget.personInfo['mac'] ?? '未知数据'.tr}", +// style: TextStyle( +// color: +// themeController.currentColor.sc4, +// fontSize: AppConstants() +// .smaller_text_fontSize, +// ), +// ), +// SizedBox( +// height: 4.rpx, +// ), +// Text( +// "睡眠报告提示".tr, +// style: TextStyle( +// color: +// themeController.currentColor.sc4, +// fontSize: AppConstants() +// .smaller_text_fontSize, +// ), +// ), +// SizedBox( +// height: 48.rpx, +// ), +// ], +// ), +// ), +// Expanded( +// child: SingleChildScrollView( +// child: deviceTypeController +// .experience_status.value != +// 200 || +// !deviceTypeController.own.value +// ? Container() +// : Column( +// children: [ +// Padding( +// padding: +// EdgeInsetsDirectional.fromSTEB( +// 30.rpx, 0, 30.rpx, 0), +// child: Container( +// child: Column( +// children: [ +// Row( +// mainAxisAlignment: +// MainAxisAlignment +// .spaceBetween, +// children: [ +// DeviceStatusInfoWidget( +// title: "在离床".tr, +// iconAsset: +// "assets/img/icon/bed_status.svg", +// value: inBed, +// ), +// DeviceStatusInfoWidget( +// title: "体动".tr, +// iconAsset: +// "assets/img/icon/bodymotion.svg", +// value: inBed == "离床".tr +// ? ("-") +// : (bodyMotion == +// null || +// bodyMotion == +// -1) +// ? "-" +// : "$bodyMotion", +// ), +// ], +// ), +// Row( +// mainAxisAlignment: +// MainAxisAlignment +// .spaceBetween, +// children: [ +// DeviceStatusInfoWidget( +// title: "心率".tr, +// iconAsset: +// "assets/img/icon/heart.svg", +// value: inBed == "离床".tr +// ? "-" +// : ((heartrate == +// null || +// heartrate == +// -1) +// ? "-" +// : "$heartrate"), +// ), +// DeviceStatusInfoWidget( +// title: "打鼾".tr, +// iconAsset: +// "assets/img/icon/snore.svg", +// value: inBed == "离床".tr +// ? "-" +// : ('${snores}'.tr), +// ), +// ], +// ), +// Row( +// mainAxisAlignment: +// MainAxisAlignment +// .spaceBetween, +// children: [ +// DeviceStatusInfoWidget( +// title: "呼吸".tr, +// iconAsset: +// "assets/img/icon/breathe.svg", +// value: inBed == "离床".tr +// ? ("-") +// : ((breathrate == +// null || +// breathrate == +// -1) +// ? "-" +// : "$breathrate"), +// ), +// DeviceStatusInfoWidget( +// title: "呼吸暂停".tr, +// iconAsset: +// "assets/img/icon/breathe_pause.svg", +// value: inBed == "离床".tr +// ? "-" +// : ('${breathState}'), +// ), +// ], +// ), +// ].divide( +// SizedBox(height: 49.rpx)), +// ), +// ), +// ), +// Padding( +// padding: +// EdgeInsetsDirectional.fromSTEB( +// 0.rpx, +// 67.rpx, +// 0.rpx, +// 0.rpx), +// child: Container( +// height: 40.rpx, +// child: Text( +// bodyMotion >= maxBodyMotion +// ? '请保持静止'.tr +// : "", +// style: TextStyle( +// fontFamily: 'Inter', +// fontSize: 26.rpx, +// letterSpacing: 0.0, +// color: themeController +// .currentColor.sc9, +// ), +// ), +// ), +// ), +// // SizedBox( +// // height: 207.rpx, +// // ), +// ], +// ), +// )), +// if (deviceTypeController +// .experience_status.value != +// 200 || +// !deviceTypeController.own.value) +// ClickableContainer( +// backgroundColor: +// Colors.transparent, // 可自定义背景色 +// highlightColor: Colors.transparent, // 点击涟漪颜色 +// borderRadius: 16.rpx, // 圆角大小,可按需调整 +// padding: EdgeInsetsDirectional.fromSTEB( +// 30.rpx, 0.rpx, 30.rpx, 0.rpx), +// onTap: () {}, +// child: Container( +// padding: EdgeInsetsDirectional.fromSTEB( +// 26.rpx, 26.rpx, 26.rpx, 26.rpx), +// decoration: BoxDecoration( +// // color: FlutterFlowTheme.of(context) +// // .primaryBackground +// // .withOpacity(0.6), // 半透明背景 +// borderRadius: +// BorderRadius.circular(16.rpx), +// border: Border.all( +// color: themeController.currentColor.sc4 +// .withOpacity(0.5), +// width: 0.5.rpx, +// ), +// ), +// child: Row( +// crossAxisAlignment: +// CrossAxisAlignment.start, +// children: [ +// Padding( +// padding: +// EdgeInsetsDirectional.fromSTEB( +// 0, 8.rpx, 0, 0), +// child: Container( +// width: 23.rpx, +// height: 23.rpx, +// // width: double.infinity, +// decoration: BoxDecoration(), +// child: SvgPicture.asset( +// 'assets/img/icon/tips.svg', +// fit: BoxFit.cover, +// color: themeController +// .currentColor.sc4, +// ), +// ), +// ), +// Expanded( +// child: Text( +// '使用方式:人员使用健康快检功能时,只需平躺或坐在正常运行中的体征传感器传感器上方,保持静止,然后点击“启动快检”,待进度条完成,即可得出快检报告。' +// .tr, +// style: TextStyle( +// fontFamily: 'Inter', +// letterSpacing: 0.0, +// color: themeController +// .currentColor.sc4, +// ), +// ), +// ), +// ].divide(SizedBox(width: 23.rpx)), +// ), +// ), +// ), +// SizedBox( +// height: 40.rpx, +// ), +// if (deviceTypeController +// .experience_status.value != +// 200 || +// !deviceTypeController.own.value) +// Padding( +// padding: EdgeInsets.fromLTRB( +// 100.rpx, 0, 100.rpx, 0), +// child: Column( +// children: [ +// CustomCard( +// borderRadius: AppConstants() +// .button_container_radius, // 圆角半径 +// onTap: () async { +// // HapticFeedback.lightImpact(); + +// bool canStart = +// await deviceTypeController +// .checkReportStatus( +// widget.personInfo['mac']); +// // if (!canStart) { +// // NewTopSlideNotification.show( +// // text: "设备正在快检中,请稍后再试!".tr, +// // textColor: themeController +// // .currentColor.sc9); +// // return; +// // } + +// if (!deviceTypeController.own.value && +// deviceTypeController +// .experience_status +// .value == +// APPQuickCheckStatus +// .inProgress.value) { +// NewTopSlideNotification.show( +// text: "设备正在快检中,请稍后再试!".tr, +// textColor: themeController +// .currentColor.sc9); +// return; +// } +// bool opRes = +// await deviceTypeController +// .qcCheckControl( +// widget.personInfo, 1); +// if (!opRes) { +// return; +// } + +// deviceTypeController +// .experience_percent.value = 0; +// progressNotifier.value = 0; +// deviceTypeController +// .experience_status.value = 200; +// _startCheckStatusTimer(); +// deviceTypeController.updateAll(); +// }, +// colors: AppConstants() +// .thNormalButton, // 渐变色是同一个色,也可以根据需要调整 +// child: Container( +// width: +// // MediaQuery.sizeOf(context).width * 0.66, +// bodySize.maxWidth, +// height: MediaQuery.sizeOf(context) +// .height * +// 0.055, +// constraints: BoxConstraints( +// minWidth: 500.rpx, +// minHeight: 90.rpx, +// ), +// child: Row( +// mainAxisSize: MainAxisSize.max, +// mainAxisAlignment: +// MainAxisAlignment.center, +// children: [ +// Text( +// '开始快检'.tr, +// style: TextStyle( +// color: themeController +// .currentColor.sc3, +// fontFamily: 'Inter', +// fontSize: AppConstants() +// .normal_text_fontSize, +// letterSpacing: 0.0, +// ), +// ), +// ].divide(SizedBox( +// width: 17.rpx, +// )), +// ), +// ), +// ), +// ], +// ), +// ), +// if (deviceTypeController +// .experience_status.value == +// 200 && +// deviceTypeController.own.value) +// Padding( +// padding: EdgeInsetsDirectional.fromSTEB( +// 100.rpx, 0.rpx, 100.rpx, 60.rpx), +// child: CalibrationProgressWidget( +// progressNotifier: progressNotifier, +// failureNotifier: failureNotifier, +// ), +// ), +// if (deviceTypeController +// .experience_status.value == +// 200 && +// deviceTypeController.own.value) +// Padding( +// padding: EdgeInsets.fromLTRB( +// 100.rpx, 0, 100.rpx, 0), +// child: Column( +// children: [ +// CustomCard( +// borderRadius: AppConstants() +// .button_container_radius, // 圆角半径 +// onTap: () { +// showCancelConfirmDialog( +// context, widget.personInfo); +// }, +// colors: [ +// themeController.currentColor.sc9 +// ], // 渐变色是同一个色,也可以根据需要调整 +// child: Container( +// width: +// // MediaQuery.sizeOf(context).width * 0.66, +// bodySize.maxWidth, +// height: MediaQuery.sizeOf(context) +// .height * +// 0.055, +// constraints: BoxConstraints( +// minWidth: 500.rpx, +// minHeight: 90.rpx, +// ), +// child: Row( +// mainAxisSize: MainAxisSize.max, +// mainAxisAlignment: +// MainAxisAlignment.center, +// children: [ +// Text( +// '终止'.tr, +// style: TextStyle( +// color: themeController +// .currentColor.sc3, +// fontFamily: 'Inter', +// fontSize: AppConstants() +// .normal_text_fontSize, +// letterSpacing: 0.0, +// ), +// ), +// ].divide(SizedBox( +// width: 17.rpx, +// )), +// ), +// ), +// ), +// ], +// ), +// ), +// SizedBox( +// height: 40.rpx, +// ), +// SizedBox( +// height: 26.rpx, +// ), +// ], +// ), +// ), +// ), +// ], +// ); +// })), +// ), +// ), +// ), +// ); +// } + +// void _closeWebSocket() { +// // 取消WebSocket订阅 +// edm.EasyDartModule.websocket.sendData( +// jsonEncode(WebSocketMessage(path: "/vsbs/web/rt/marttress", type: 2))); +// _onlineTimer?.cancel(); +// } + +// // 开始状态查询定时器 +// void _startCheckStatusTimer() { +// _checkStatusTimer?.cancel(); // 取消之前的定时器 + +// _checkStatusTimer = Timer.periodic(Duration(seconds: 3), (timer) { +// if (mounted) { +// edm.EasyDartModule.logger.info("定时查询快检状态"); +// deviceTypeController +// .checkReportStatus(widget.personInfo['mac']) +// .then((_) async { +// // 如果状态变回404(非体验中),停止定时器 +// progressNotifier.value = +// deviceTypeController.experience_percent.value.toDouble(); +// deviceTypeController.updateAll(); +// if (deviceTypeController.experience_status.value == +// APPQuickCheckStatus.completed.value) { +// //体验正常结束 +// try { +// deviceTypeController.experience_status.value = 404; +// deviceTypeController.experience_status.value = 0; +// progressNotifier.value = 0; +// edm.EasyDartModule.logger.info("快检结束,停止定时查询"); +// _checkStatusTimer?.cancel(); +// Map data = { +// "id": deviceTypeController.experience_id.value, +// "mac": widget.personInfo['mac'] +// }; +// // await deviceTypeController.getCheckHistory( +// // id: deviceTypeController.experience_id.value, +// // mac: widget.personInfo['mac']); +// bool hasVibrator = await Vibration.hasVibrator(); +// if (hasVibrator) { +// // 震动1000毫秒(1秒) +// Vibration.vibrate(duration: 500); +// } +// Get.toNamed('/healthQuickCheckReportPage', arguments: data); +// } catch (e) { +// edm.EasyDartModule.logger.error("快检报告页面加载失败---?${e.toString()}"); +// } +// } +// if (deviceTypeController.experience_status.value == +// APPQuickCheckStatus.timeout.value || +// deviceTypeController.experience_status.value == +// APPQuickCheckStatus.calculateError.value || +// deviceTypeController.experience_status.value == +// APPQuickCheckStatus.insufficientData.value) { +// // 根据状态码获取对应的枚举 +// APPQuickCheckStatus? status = APPQuickCheckStatusExtension.fromInt( +// deviceTypeController.experience_status.value); + +// // 获取对应的状态描述文字 +// String statusMessage = status?.description ?? "体验异常结束".tr; + +// //体验异常结束 +// deviceTypeController.experience_status.value = 404; +// deviceTypeController.experience_percent.value = 0; +// progressNotifier.value = 0; +// edm.EasyDartModule.logger +// .info("快检结束,停止定时查询 - 状态: ${status?.description}"); +// _checkStatusTimer?.cancel(); +// await showTipDialog( +// context, +// Text( +// statusMessage, +// style: TextStyle( +// color: Colors.white, +// ), +// ), +// ); +// } +// if (!deviceTypeController.own.value) { +// _checkStatusTimer?.cancel(); +// } +// }); +// } +// }); +// } +// } + import 'dart:async'; import 'dart:ui' as ui; @@ -23,8 +1291,12 @@ import 'package:vbvs_app/controller/user_info_controller.dart'; import 'package:vbvs_app/enum/APPQuickCheckStatus.dart'; import 'package:vbvs_app/model/WebSocketMessage.dart'; import 'package:vbvs_app/pages/device/component/DeviceStatusInfoWidget.dart'; +import 'package:vbvs_app/pages/device/component/health_experience_tool.dart'; import 'package:vbvs_app/pages/device_bind/componnet/CalibrationProgressWidget.dart'; import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vibration/vibration.dart'; + +import 'component/SpeedControlledGif.dart'; class HealthCheckPage extends StatefulWidget { var personInfo; @@ -35,7 +1307,7 @@ class HealthCheckPage extends StatefulWidget { } class _HealthCheckPageState extends State - with WidgetsBindingObserver { + with WidgetsBindingObserver, SingleTickerProviderStateMixin { GlobalController globalController = Get.find(); UserInfoController userInfoController = Get.find(); BlueteethBindController blueteethBindController = Get.find(); @@ -46,7 +1318,7 @@ class _HealthCheckPageState extends State String breathState = "-"; String inBed = "-"; String onlineState = "离线".tr; - Timer? _onlineTimer; // 添加 Timer 引用 + Timer? _onlineTimer; int bodyMotion = -1; int breathrate = -1; String snores = "-"; @@ -55,12 +1327,54 @@ class _HealthCheckPageState extends State final ValueNotifier progressNotifier = ValueNotifier(0.0); final ValueNotifier failureNotifier = ValueNotifier(false); - Timer? _checkStatusTimer; // 添加状态查询定时器 - bool _isInitialized = false; // 添加初始化标志 + Timer? _checkStatusTimer; + bool _isInitialized = false; + + // 动画控制器 + late AnimationController _animationController; + // 顶部动画:从上往下滑动 + late Animation _topSlideAnimation; + // 左侧容器动画:从左向右滑动 + late Animation _leftSlideAnimation; + // 右侧容器动画:从右向左滑动 + late Animation _rightSlideAnimation; @override void initState() { - WidgetsBinding.instance.addObserver(this); // 添加生命周期观察者 + // 初始化动画控制器 + _animationController = AnimationController( + duration: const Duration(milliseconds: 400), + vsync: this, + ); + + // 顶部下滑动画:从顶部滑入 + _topSlideAnimation = Tween( + begin: const Offset(0, -1), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeOutCubic, + )); + + // 左侧滑动动画:从左向右 + _leftSlideAnimation = Tween( + begin: const Offset(-1, 0), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeOutCubic, + )); + + // 右侧滑动动画:从右向左 + _rightSlideAnimation = Tween( + begin: const Offset(1, 0), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeOutCubic, + )); + + WidgetsBinding.instance.addObserver(this); try { deviceTypeController .checkReportStatus(widget.personInfo['mac']) @@ -68,10 +1382,11 @@ class _HealthCheckPageState extends State setState(() { _isInitialized = true; }); - // 如果当前状态是体验中,启动定时器 if (deviceTypeController.experience_status.value == 200 && deviceTypeController.own.value) { _startCheckStatusTimer(); + // 如果已经在体验中,直接播放动画 + _animationController.forward(); } }); } catch (e) { @@ -80,11 +1395,30 @@ class _HealthCheckPageState extends State _initWebSocket(); + // 监听状态变化,控制动画 + ever(deviceTypeController.experience_status, (status) { + final isExperiencing = status == 200 && deviceTypeController.own.value; + if (isExperiencing) { + _animationController.forward(); + } else { + _animationController.reverse(); + } + }); + + ever(deviceTypeController.own, (own) { + final isExperiencing = + deviceTypeController.experience_status.value == 200 && own; + if (isExperiencing) { + _animationController.forward(); + } else { + _animationController.reverse(); + } + }); + super.initState(); } Future _initWebSocket() async { - // 发送WebSocket请求 try { Future.delayed(Duration(seconds: 0), () { CommonVariables.callMap["/vsbs/web/rt/marttress"] = (data) { @@ -101,7 +1435,6 @@ class _HealthCheckPageState extends State return; } inBed = data["inBed"]; - // 心率 呼吸 体动 呼吸暂停 if ("离床".tr == inBed) { breathState = "否".tr; bodyMotion = 0; @@ -109,7 +1442,7 @@ class _HealthCheckPageState extends State heartrate = 0; snores = "否".tr; } else { - onlineState = "在线".tr; // 接收到数据,设置为在线 + onlineState = "在线".tr; breathState = data["breathState"] == null || data["breathState"] == "" ? "-" @@ -127,10 +1460,10 @@ class _HealthCheckPageState extends State if (mounted) { setState(() { - onlineState = "在线".tr; // 接收到数据,设置为在线 + onlineState = "在线".tr; }); } - _startOnlineTimer(); // 重置定时器 + _startOnlineTimer(); }; }); } catch (e) { @@ -165,51 +1498,45 @@ class _HealthCheckPageState extends State data: {"mac": widget.personInfo['mac']}))); _startOnlineTimer(); } - //y @override void dispose() { - WidgetsBinding.instance.removeObserver(this); // 移除生命周期观察者 + WidgetsBinding.instance.removeObserver(this); _onlineTimer?.cancel(); - _checkStatusTimer?.cancel(); // 取消状态查询定时器 + _checkStatusTimer?.cancel(); _closeWebSocket(); + // _animationController.dispose(); CommonVariables.callMap.remove("/vsbs/web/rt/marttress"); super.dispose(); } - // 监听应用生命周期变化 @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { - // 应用回到前台时重新连接WebSocket edm.EasyDartModule.logger.info("app切回页面,重连websocket"); _initWebSocket(); - _initWebSocket(); - // 重新查询状态,然后根据状态决定是否启动定时器 deviceTypeController .checkReportStatus(widget.personInfo['mac']) .then((_) { - // 如果当前状态是体验中,重新启动定时器 if (deviceTypeController.experience_status.value != 404 && deviceTypeController.own.value) { _startCheckStatusTimer(); } }); } else if (state == AppLifecycleState.paused) { - // 应用进入后台时关闭WebSocket和定时器 _closeWebSocket(); _checkStatusTimer?.cancel(); } } void _startOnlineTimer() { - _onlineTimer?.cancel(); // 取消之前的定时器 + _onlineTimer?.cancel(); _onlineTimer = Timer.periodic(Duration(seconds: 60), (timer) { if (mounted) { setState(() { edm.EasyDartModule.logger.info("60 秒内没有接收到数据,设置为离线"); - onlineState = "离线".tr; // 30 秒内没有接收到数据,设置为离线 + onlineState = "离线".tr; inBed = "-"; bodyMotion = -1; heartrate = -1; @@ -221,13 +1548,90 @@ class _HealthCheckPageState extends State }); } + void _closeWebSocket() { + edm.EasyDartModule.websocket.sendData( + jsonEncode(WebSocketMessage(path: "/vsbs/web/rt/marttress", type: 2))); + _onlineTimer?.cancel(); + } + + void _startCheckStatusTimer() { + _checkStatusTimer?.cancel(); + + _checkStatusTimer = Timer.periodic(Duration(seconds: 3), (timer) { + if (mounted) { + edm.EasyDartModule.logger.info("定时查询快检状态"); + deviceTypeController + .checkReportStatus(widget.personInfo['mac']) + .then((_) async { + progressNotifier.value = + deviceTypeController.experience_percent.value.toDouble(); + deviceTypeController.updateAll(); + if (deviceTypeController.experience_status.value == + APPQuickCheckStatus.completed.value) { + try { + deviceTypeController.experience_status.value = 404; + deviceTypeController.experience_status.value = 0; + progressNotifier.value = 0; + edm.EasyDartModule.logger.info("快检结束,停止定时查询"); + _checkStatusTimer?.cancel(); + Map data = { + "id": deviceTypeController.experience_id.value, + "mac": widget.personInfo['mac'] + }; + bool hasVibrator = await Vibration.hasVibrator(); + if (hasVibrator) { + Vibration.vibrate(duration: 500); + } + Get.toNamed('/healthQuickCheckReportPage', arguments: data); + } catch (e) { + edm.EasyDartModule.logger.error("快检报告页面加载失败---?${e.toString()}"); + } + } + if (deviceTypeController.experience_status.value == + APPQuickCheckStatus.timeout.value || + deviceTypeController.experience_status.value == + APPQuickCheckStatus.calculateError.value || + deviceTypeController.experience_status.value == + APPQuickCheckStatus.insufficientData.value) { + APPQuickCheckStatus? status = APPQuickCheckStatusExtension.fromInt( + deviceTypeController.experience_status.value); + + String statusMessage = status?.description ?? "体验异常结束".tr; + + deviceTypeController.experience_status.value = 404; + deviceTypeController.experience_percent.value = 0; + progressNotifier.value = 0; + edm.EasyDartModule.logger + .info("快检结束,停止定时查询 - 状态: ${status?.description}"); + _checkStatusTimer?.cancel(); + bool hasVibrator = await Vibration.hasVibrator(); + if (hasVibrator) { + Vibration.vibrate(duration: 500); + } + await showTipDialog( + context, + Text( + statusMessage, + style: TextStyle( + color: themeController.currentColor.sc9, + ), + ), + ); + } + if (!deviceTypeController.own.value) { + _checkStatusTimer?.cancel(); + } + }); + } + }); + } + @override Widget build(BuildContext context) { Map device = widget.personInfo; return LayoutBuilder( builder: (context, bodySize) => GestureDetector( - // onTap: () => FocusScope.of(context).unfocus(),, child: Container( decoration: BoxDecoration( image: DecorationImage( @@ -278,11 +1682,9 @@ class _HealthCheckPageState extends State highlightColor: Colors.transparent, padding: EdgeInsets.fromLTRB( 20.rpx, 20.rpx, 20.rpx, 20.rpx), - onTap: () { + onTap: () async { Get.toNamed('/healthExperienceHistory', arguments: widget.personInfo); - // Get.toNamed('/healthQuickCheckReportPage', - // arguments: widget.personInfo); }, child: SvgPicture.asset( 'assets/img/icon/history.svg', @@ -303,6 +1705,10 @@ class _HealthCheckPageState extends State body: SafeArea( top: true, child: Obx(() { + final isExperiencing = + deviceTypeController.experience_status.value == 200 && + deviceTypeController.own.value; + return Stack( children: [ Align( @@ -319,8 +1725,7 @@ class _HealthCheckPageState extends State ) : SpeedControlledGif( 'assets/img/body_black.gif', - speedFactor: - 2, // 2.0 for 2x speed, 0.5 for half speed + speedFactor: 2, fit: BoxFit.contain, ), ), @@ -334,20 +1739,17 @@ class _HealthCheckPageState extends State child: Column( mainAxisSize: MainAxisSize.max, children: [ - if (deviceTypeController - .experience_status.value != - 200 || - !deviceTypeController.own.value) + // 顶部区域 - 未体验显示个人信息,体验中显示快检中信息(带动画) + if (!isExperiencing) Padding( padding: EdgeInsetsDirectional.fromSTEB( 30.rpx, 0.rpx, 30.rpx, 100.rpx), child: ClickableContainer( backgroundColor: themeController.currentColor.sc5, - highlightColor: - Colors.transparent, // 点击涟漪颜色 - borderRadius: AppConstants() - .normal_container_radius, // 如果你想加圆角可以设置 eg. 12.rpx + highlightColor: Colors.transparent, + borderRadius: + AppConstants().normal_container_radius, padding: EdgeInsets.zero, onTap: () { print('点击了体征卡片'); @@ -375,7 +1777,6 @@ class _HealthCheckPageState extends State CrossAxisAlignment .center, children: [ - // 标签 - 也不能换行,超出显示... Expanded( flex: 3, child: Text( @@ -394,10 +1795,7 @@ class _HealthCheckPageState extends State .ellipsis, ), ), - SizedBox( - width: 20 - .rpx), // 标签和值之间的间距 - // 值 + SizedBox(width: 20.rpx), Expanded( flex: 4, child: Text( @@ -474,7 +1872,7 @@ class _HealthCheckPageState extends State .toString() .trim() .isNotEmpty - ? _getGenderText( + ? getGenderText( device['person'] [ 'gender']) @@ -559,8 +1957,7 @@ class _HealthCheckPageState extends State ), ), - SizedBox( - width: 30.rpx), // 左右两列之间的间距 + SizedBox(width: 30.rpx), // 右侧列 - 标签和值 Expanded( @@ -656,11 +2053,11 @@ class _HealthCheckPageState extends State .toString() .trim() .isNotEmpty - ? _calculateAge( - device['person'] - [ - 'birthday'] - .toString()) + ? calculateAge(device[ + 'person'] + [ + 'birthday'] + .toString()) : '-'.tr, style: TextStyle( fontFamily: @@ -747,421 +2144,406 @@ class _HealthCheckPageState extends State ], ), ), + ) + else + // 体验中状态 - 快检中信息(带动画) + AnimatedBuilder( + animation: _animationController, + builder: (context, child) { + return SlideTransition( + position: _topSlideAnimation, + child: Padding( + padding: EdgeInsets.fromLTRB( + 46.rpx, 0, 46.rpx, 0), + child: Column( + children: [ + SizedBox( + height: 103.rpx, + ), + Text( + "快检中...".tr, + style: TextStyle( + color: themeController + .currentColor.sc1, + fontSize: AppConstants() + .bigger_text_fontSize, + ), + ), + SizedBox( + height: 30.rpx, + ), + Text( + "MAC号".tr + + ": ${widget.personInfo['mac'] ?? '未知数据'.tr}", + style: TextStyle( + color: themeController + .currentColor.sc4, + fontSize: AppConstants() + .smaller_text_fontSize, + ), + ), + SizedBox( + height: 4.rpx, + ), + Text( + "睡眠报告提示".tr, + style: TextStyle( + color: themeController + .currentColor.sc4, + fontSize: AppConstants() + .smaller_text_fontSize, + ), + ), + SizedBox( + height: 48.rpx, + ), + ], + ), + ), + ); + }, ), - if (deviceTypeController - .experience_status.value == - 200 && - deviceTypeController.own.value) - Padding( - padding: - EdgeInsets.fromLTRB(46.rpx, 0, 46.rpx, 0), - child: Column( - children: [ - SizedBox( - height: 103.rpx, - ), - Text( - "快检中...".tr, - style: TextStyle( - color: - themeController.currentColor.sc1, - fontSize: AppConstants() - .title_text_fontSize, - ), - ), - SizedBox( - height: 30.rpx, - ), - Text( - "MAC号".tr + - ": ${widget.personInfo['mac'] ?? '未知数据'.tr}", - style: TextStyle( - color: - themeController.currentColor.sc4, - fontSize: AppConstants() - .smaller_text_fontSize, - ), - ), - SizedBox( - height: 4.rpx, - ), - Text( - "睡眠报告提示".tr, - style: TextStyle( - color: - themeController.currentColor.sc4, - fontSize: AppConstants() - .smaller_text_fontSize, - ), - ), - SizedBox( - height: 48.rpx, - ), - ], - ), - ), - Expanded( + + // 中间区域 - 体征信息(体验中时显示,带动画) + if (isExperiencing) + Expanded( child: SingleChildScrollView( - child: deviceTypeController - .experience_status.value != - 200 || - !deviceTypeController.own.value - ? Container() - : Column( - children: [ - Padding( - padding: - EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0, 30.rpx, 0), - child: Container( - child: Column( - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - DeviceStatusInfoWidget( - title: "在离床".tr, - iconAsset: - "assets/img/icon/bed_status.svg", - value: inBed, - ), - DeviceStatusInfoWidget( - title: "体动".tr, - iconAsset: - "assets/img/icon/bodymotion.svg", - value: inBed == "离床".tr - ? ("-") - : (bodyMotion == - null || - bodyMotion == - -1) - ? "-" - : "$bodyMotion", - ), - ], - ), - Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - DeviceStatusInfoWidget( - title: "心率".tr, - iconAsset: - "assets/img/icon/heart.svg", - value: inBed == "离床".tr - ? "-" - : ((heartrate == - null || - heartrate == - -1) - ? "-" - : "$heartrate"), - ), - DeviceStatusInfoWidget( - title: "打鼾".tr, - iconAsset: - "assets/img/icon/snore.svg", - value: inBed == "离床".tr - ? "-" - : ('${snores}'.tr), - ), - ], - ), - Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - DeviceStatusInfoWidget( - title: "呼吸".tr, - iconAsset: - "assets/img/icon/breathe.svg", - value: inBed == "离床".tr - ? ("-") - : ((breathrate == - null || - breathrate == - -1) - ? "-" - : "$breathrate"), - ), - DeviceStatusInfoWidget( - title: "呼吸暂停".tr, - iconAsset: - "assets/img/icon/breathe_pause.svg", - value: inBed == "离床".tr - ? "-" - : ('${breathState}'), - ), - ], - ), - ].divide( - SizedBox(height: 49.rpx)), - ), - ), - ), - Padding( - padding: - EdgeInsetsDirectional.fromSTEB( - 0.rpx, - 67.rpx, - 0.rpx, - 0.rpx), - child: Container( - height: 40.rpx, - child: Text( - bodyMotion >= maxBodyMotion - ? '请保持静止'.tr - : "", - style: TextStyle( - fontFamily: 'Inter', - fontSize: 26.rpx, - letterSpacing: 0.0, - color: themeController - .currentColor.sc9, - ), - ), - ), - ), - // SizedBox( - // height: 207.rpx, - // ), - ], - ), - )), - if (deviceTypeController - .experience_status.value != - 200 || - !deviceTypeController.own.value) - ClickableContainer( - backgroundColor: - Colors.transparent, // 可自定义背景色 - highlightColor: Colors.transparent, // 点击涟漪颜色 - borderRadius: 16.rpx, // 圆角大小,可按需调整 - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0.rpx), - onTap: () {}, - child: Container( - padding: EdgeInsetsDirectional.fromSTEB( - 26.rpx, 26.rpx, 26.rpx, 26.rpx), - decoration: BoxDecoration( - // color: FlutterFlowTheme.of(context) - // .primaryBackground - // .withOpacity(0.6), // 半透明背景 - borderRadius: - BorderRadius.circular(16.rpx), - border: Border.all( - color: themeController.currentColor.sc4 - .withOpacity(0.5), - width: 0.5.rpx, - ), - ), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, + child: Column( children: [ Padding( padding: EdgeInsetsDirectional.fromSTEB( - 0, 8.rpx, 0, 0), + 30.rpx, 0, 30.rpx, 0), + child: Row( + children: [ + // 左侧三个容器(从左向右滑动) + Expanded( + child: AnimatedBuilder( + animation: + _animationController, + builder: (context, child) { + return SlideTransition( + position: + _leftSlideAnimation, + child: Column( + children: [ + DeviceStatusInfoWidget( + title: "在离床".tr, + iconAsset: + "assets/img/icon/bed_status.svg", + value: inBed, + ), + SizedBox( + height: 49.rpx), + DeviceStatusInfoWidget( + title: "心率".tr, + iconAsset: + "assets/img/icon/heart.svg", + value: inBed == + "离床".tr + ? "-" + : ((heartrate == + null || + heartrate == + -1) + ? "-" + : "$heartrate"), + ), + SizedBox( + height: 49.rpx), + DeviceStatusInfoWidget( + title: "呼吸".tr, + iconAsset: + "assets/img/icon/breathe.svg", + value: inBed == + "离床".tr + ? ("-") + : ((breathrate == + null || + breathrate == + -1) + ? "-" + : "$breathrate"), + ), + ], + ), + ); + }, + ), + ), + SizedBox(width: 20.rpx), + // 右侧三个容器(从右向左滑动) + Expanded( + child: AnimatedBuilder( + animation: + _animationController, + builder: (context, child) { + return SlideTransition( + position: + _rightSlideAnimation, + child: Column( + children: [ + DeviceStatusInfoWidget( + title: "体动".tr, + iconAsset: + "assets/img/icon/bodymotion.svg", + value: inBed == + "离床".tr + ? ("-") + : (bodyMotion == + null || + bodyMotion == + -1) + ? "-" + : "$bodyMotion", + ), + SizedBox( + height: 49.rpx), + DeviceStatusInfoWidget( + title: "打鼾".tr, + iconAsset: + "assets/img/icon/snore.svg", + value: inBed == + "离床".tr + ? "-" + : ('${snores}' + .tr), + ), + SizedBox( + height: 49.rpx), + DeviceStatusInfoWidget( + title: "呼吸暂停".tr, + iconAsset: + "assets/img/icon/breathe_pause.svg", + value: inBed == + "离床".tr + ? "-" + : ('${breathState}'), + ), + ], + ), + ); + }, + ), + ), + ], + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0.rpx, 67.rpx, 0.rpx, 0.rpx), child: Container( - width: 23.rpx, - height: 23.rpx, - // width: double.infinity, - decoration: BoxDecoration(), - child: SvgPicture.asset( - 'assets/img/icon/tips.svg', - fit: BoxFit.cover, - color: themeController - .currentColor.sc4, + height: 40.rpx, + child: Text( + bodyMotion >= maxBodyMotion + ? '请保持静止'.tr + : "", + style: TextStyle( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc9, + ), ), ), ), - Expanded( - child: Text( - '使用方式:人员使用健康快检功能时,只需平躺或坐在正常运行中的体征传感器传感器上方,保持静止,然后点击“启动快检”,待进度条完成,即可得出快检报告。' - .tr, - style: TextStyle( - fontFamily: 'Inter', - letterSpacing: 0.0, - color: themeController - .currentColor.sc4, - ), - ), - ), - ].divide(SizedBox(width: 23.rpx)), + ], ), ), - ), - SizedBox( - height: 40.rpx, - ), - if (deviceTypeController - .experience_status.value != - 200 || - !deviceTypeController.own.value) - Padding( - padding: EdgeInsets.fromLTRB( - 100.rpx, 0, 100.rpx, 0), - child: Column( - children: [ - CustomCard( - borderRadius: AppConstants() - .button_container_radius, // 圆角半径 - onTap: () async { - bool canStart = - await deviceTypeController - .checkReportStatus( - widget.personInfo['mac']); - // if (!canStart) { - // NewTopSlideNotification.show( - // text: "设备正在快检中,请稍后再试!".tr, - // textColor: themeController - // .currentColor.sc9); - // return; - // } + ) + else + // 未体验状态 - 占位空白 + Expanded(child: Container()), - if (!deviceTypeController.own.value && + // 底部区域 - 未体验显示说明和开始按钮,体验中显示进度条和终止按钮(无动画) + if (!isExperiencing) + Column( + children: [ + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.transparent, + borderRadius: 16.rpx, + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 0.rpx, 30.rpx, 0.rpx), + onTap: () {}, + child: Container( + padding: EdgeInsetsDirectional.fromSTEB( + 26.rpx, 26.rpx, 26.rpx, 26.rpx), + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(16.rpx), + border: Border.all( + color: themeController + .currentColor.sc4 + .withOpacity(0.5), + width: 0.5.rpx, + ), + ), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional + .fromSTEB(0, 8.rpx, 0, 0), + child: Container( + width: 23.rpx, + height: 23.rpx, + decoration: BoxDecoration(), + child: SvgPicture.asset( + 'assets/img/icon/tips.svg', + fit: BoxFit.cover, + color: themeController + .currentColor.sc4, + ), + ), + ), + Expanded( + child: Text( + '使用方式:人员使用健康快检功能时,只需平躺或坐在正常运行中的体征传感器传感器上方,保持静止,然后点击“启动快检”,待进度条完成,即可得出快检报告。' + .tr, + style: TextStyle( + fontFamily: 'Inter', + letterSpacing: 0.0, + color: themeController + .currentColor.sc4, + ), + ), + ), + ].divide(SizedBox(width: 23.rpx)), + ), + ), + ), + SizedBox(height: 40.rpx), + Padding( + padding: EdgeInsets.fromLTRB( + 100.rpx, 0, 100.rpx, 0), + child: Column( + children: [ + CustomCard( + borderRadius: AppConstants() + .button_container_radius, + onTap: () async { + bool opRes = + await deviceTypeController + .qcCheckControl( + widget.personInfo, 1); + if (!opRes) { + return; + } deviceTypeController - .experience_status - .value == - APPQuickCheckStatus - .inProgress.value) { - NewTopSlideNotification.show( - text: "设备正在快检中,请稍后再试!".tr, - textColor: themeController - .currentColor.sc9); - return; - } - bool opRes = - await deviceTypeController - .qcCheckControl( - widget.personInfo, 1); - if (!opRes) { - return; - } - - deviceTypeController - .experience_percent.value = 0; - progressNotifier.value = 0; - deviceTypeController - .experience_status.value = 200; - _startCheckStatusTimer(); - deviceTypeController.updateAll(); - }, - colors: AppConstants() - .thNormalButton, // 渐变色是同一个色,也可以根据需要调整 - child: Container( - width: - // MediaQuery.sizeOf(context).width * 0.66, - bodySize.maxWidth, - height: MediaQuery.sizeOf(context) - .height * - 0.055, - constraints: BoxConstraints( - minWidth: 500.rpx, - minHeight: 90.rpx, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Text( - '开始快检'.tr, - style: TextStyle( - color: themeController - .currentColor.sc3, - fontFamily: 'Inter', - fontSize: AppConstants() - .normal_text_fontSize, - letterSpacing: 0.0, - ), + .experience_percent.value = 0; + progressNotifier.value = 0; + deviceTypeController + .experience_status + .value = 200; + _startCheckStatusTimer(); + deviceTypeController.updateAll(); + }, + colors: + AppConstants().thNormalButton, + child: Container( + width: bodySize.maxWidth, + height: MediaQuery.sizeOf(context) + .height * + 0.055, + constraints: BoxConstraints( + minWidth: 500.rpx, + minHeight: 90.rpx, ), - ].divide(SizedBox( - width: 17.rpx, - )), - ), - ), - ), - ], - ), - ), - if (deviceTypeController - .experience_status.value == - 200 && - deviceTypeController.own.value) - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 100.rpx, 0.rpx, 100.rpx, 60.rpx), - child: CalibrationProgressWidget( - progressNotifier: progressNotifier, - failureNotifier: failureNotifier, - ), - ), - if (deviceTypeController - .experience_status.value == - 200 && - deviceTypeController.own.value) - Padding( - padding: EdgeInsets.fromLTRB( - 100.rpx, 0, 100.rpx, 0), - child: Column( - children: [ - CustomCard( - borderRadius: AppConstants() - .button_container_radius, // 圆角半径 - onTap: () { - _showCancelConfirmDialog(); - }, - colors: [ - themeController.currentColor.sc9 - ], // 渐变色是同一个色,也可以根据需要调整 - child: Container( - width: - // MediaQuery.sizeOf(context).width * 0.66, - bodySize.maxWidth, - height: MediaQuery.sizeOf(context) - .height * - 0.055, - constraints: BoxConstraints( - minWidth: 500.rpx, - minHeight: 90.rpx, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Text( - '终止'.tr, - style: TextStyle( - color: themeController - .currentColor.sc3, - fontFamily: 'Inter', - fontSize: AppConstants() - .normal_text_fontSize, - letterSpacing: 0.0, - ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Text( + '开始快检'.tr, + style: TextStyle( + color: themeController + .currentColor.sc3, + fontFamily: 'Inter', + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0.0, + ), + ), + ].divide( + SizedBox(width: 17.rpx)), ), - ].divide(SizedBox( - width: 17.rpx, - )), + ), ), - ), + ], ), - ], - ), + ), + SizedBox(height: 40.rpx), + SizedBox(height: 26.rpx), + ], + ) + else + Column( + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 100.rpx, 0.rpx, 100.rpx, 60.rpx), + child: CalibrationProgressWidget( + progressNotifier: progressNotifier, + failureNotifier: failureNotifier, + ), + ), + Padding( + padding: EdgeInsets.fromLTRB( + 100.rpx, 0, 100.rpx, 0), + child: Column( + children: [ + CustomCard( + borderRadius: AppConstants() + .button_container_radius, + onTap: () { + showCancelConfirmDialog( + context, widget.personInfo); + }, + colors: [ + themeController.currentColor.sc9 + ], + child: Container( + width: bodySize.maxWidth, + height: MediaQuery.sizeOf(context) + .height * + 0.055, + constraints: BoxConstraints( + minWidth: 500.rpx, + minHeight: 90.rpx, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Text( + '终止'.tr, + style: TextStyle( + color: themeController + .currentColor.sc3, + fontFamily: 'Inter', + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0.0, + ), + ), + ].divide( + SizedBox(width: 17.rpx)), + ), + ), + ), + ], + ), + ), + SizedBox(height: 40.rpx), + SizedBox(height: 26.rpx), + ], ), - SizedBox( - height: 40.rpx, - ), - SizedBox( - height: 26.rpx, - ), ], ), ), @@ -1174,229 +2556,4 @@ class _HealthCheckPageState extends State ), ); } - - void _closeWebSocket() { - // 取消WebSocket订阅 - edm.EasyDartModule.websocket.sendData( - jsonEncode(WebSocketMessage(path: "/vsbs/web/rt/marttress", type: 2))); - _onlineTimer?.cancel(); - } - - String _getGenderText(dynamic gender) { - var genderMap = { - '1': '男'.tr, - '2': '女'.tr, - }; - - String genderStr = gender.toString().trim(); - return genderMap[genderStr] ?? '-'.tr; - } - - String _calculateAge(String birthdayStr) { - try { - // 解析生日字符串 (格式: yyyy/MM/dd) - List parts = birthdayStr.trim().split('/'); - if (parts.length != 3) return '-'.tr; - - int year = int.parse(parts[0]); - int month = int.parse(parts[1]); - int day = int.parse(parts[2]); - - DateTime birthDate = DateTime(year, month, day); - DateTime today = DateTime.now(); - - // 计算年龄 - int age = today.year - birthDate.year; - - // 如果今年还没过生日,年龄减1 - if (today.month < birthDate.month || - (today.month == birthDate.month && today.day < birthDate.day)) { - age--; - } - - return age.toString(); - } catch (e) { - return '-'.tr; - } - } - - // 显示解绑确认对话框 - void _showCancelConfirmDialog() { - showConfirmDialog( - context, - Container(), - "是否确认结束?".tr, - onConfirm: () async { - bool opRes = - await deviceTypeController.qcCheckControl(widget.personInfo, 2); - if (!opRes) { - return; - } - deviceTypeController.experience_status.value = 404; - deviceTypeController.updateAll(); - }, - onCancel: () {}, - ); - } - - // 开始状态查询定时器 - void _startCheckStatusTimer() { - _checkStatusTimer?.cancel(); // 取消之前的定时器 - - _checkStatusTimer = Timer.periodic(Duration(seconds: 3), (timer) { - if (mounted) { - edm.EasyDartModule.logger.info("定时查询快检状态"); - deviceTypeController - .checkReportStatus(widget.personInfo['mac']) - .then((_) async { - // 如果状态变回404(非体验中),停止定时器 - progressNotifier.value = - deviceTypeController.experience_percent.value.toDouble(); - deviceTypeController.updateAll(); - if (deviceTypeController.experience_status.value == - APPQuickCheckStatus.completed.value) { - //体验正常结束 - try { - deviceTypeController.experience_status.value = 404; - deviceTypeController.experience_status.value = 0; - progressNotifier.value = 0; - edm.EasyDartModule.logger.info("快检结束,停止定时查询"); - _checkStatusTimer?.cancel(); - Map data = { - "id": deviceTypeController.experience_id.value, - "mac": widget.personInfo['mac'] - }; - // await deviceTypeController.getCheckHistory( - // id: deviceTypeController.experience_id.value, - // mac: widget.personInfo['mac']); - Get.toNamed('/healthQuickCheckReportPage', arguments: data); - } catch (e) { - edm.EasyDartModule.logger.error("快检报告页面加载失败---?${e.toString()}"); - } - } - if (deviceTypeController.experience_status.value == - APPQuickCheckStatus.timeout.value || - deviceTypeController.experience_status.value == - APPQuickCheckStatus.calculateError.value || - deviceTypeController.experience_status.value == - APPQuickCheckStatus.insufficientData.value) { - // 根据状态码获取对应的枚举 - APPQuickCheckStatus? status = APPQuickCheckStatusExtension.fromInt( - deviceTypeController.experience_status.value); - - // 获取对应的状态描述文字 - String statusMessage = status?.description ?? "体验异常结束".tr; - - //体验异常结束 - deviceTypeController.experience_status.value = 404; - deviceTypeController.experience_percent.value = 0; - progressNotifier.value = 0; - edm.EasyDartModule.logger - .info("快检结束,停止定时查询 - 状态: ${status?.description}"); - _checkStatusTimer?.cancel(); - // NewTopSlideNotification.show( - // text: statusMessage, - // textColor: themeController.currentColor.sc9); - await showTipDialog( - context, - Text( - statusMessage, - style: TextStyle( - color: Colors.white, - ), - ), - ); - } - if (!deviceTypeController.own.value) { - _checkStatusTimer?.cancel(); - } - }); - } - }); - } -} - -class SpeedControlledGif extends StatefulWidget { - final String assetPath; - final double speedFactor; - final BoxFit? fit; - - /// [speedFactor] 播放速度倍数,默认1.0, - /// 大于1表示加速播放,小于1表示减速播放 - const SpeedControlledGif( - this.assetPath, { - Key? key, - this.speedFactor = 1.0, - this.fit, - }) : super(key: key); - - @override - _SpeedControlledGifState createState() => _SpeedControlledGifState(); -} - -class _SpeedControlledGifState extends State { - ui.Codec? _codec; - ui.FrameInfo? _currentFrame; - Timer? _timer; - bool _isDisposed = false; - - @override - void initState() { - super.initState(); - _loadGif(); - } - - Future _loadGif() async { - final data = await rootBundle.load(widget.assetPath); - _codec = await ui.instantiateImageCodec(data.buffer.asUint8List()); - - if (_isDisposed) return; - - _showNextFrame(); - } - - Future _showNextFrame() async { - if (_isDisposed || _codec == null) return; - - _currentFrame = await _codec!.getNextFrame(); - - if (_isDisposed) return; - - if (mounted) { - setState(() {}); - } - - // 取当前帧持续时间,单位毫秒 - final baseDuration = _currentFrame?.duration.inMilliseconds ?? 100; - - // 限制最小帧间隔,防止刷新过快 - const minFrameDuration = 50; - - // 计算实际播放帧间隔,speedFactor越大速度越快 - final adjustedDuration = (baseDuration / widget.speedFactor) - .round() - .clamp(minFrameDuration, 10000); - - _timer = Timer(Duration(milliseconds: adjustedDuration), _showNextFrame); - } - - @override - void dispose() { - _isDisposed = true; - _timer?.cancel(); - _codec?.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - if (_currentFrame == null) { - // 加载中或无帧时显示空容器或占位 - return Container(); - } - return RawImage( - image: _currentFrame!.image, - fit: widget.fit, - ); - } } diff --git a/lib/pages/mh_page/MattressControl.dart b/lib/pages/mh_page/MattressControl.dart index bdc13df..701db1e 100644 --- a/lib/pages/mh_page/MattressControl.dart +++ b/lib/pages/mh_page/MattressControl.dart @@ -1,4 +1,5 @@ import 'dart:ui'; +import 'dart:io'; // 添加这个导入 import 'package:ef/ef.dart'; import 'package:flutter/material.dart'; @@ -22,6 +23,18 @@ class _MattressControlPageState extends State { @override Widget build(BuildContext context) { + // 如果是 iOS 平台,使用 PopScope 禁用左滑返回 + if (Platform.isIOS) { + return PopScope( + canPop: false, // 禁用返回手势 + child: buildContent(), + ); + } + return buildContent(); + } + + // 将原有的 build 内容提取到这个方法中 + Widget buildContent() { return LayoutBuilder( builder: (context, bodySize) => GestureDetector( child: Obx(() { @@ -73,4 +86,4 @@ class _MattressControlPageState extends State { class ControlCardController extends GetxController { final List switchStates = List.generate(9, (index) => false.obs); -} +} \ No newline at end of file diff --git a/lib/pages/sleep_report/chart/QcTimeSeriesChart.dart b/lib/pages/sleep_report/chart/QcTimeSeriesChart.dart index c222dc4..1ec4672 100644 --- a/lib/pages/sleep_report/chart/QcTimeSeriesChart.dart +++ b/lib/pages/sleep_report/chart/QcTimeSeriesChart.dart @@ -14,10 +14,12 @@ // class QcTimeSeriesChart extends StatelessWidget { // final List dataPoints; // final double yMin; -// final double yMax; +// final double yMax; // 注意:这个值在使用时会自动加 padding // final int xSegmentCount; // final double? baseValue; // 基准值,可选 // final String? baseLabel; // 基准值标签,可选 +// final double yAxisPadding; // Y轴顶部留白,默认为20 +// final double yAxisMargin; // Y轴上下边距,默认为2 // const QcTimeSeriesChart({ // Key? key, @@ -27,16 +29,21 @@ // this.xSegmentCount = 11, // this.baseValue, // this.baseLabel, +// this.yAxisPadding = 20.0, // 默认为20 +// this.yAxisMargin = 2.0, // Y轴上下边距,默认为2 // }) : super(key: key); +// // 添加一个 getter 来获取实际使用的 yMax(自动加 padding) +// double get _actualYMax => yMax + yAxisPadding; + // int get _dataPointCount => dataPoints.length; // List _generateYAxisTicks() { -// if (yMin >= yMax) { +// if (yMin >= _actualYMax) { // return [0, 20, 40, 60, 80, 100]; // } -// double step = (yMax - yMin) / 5; +// double step = (_actualYMax - yMin) / 5; // List ticks = []; // for (int i = 0; i <= 5; i++) { @@ -114,7 +121,7 @@ // Widget build(BuildContext context) { // // final yTicks = _generateYAxisTicks(); // final double xMax = _dataPointCount.toDouble(); -// final double yRange = yMax - yMin; +// final double yRange = _actualYMax - yMin; // final double yInterval = yRange > 0 ? yRange / 5 : 20.0; // final tickIndices = _getTickIndices(); @@ -229,8 +236,8 @@ // LineChartData( // minX: 1, // maxX: xMax + 0.5, -// minY: yMin - 2, -// maxY: yMax + 2, +// minY: yMin - yAxisMargin, +// maxY: _actualYMax + yAxisMargin, // gridData: FlGridData(show: false), // extraLinesData: ExtraLinesData( // horizontalLines: horizontalLines, @@ -333,7 +340,7 @@ // // 使用 Positioned 来绘制最大值和最小值的标签 // if (minMaxData['minIndex'] != -1 || minMaxData['maxIndex'] != -1) // _buildMinMaxLabels( -// context, constraints, minMaxData, xMax, yMin, yMax), +// context, constraints, minMaxData, xMax, yMin, _actualYMax), // ], // ); // }, @@ -360,11 +367,11 @@ // double chartHeight = constraints.maxHeight - topPadding - bottomPadding; // // X轴范围:1 到 xMax -// // Y轴范围:yMin - 2 到 yMax + 2 +// // Y轴范围:yMin - yAxisMargin 到 yMax + yAxisMargin // double xMin = 1; // double xMaxValue = xMax; -// double yMinValue = yMin - 2; -// double yMaxValue = yMax + 2; +// double yMinValue = yMin - yAxisMargin; +// double yMaxValue = yMax + yAxisMargin; // double xRange = xMaxValue - xMin; // double yRange = yMaxValue - yMinValue; @@ -400,14 +407,12 @@ // children: [ // // SVG图片作为背景 // SvgPicture.asset( -// 'assets/img/icon/location.svg', // 替换为你的SVG图片路径 -// // width: 28.rpx, -// // height: 40.rpx, +// 'assets/img/icon/location.svg', // fit: BoxFit.contain, // color: stringToColor("#d69dd2"), // ), // Padding( -// padding: EdgeInsets.only(bottom: 12.rpx), // 调整这个值来控制向上移动的距离 +// padding: EdgeInsets.only(bottom: 12.rpx), // child: Text( // '${minMaxData['minValue'].toStringAsFixed(0)}', // style: TextStyle( @@ -453,23 +458,12 @@ // children: [ // // SVG图片作为背景 // SvgPicture.asset( -// 'assets/img/icon/location.svg', // 替换为你的SVG图片路径 -// // width: 28.rpx, -// // height: 40.rpx, +// 'assets/img/icon/location.svg', // fit: BoxFit.contain, // color: stringToColor("#FF9F66"), // ), -// // 文字覆盖在SVG上 -// // Text( -// // '${minMaxData['maxValue'].toStringAsFixed(0)}', -// // style: TextStyle( -// // color: Colors.white, -// // fontSize: AppConstants().smaller_text_fontSize, -// // // fontWeight: FontWeight.bold, -// // ), -// // ), // Padding( -// padding: EdgeInsets.only(bottom: 12.rpx), // 调整这个值来控制向上移动的距离 +// padding: EdgeInsets.only(bottom: 12.rpx), // child: Text( // '${minMaxData['maxValue'].toStringAsFixed(0)}', // style: TextStyle( @@ -512,6 +506,7 @@ class QcTimeSeriesChart extends StatelessWidget { final double? baseValue; // 基准值,可选 final String? baseLabel; // 基准值标签,可选 final double yAxisPadding; // Y轴顶部留白,默认为20 + final double yAxisMargin; // Y轴上下边距,默认为2 const QcTimeSeriesChart({ Key? key, @@ -522,6 +517,7 @@ class QcTimeSeriesChart extends StatelessWidget { this.baseValue, this.baseLabel, this.yAxisPadding = 20.0, // 默认为20 + this.yAxisMargin = 2.0, // Y轴上下边距,默认为2 }) : super(key: key); // 添加一个 getter 来获取实际使用的 yMax(自动加 padding) @@ -618,6 +614,10 @@ class QcTimeSeriesChart extends StatelessWidget { final tickIndices = _getTickIndices(); final minMaxData = _findMinMax(); + // 计算实际显示的Y轴范围 + final double actualMinY = yMin - yAxisMargin; + final double actualMaxY = _actualYMax + yAxisMargin; + // 将数据点分割成多个连续段 List> lineSegments = []; List currentSegment = []; @@ -727,8 +727,8 @@ class QcTimeSeriesChart extends StatelessWidget { LineChartData( minX: 1, maxX: xMax + 0.5, - minY: yMin - 2, - maxY: _actualYMax + 2, + minY: actualMinY, + maxY: actualMaxY, gridData: FlGridData(show: false), extraLinesData: ExtraLinesData( horizontalLines: horizontalLines, @@ -776,6 +776,19 @@ class QcTimeSeriesChart extends StatelessWidget { reservedSize: 60.rpx, interval: yInterval, getTitlesWidget: (value, meta) { + // 不显示最小值和最大值 + // 判断是否是实际显示范围的最小值或最大值 + if (value == actualMinY || value == actualMaxY) { + return const SizedBox.shrink(); + } + + // 检查值是否为整数(避免显示小数) + double roundedValue = value.roundToDouble(); + if ((value - roundedValue).abs() > 0.01) { + // 如果不是整数,不显示 + return const SizedBox.shrink(); + } + return Padding( padding: const EdgeInsets.only(right: 8.0), child: Text( @@ -858,11 +871,11 @@ class QcTimeSeriesChart extends StatelessWidget { double chartHeight = constraints.maxHeight - topPadding - bottomPadding; // X轴范围:1 到 xMax - // Y轴范围:yMin - 2 到 yMax + 2 + // Y轴范围:yMin - yAxisMargin 到 yMax + yAxisMargin double xMin = 1; double xMaxValue = xMax; - double yMinValue = yMin - 2; - double yMaxValue = yMax + 2; + double yMinValue = yMin - yAxisMargin; + double yMaxValue = yMax + yAxisMargin; double xRange = xMaxValue - xMin; double yRange = yMaxValue - yMinValue; diff --git a/lib/pages/sleep_report/qc_report/QcBreatheStandardWidget.dart b/lib/pages/sleep_report/qc_report/QcBreatheStandardWidget.dart index 866495c..c124e0c 100644 --- a/lib/pages/sleep_report/qc_report/QcBreatheStandardWidget.dart +++ b/lib/pages/sleep_report/qc_report/QcBreatheStandardWidget.dart @@ -110,19 +110,19 @@ class _QcBreatheStandardWidgetState extends State { }; Map baseBreath = { - 'name': '基础呼吸', + 'name': '基准呼吸'.tr, 'value': brData['base'].toInt() ?? 0, 'unit': '次/分', }; Map minBreath = { - 'name': '最低呼吸', + 'name': '最低呼吸'.tr, 'value': brData['min'].toInt() ?? 0, 'unit': '次/分', }; Map maxBreath = { - 'name': '最高呼吸', + 'name': '最高呼吸'.tr, 'value': brData['max'].toInt() ?? 0, 'unit': '次/分', }; @@ -260,6 +260,7 @@ class _QcBreatheStandardWidgetState extends State { // baseValue: 16, // 传入基准值 baseLabel: '基准', // 可选的自定义标签 yAxisPadding: 0, + ), ), ].divide(SizedBox( diff --git a/lib/pages/sleep_report/qc_report/QcHeartRateStandardWidget.dart b/lib/pages/sleep_report/qc_report/QcHeartRateStandardWidget.dart index a79a147..ba39c47 100644 --- a/lib/pages/sleep_report/qc_report/QcHeartRateStandardWidget.dart +++ b/lib/pages/sleep_report/qc_report/QcHeartRateStandardWidget.dart @@ -267,7 +267,8 @@ class _QcHeartRateStandardWidgetState extends State { baseValue: hrData['base'].toDouble(), // 传入基准值 // baseValue: 65, // 传入基准值 baseLabel: '基准', // 可选的自定义标签 - yAxisPadding: 20, + yAxisPadding: 0, + yAxisMargin: 5, ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index be31b03..b331644 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -92,6 +92,7 @@ dependencies: network_info_plus: ^5.0.1 # wifi_info_flutter: ^2.0.2 shelf_static: ^1.1.3 + vibration: ^3.1.8 dev_dependencies: