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_bind/componnet/CalibrationProgressWidget.dart'; import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.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: () { Get.toNamed('/healthExperienceHistory', arguments: widget.personInfo); // Get.toNamed('/healthQuickCheckReportPage', // 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: [ // Row( // mainAxisSize: MainAxisSize.max, // children: [ // Flexible( // flex: 2, // child: Column( // crossAxisAlignment: // CrossAxisAlignment.start, // children: [ // Row( // children: [ // Column( // crossAxisAlignment: // CrossAxisAlignment // .end, // children: [ // Text( // '实时体征.姓名'.tr, // style: TextStyle( // fontFamily: // 'Inter', // fontSize: 26.rpx, // letterSpacing: // 0.0, // color: themeController // .currentColor // .sc4, // ), // ), // ].divide(SizedBox( // height: 34.rpx)), // ), // Column( // crossAxisAlignment: // CrossAxisAlignment // .start, // children: [ // Container( // width: MediaQuery // .sizeOf( // context) // .width * // 0.2, // 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, // letterSpacing: // 0.0, // color: themeController // .currentColor // .sc3, // ), // maxLines: 1, // overflow: // TextOverflow // .ellipsis, // ), // ), // ].divide(SizedBox( // height: 34.rpx)), // ), // ] // .divide(SizedBox( // width: 33.rpx)) // .addToStart(SizedBox( // width: 37.rpx)), // ), // Row( // children: [ // Column( // crossAxisAlignment: // CrossAxisAlignment // .end, // children: [ // Text( // '性别'.tr, // style: TextStyle( // fontFamily: // 'Inter', // fontSize: 26.rpx, // letterSpacing: // 0.0, // color: themeController // .currentColor // .sc4, // ), // ), // ].divide(SizedBox( // height: 34.rpx)), // ), // Column( // crossAxisAlignment: // CrossAxisAlignment // .start, // children: [ // Container( // width: MediaQuery // .sizeOf( // context) // .width * // 0.2, // 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, // letterSpacing: // 0.0, // color: themeController // .currentColor // .sc3, // ), // maxLines: 1, // overflow: // TextOverflow // .ellipsis, // ), // ), // ].divide(SizedBox( // height: 34.rpx)), // ), // ] // .divide(SizedBox( // width: 33.rpx)) // .addToStart(SizedBox( // width: 37.rpx)), // ), // Row( // children: [ // Column( // crossAxisAlignment: // CrossAxisAlignment // .end, // children: [ // Text( // '身高'.tr, // style: TextStyle( // fontFamily: // 'Inter', // fontSize: 26.rpx, // letterSpacing: // 0.0, // color: themeController // .currentColor // .sc4, // ), // ), // ].divide(SizedBox( // height: 34.rpx)), // ), // Column( // crossAxisAlignment: // CrossAxisAlignment // .start, // children: [ // Container( // width: MediaQuery // .sizeOf( // context) // .width * // 0.2, // child: Text( // device['person'] != null && // device['person'] // [ // 'height'] != // null && // device['person'] // [ // 'height'] // .toString() // .trim() // .isNotEmpty // ? device['person'] // [ // 'height'] // .toString() + // "cm" // : '-'.tr + // "cm", // style: TextStyle( // fontFamily: // 'Inter', // fontSize: // 26.rpx, // letterSpacing: // 0.0, // color: themeController // .currentColor // .sc3, // ), // maxLines: 1, // overflow: // TextOverflow // .ellipsis, // ), // ), // ].divide(SizedBox( // height: 34.rpx)), // ), // ] // .divide(SizedBox( // width: 33.rpx)) // .addToStart(SizedBox( // width: 37.rpx)), // ), // ] // .addToStart( // SizedBox(height: 0.rpx)) // .addToEnd( // SizedBox(height: 0.rpx)) // .divide(SizedBox( // height: 36.rpx, // )), // ), // ), // Flexible( // flex: 3, // child: Column( // crossAxisAlignment: // CrossAxisAlignment.start, // children: [ // Row( // children: [ // Column( // crossAxisAlignment: // CrossAxisAlignment // .end, // children: [ // Text( // '实时体征.设备ID'.tr, // style: TextStyle( // fontFamily: // 'Inter', // fontSize: 26.rpx, // letterSpacing: // 0.0, // color: themeController // .currentColor // .sc4, // ), // ), // ].divide(SizedBox( // height: 34.rpx)), // ), // Expanded( // child: Column( // crossAxisAlignment: // CrossAxisAlignment // .start, // children: [ // Text( // '${device['code'] ?? '未知数据'.tr}', // // "D11250300003", // style: TextStyle( // fontFamily: // 'Inter', // fontSize: // 26.rpx, // letterSpacing: // 0.0, // color: themeController // .currentColor // .sc3, // ), // maxLines: 1, // overflow: // TextOverflow // .ellipsis, // ), // ].divide(SizedBox( // height: 34.rpx)), // ), // ), // ] // .divide(SizedBox( // width: 33.rpx)) // .addToStart(SizedBox( // width: 37.rpx)), // ), // Row( // children: [ // Column( // crossAxisAlignment: // CrossAxisAlignment // .end, // children: [ // Text( // '年龄'.tr, // style: TextStyle( // fontFamily: // 'Inter', // fontSize: 26.rpx, // letterSpacing: // 0.0, // color: themeController // .currentColor // .sc4, // ), // ), // ].divide(SizedBox( // height: 34.rpx)), // ), // Expanded( // child: Column( // crossAxisAlignment: // CrossAxisAlignment // .start, // children: [ // 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, // letterSpacing: // 0.0, // color: themeController // .currentColor // .sc3, // ), // maxLines: 1, // overflow: // TextOverflow // .ellipsis, // ), // ].divide(SizedBox( // height: 34.rpx)), // ), // ), // ] // .divide(SizedBox( // width: 33.rpx)) // .addToStart(SizedBox( // width: 37.rpx)), // ), // Row( // children: [ // Column( // crossAxisAlignment: // CrossAxisAlignment // .end, // children: [ // Text( // '体重'.tr, // style: TextStyle( // fontFamily: // 'Inter', // fontSize: 26.rpx, // letterSpacing: // 0.0, // color: themeController // .currentColor // .sc4, // ), // ), // // Text( // // '实时体征.体重'.tr, // // style: TextStyle( // // fontFamily: 'Inter', // // fontSize: 26.rpx, // // letterSpacing: 0.0, // // color: themeController // // .currentColor.sc4, // // ), // // ), // ].divide(SizedBox( // height: 34.rpx)), // ), // Expanded( // child: Column( // crossAxisAlignment: // CrossAxisAlignment // .start, // children: [ // Text( // device['person'] != null && // device['person'] // [ // 'weight'] != // null && // device['person'] // [ // 'weight'] // .toString() // .trim() // .isNotEmpty // ? device['person'] // [ // 'weight'] // .toString() + // "kg" // : '-'.tr + // "kg", // style: TextStyle( // fontFamily: // 'Inter', // fontSize: // 26.rpx, // letterSpacing: // 0.0, // color: themeController // .currentColor // .sc3, // ), // maxLines: 1, // overflow: // TextOverflow // .ellipsis, // ), // ].divide(SizedBox( // height: 34.rpx)), // ), // ), // ] // .divide(SizedBox( // width: 33.rpx)) // .addToStart(SizedBox( // width: 37.rpx)), // ), // ] // .addToStart( // SizedBox(height: 0.rpx)) // .addToEnd( // SizedBox(height: 0.rpx)) // .divide(SizedBox( // height: 36.rpx, // )), // ), // ), // ], // ), Container( padding: EdgeInsets.fromLTRB( 37.rpx, 37.rpx, 37.rpx, 37.rpx), child: Row( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, children: [ // 左侧列 - 标签和值 Expanded( flex: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 姓名行 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'][ '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: 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'] [ '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: 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'] [ '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: 3, 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 { 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(); }, 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(); } 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) { //体验正常结束 deviceTypeController.experience_status.value = 404; deviceTypeController.experience_status.value = 0; progressNotifier.value = 0; edm.EasyDartModule.logger.info("快检结束,停止定时查询"); _checkStatusTimer?.cancel(); await deviceTypeController.getCheckHistory( id: deviceTypeController.experience_id.value,mac: widget.personInfo['mac']); Get.toNamed('/healthQuickCheckReportPage', arguments: deviceTypeController.currentCq.value); } // if (deviceTypeController.experience_status.value != APPQuickCheckStatus.inProgress.value && // deviceTypeController.experience_status.value != 201 && // deviceTypeController.experience_status.value != 404 && // deviceTypeController.experience_status.value != 203) { // //体验异常结束 // deviceTypeController.experience_status.value = 404; // deviceTypeController.experience_percent.value = 0; // progressNotifier.value = 0; // edm.EasyDartModule.logger.info("快检结束,停止定时查询"); // _checkStatusTimer?.cancel(); // // NewTopSlideNotification.show( // // text: // // "体验异常结束".tr, // // textColor: themeController.currentColor.sc9); // } 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, ); } }