Files
tuiche/lib/pages/device/health_experience.dart

1403 lines
74 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import '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<HealthCheckPage> createState() => _HealthCheckPageState();
}
class _HealthCheckPageState extends State<HealthCheckPage>
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<double> progressNotifier = ValueNotifier<double>(0.0);
final ValueNotifier<bool> failureNotifier = ValueNotifier<bool>(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<void> _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: [
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 {
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<String> 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<SpeedControlledGif> {
ui.Codec? _codec;
ui.FrameInfo? _currentFrame;
Timer? _timer;
bool _isDisposed = false;
@override
void initState() {
super.initState();
_loadGif();
}
Future<void> _loadGif() async {
final data = await rootBundle.load(widget.assetPath);
_codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
if (_isDisposed) return;
_showNextFrame();
}
Future<void> _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,
);
}
}