twelve_ecg.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:fis_common/logger/logger.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:get/get.dart';
  6. import 'package:vitalapp/architecture/utils/prompt_box.dart';
  7. import 'package:vitalapp/components/button.dart';
  8. import 'package:vitalapp/global.dart';
  9. import 'package:vitalapp/managers/device_controller_manager.dart';
  10. import 'package:vitalapp/managers/interfaces/data_convert.dart';
  11. import 'package:vitalapp/managers/interfaces/models/device.dart';
  12. import 'package:vitalapp/pages/medical/widgets/exam_card.dart';
  13. import 'package:vitalapp/pages/medical/controller.dart';
  14. import 'package:vitalapp/pages/medical/models/twelve_ecg.dart';
  15. import 'package:vitalapp/pages/medical/models/worker.dart';
  16. import 'package:vitalapp/pages/medical/widgets/device_status_position.dart';
  17. import 'package:vitalapp/pages/medical/widgets/twelve_ecg_view/controller.dart';
  18. import 'package:vitalapp/pages/medical/widgets/twelve_ecg_view/view.dart';
  19. import 'package:vitalapp/pages/medical/widgets/twelve_ecg_view/widgets/conclusion_dialog.dart';
  20. import 'package:vitalapp/pages/medical/widgets/twelve_ecg_view/widgets/count_down_page.dart';
  21. import 'package:vitalapp/pages/medical/widgets/twelve_ecg_view/widgets/ecg_device_status.dart';
  22. import 'package:vnote_device_plugin/consts/types.dart';
  23. import 'package:http/http.dart' as http;
  24. import 'package:vnote_device_plugin/devices/twelve_heart.dart';
  25. import 'package:vnote_device_plugin/models/exams/twelve_heart.dart';
  26. class TwelveHeartRate extends StatefulWidget {
  27. const TwelveHeartRate({
  28. super.key,
  29. });
  30. @override
  31. State<TwelveHeartRate> createState() => _HeartRateState();
  32. }
  33. class _HeartRateState extends State<TwelveHeartRate> {
  34. final MedicalController medicalController = Get.find<MedicalController>();
  35. final IDataConvertManager dataConvertManager =
  36. Get.find<IDataConvertManager>();
  37. DeviceControllerManager? twelveHeart;
  38. late TwelveHeartDeviceWorker worker;
  39. WorkerStatus connectStatus = WorkerStatus.connecting;
  40. TwelveEcgStatus twelveEcgStatus = TwelveEcgStatus.noSampling;
  41. List<int> ecgPoint = [];
  42. int errorCount = 0;
  43. bool isConnectFail = false;
  44. late String _heart = medicalController.diagnosisDataValue['TwelveHeart']
  45. ?['HEART12']
  46. ?.toString() ??
  47. '';
  48. late String _assess =
  49. medicalController.diagnosisDataValue['TwelveHeart']?['ASSESS12'] ?? '';
  50. late String _analyse12 =
  51. medicalController.diagnosisDataValue['TwelveHeart']?['Analyse12'] ?? '';
  52. /// 初始时的心电初始数据
  53. List<int>? initEcgData;
  54. @override
  55. Widget build(BuildContext context) {
  56. return Stack(
  57. children: [
  58. _buildEcgView(),
  59. DeviceStatusPosition(
  60. currentTop: 24,
  61. deviceStatus: EcgDeviceStatus(
  62. connectStatus: connectStatus,
  63. ),
  64. ),
  65. if (twelveEcgStatus == TwelveEcgStatus.unstableSampling)
  66. _buildUnstableSampling(),
  67. if (twelveEcgStatus == TwelveEcgStatus.stableSampling)
  68. _buildCountdownPage(),
  69. _buildOperateButton(),
  70. if (_assess.isNotEmpty)
  71. const Positioned(
  72. left: 16,
  73. bottom: 10,
  74. child: Text(
  75. "轻触屏幕查看心电图",
  76. style: TextStyle(
  77. fontSize: 24,
  78. color: Colors.black,
  79. ),
  80. ),
  81. ),
  82. ],
  83. );
  84. }
  85. Widget _buildEcgView() {
  86. return ExamCard(
  87. titleText: const SizedBox(),
  88. content: SizedBox(
  89. height: 620,
  90. // margin: const EdgeInsets.only(bottom: 10),
  91. child: Column(
  92. children: [
  93. Row(
  94. mainAxisAlignment: MainAxisAlignment.end,
  95. children: [
  96. const Expanded(child: SizedBox()),
  97. const Text(
  98. "心率",
  99. style: TextStyle(
  100. fontSize: 24,
  101. color: Colors.black,
  102. ),
  103. ),
  104. const SizedBox(
  105. width: 10,
  106. ),
  107. Text(
  108. _heart.isEmpty ? '--' : _heart,
  109. style: const TextStyle(
  110. fontSize: 60,
  111. color: Colors.black,
  112. ),
  113. ),
  114. const Text(
  115. " bpm",
  116. style: TextStyle(fontSize: 25),
  117. ),
  118. const SizedBox(
  119. width: 250,
  120. )
  121. ],
  122. ),
  123. Expanded(
  124. child: ListView(
  125. shrinkWrap: true,
  126. children: [
  127. SizedBox(
  128. height: 520,
  129. child: LayoutBuilder(builder: (context, constraints) {
  130. if (initEcgData == null) {
  131. return Container();
  132. } else {
  133. return TwelveEcgView(
  134. width: constraints.maxWidth,
  135. height: constraints.maxHeight,
  136. initData: initEcgData!,
  137. currentIndex: 0,
  138. isConclusion: _assess.isNotEmpty,
  139. );
  140. }
  141. }),
  142. ),
  143. ],
  144. ),
  145. ),
  146. ],
  147. ),
  148. ),
  149. );
  150. }
  151. Widget _buildUnstableSampling() {
  152. return const Positioned(
  153. left: 20,
  154. top: 24,
  155. child: Text(
  156. '波形正在稳定中',
  157. style: TextStyle(fontSize: 24, color: Colors.blue),
  158. ),
  159. );
  160. }
  161. Widget _buildCountdownPage() {
  162. return Positioned(
  163. left: 20,
  164. top: 24,
  165. child: Row(
  166. children: const [
  167. CountdownPage(
  168. seconds: 30,
  169. ),
  170. ],
  171. ),
  172. );
  173. }
  174. Widget _buildOperateButton() {
  175. return Positioned(
  176. left: 24,
  177. top: 24,
  178. child: Row(
  179. children: [
  180. if (twelveEcgStatus == TwelveEcgStatus.noSampling &&
  181. (connectStatus == WorkerStatus.connectionFailed ||
  182. connectStatus == WorkerStatus.disconnected ||
  183. connectStatus == WorkerStatus.boundDeviceFail)) ...[
  184. SizedBox(
  185. width: 150,
  186. height: 50,
  187. child: VButton(
  188. onTap: () =>
  189. connectStatus == WorkerStatus.connecting ? null : connect(),
  190. child: const Center(
  191. child: Text(
  192. "采样",
  193. style: TextStyle(fontSize: 24),
  194. ),
  195. ),
  196. ),
  197. ),
  198. const SizedBox(
  199. width: 20,
  200. ),
  201. ],
  202. if (_assess.isNotEmpty)
  203. SizedBox(
  204. width: 150,
  205. height: 50,
  206. child: VButton(
  207. onTap: () => _openConclusion(),
  208. child: const Center(
  209. child: Text(
  210. "查看结果",
  211. style: TextStyle(fontSize: 24),
  212. ),
  213. ),
  214. ),
  215. ),
  216. ],
  217. ),
  218. );
  219. }
  220. // 设备初始化
  221. Future<void> initDevice() async {
  222. DeviceModel? device =
  223. await medicalController.getDevice(DeviceTypes.TWELVEHEART);
  224. if (device == null) {
  225. connectStatus = WorkerStatus.unboundDevice;
  226. setState(() {});
  227. return;
  228. }
  229. twelveHeart = DeviceControllerManager(
  230. DeviceTypes.TWELVEHEART, device.model, device.mac);
  231. worker = twelveHeart!.worker as TwelveHeartDeviceWorker;
  232. connectStatus = WorkerStatus.disconnected;
  233. loadListeners();
  234. }
  235. /// 尝试重连
  236. Future<void> tryReconnect() async {
  237. await disconnect();
  238. await connect();
  239. setState(() {
  240. connectStatus = WorkerStatus.connecting;
  241. isConnectFail = false;
  242. });
  243. }
  244. Future<void> disconnect() async {
  245. await worker.disconnect();
  246. }
  247. Future<void> connect() async {
  248. connectStatus = WorkerStatus.connecting;
  249. isConnectFail = false;
  250. await worker.connect();
  251. setState(() {});
  252. }
  253. @override
  254. void dispose() {
  255. twelveHeart?.dispose();
  256. twelveHeart = null;
  257. releaseListeners();
  258. disconnect();
  259. worker.dispose();
  260. super.dispose();
  261. }
  262. /// 重置数据以及心电图
  263. void resetEcgView() {
  264. try {
  265. TwelveEcgViewController ecgViewController =
  266. Get.find<TwelveEcgViewController>();
  267. ecgViewController.isInitPoints = false;
  268. ecgViewController.allPoints = [];
  269. ecgViewController.reset();
  270. } catch (e) {
  271. logger.i('心率图测量失败:$e');
  272. }
  273. }
  274. /// 数据初始化
  275. Future<void> initData() async {
  276. if (medicalController.diagnosisDataValue['TwelveHeart'] == null) {
  277. medicalController.diagnosisDataValue['TwelveHeart'] = {};
  278. }
  279. if (medicalController.diagnosisDataValue['TwelveHeart']?['ECG_POINT12'] !=
  280. null &&
  281. medicalController.diagnosisDataValue['TwelveHeart']?['ECG_POINT12'] !=
  282. "[]") {
  283. String pointInfo = medicalController.diagnosisDataValue['TwelveHeart']
  284. ?['ECG_POINT12'] ??
  285. '';
  286. if ((pointInfo.toString().startsWith('https://') ||
  287. pointInfo.toString().startsWith('http://')) &&
  288. kIsOnline) {
  289. final response = await http.get(Uri.parse(pointInfo));
  290. pointInfo = response.body;
  291. }
  292. initEcgData = jsonDecode(pointInfo).cast<int>();
  293. } else {
  294. initEcgData = [];
  295. }
  296. setState(() {});
  297. }
  298. Future<void> initHeart() async {
  299. await initDevice();
  300. await initData();
  301. }
  302. @override
  303. void initState() {
  304. initHeart();
  305. super.initState();
  306. }
  307. void loadListeners() {
  308. worker.boundingEvent.addListener(_onBounding);
  309. worker.boundSuccessEvent.addListener(_onBoundSuccess);
  310. worker.boundFailEvent.addListener(_onBoundFail);
  311. worker.connectErrorEvent.addListener(_onConnectFail);
  312. worker.connectedEvent.addListener(_onConnectSuccess);
  313. worker.disconnectedEvent.addListener(_onDisconnected);
  314. worker.resultReceivedEvent.addListener(_onHrValueUpdate);
  315. worker.ecgStableEvent.addListener(_onEcgStable);
  316. worker.ecgConclusionEvent.addListener(_onConclusion);
  317. worker.startSaveSuccessEvent.addListener(_onSaveSuccess);
  318. }
  319. void releaseListeners() {
  320. worker.boundingEvent.removeListener(_onBounding);
  321. worker.boundSuccessEvent.removeListener(_onBoundSuccess);
  322. worker.boundFailEvent.removeListener(_onBoundFail);
  323. worker.connectErrorEvent.removeListener(_onConnectFail);
  324. worker.connectedEvent.removeListener(_onConnectSuccess);
  325. worker.disconnectedEvent.removeListener(_onDisconnected);
  326. worker.resultReceivedEvent.removeListener(_onHrValueUpdate);
  327. worker.ecgStableEvent.removeListener(_onEcgStable);
  328. worker.ecgConclusionEvent.removeListener(_onConclusion);
  329. worker.startSaveSuccessEvent.removeListener(_onSaveSuccess);
  330. }
  331. /// 设置最终数据
  332. Future<void> setEcgData() async {
  333. TwelveEcgViewController ecgViewController =
  334. Get.find<TwelveEcgViewController>();
  335. ecgViewController.isPaused = true;
  336. /// 【TODO】 这边原来是30秒的图,现在改成5秒图
  337. /// 后面又将30秒和5秒的图都存了,所以这里需要改动
  338. medicalController.diagnosisDataValue['TwelveHeart']?['ECG12'] =
  339. await ecgViewController.getFiveImageBase64();
  340. medicalController.diagnosisDataValue['TwelveHeart']?['ECG12_30s'] =
  341. await ecgViewController.getFullDataImageBase64();
  342. medicalController.diagnosisDataValue['TwelveHeart']?['ECG_POINT12'] =
  343. jsonEncode(ecgViewController.allPoints);
  344. ecgViewController.updateTwelveEcgView();
  345. }
  346. void _onConnectFail(sender, e) async {
  347. logger.i("设备连接失败:${worker.mac}");
  348. connectStatus = WorkerStatus.connectionFailed;
  349. twelveEcgStatus = TwelveEcgStatus.noSampling;
  350. setState(() {});
  351. }
  352. /// 采样稳定
  353. void _onEcgStable(sender, e) async {
  354. twelveEcgStatus = TwelveEcgStatus.stableSampling;
  355. PromptBox.toast("当前心电已稳定,开始采样");
  356. logger.i('当前心电已稳定,开始采样');
  357. resetEcgView();
  358. setState(() {});
  359. }
  360. void _onSaveSuccess(sender, e) async {
  361. if (e) {
  362. PromptBox.toast("采样结束");
  363. twelveEcgStatus = TwelveEcgStatus.noSampling;
  364. }
  365. setState(() {});
  366. }
  367. void _onBounding(sender, e) {
  368. connectStatus = WorkerStatus.boundingDevice;
  369. setState(() {});
  370. }
  371. void _onBoundSuccess(sender, e) {
  372. tryReconnect();
  373. setState(() {});
  374. }
  375. void _onBoundFail(sender, e) {
  376. connectStatus = WorkerStatus.boundDeviceFail;
  377. setState(() {});
  378. }
  379. void _onConnectSuccess(sender, e) {
  380. logger.i('_HeartRateState ${worker.mac}, 设备连接成功');
  381. twelveEcgStatus = TwelveEcgStatus.unstableSampling;
  382. isConnectFail = false;
  383. errorCount = 0;
  384. connectStatus = WorkerStatus.connected;
  385. resetEcgView();
  386. _assess = "";
  387. setState(() {});
  388. }
  389. /// 设备连接中断
  390. void _onDisconnected(sender, e) async {
  391. print('设备连接中断');
  392. logger.i("设备连接中断:${worker.mac}");
  393. // await tryReconnect();
  394. errorCount = 0;
  395. connectStatus = WorkerStatus.disconnected;
  396. twelveEcgStatus = TwelveEcgStatus.noSampling;
  397. setState(() {});
  398. }
  399. /// 更新心率
  400. void _onHrValueUpdate(_, TwelveHeartExamData e) {
  401. setState(() {
  402. if (e.heartRate < 0) {
  403. _heart = "--";
  404. } else {
  405. _heart = e.heartRate.toString();
  406. }
  407. try {
  408. TwelveEcgViewController ecgViewController =
  409. Get.find<TwelveEcgViewController>();
  410. ecgViewController.addData(e.ecgPoints);
  411. } catch (e) {
  412. print('🥥🥥🥥🥥');
  413. print(e);
  414. print('🥥🥥🥥🥥');
  415. }
  416. });
  417. }
  418. void _onConclusion(_, TwelveHeartExamResult e) async {
  419. logger.i('心率更新接收结果:${e.heartRate}');
  420. _assess = e.resultConclusion.advice;
  421. _analyse12 = jsonEncode(e.resultConclusion.toJson());
  422. await ConclusionDialog.show(twelveHeartResult: e.resultConclusion);
  423. if (e.heartRate < 0) {
  424. _heart = "--";
  425. } else {
  426. _heart = e.heartRate.toString();
  427. }
  428. medicalController.diagnosisDataValue['TwelveHeart']?['HEART12'] =
  429. e.heartRate.toString();
  430. medicalController.diagnosisDataValue['TwelveHeart']?['ASSESS12'] = _assess;
  431. medicalController.diagnosisDataValue['TwelveHeart']?['Analyse12'] =
  432. _analyse12;
  433. await setEcgData();
  434. medicalController.saveCachedRecord();
  435. setState(() {});
  436. }
  437. void _openConclusion() async {
  438. TwelveHeartResultEntity resultConclusion =
  439. TwelveHeartResultEntity.fromJson(jsonDecode(_analyse12));
  440. await ConclusionDialog.show(twelveHeartResult: resultConclusion);
  441. }
  442. }