urinalysis.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'package:get/get.dart';
  4. import 'package:vitalapp/architecture/utils/prompt_box.dart';
  5. import 'package:vitalapp/components/button.dart';
  6. import 'package:vitalapp/components/dialog_input.dart';
  7. import 'package:vitalapp/managers/device_controller_manager.dart';
  8. import 'package:vitalapp/pages/mappers/urine.dart';
  9. import 'package:vitalapp/pages/medical/widgets/device_status_position.dart';
  10. import 'package:vnote_device_plugin/consts/types.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/worker.dart';
  15. import 'package:vitalapp/pages/medical/widgets/device_status.dart';
  16. import 'package:vnote_device_plugin/devices/urine.dart';
  17. import 'package:vnote_device_plugin/models/exams/urine.dart';
  18. import 'package:fis_common/logger/logger.dart';
  19. /// 尿常规
  20. class Urinalysis extends StatefulWidget {
  21. const Urinalysis({
  22. super.key,
  23. });
  24. @override
  25. State<Urinalysis> createState() => _ExamUrinalysisState();
  26. }
  27. class _ExamUrinalysisState extends State<Urinalysis> {
  28. var controller = Get.find<MedicalController>();
  29. DeviceControllerManager? urinaly;
  30. UrineDeviceWorker? worker;
  31. UrineExamData? urineExamData;
  32. bool isConnectFail = false;
  33. bool isAutoTesting = false;
  34. int errorCount = 0;
  35. String? get deviceType => MedicalController.typeConvertMap[DeviceTypes.URINE];
  36. WorkerStatus connectStatus = WorkerStatus.connecting;
  37. List<Map<String, String>> urinalysis = [
  38. {"name": '尿白细胞', "key": 'LEU'},
  39. {"name": '红细胞/潜血', "key": 'BLD'},
  40. {"name": '尿亚硝酸盐', "key": 'NIT'},
  41. {"name": '酮体', "key": 'KET'},
  42. {"name": '尿胆原', "key": 'UBG'},
  43. {"name": '胆红素', "key": 'BIL'},
  44. {"name": '尿蛋白', "key": 'PRO'},
  45. {"name": '葡萄糖', "key": 'GLU'},
  46. {"name": '酸碱度', "key": 'PH'},
  47. {"name": '尿比重', "key": 'SG'},
  48. {"name": '维生素C', "key": 'VC'},
  49. ];
  50. Map _value = {};
  51. @override
  52. void initState() {
  53. WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
  54. initUrinlysis();
  55. currentDevice();
  56. controller.changePatient.addListener(_onPatientChange);
  57. });
  58. super.initState();
  59. }
  60. Future<void> currentDevice() async {
  61. DeviceModel? device = await controller.getDevice(DeviceTypes.URINE);
  62. if (device == null) {
  63. connectStatus = WorkerStatus.unboundDevice;
  64. return;
  65. }
  66. urinaly =
  67. DeviceControllerManager(DeviceTypes.URINE, device.model, device.mac);
  68. worker = urinaly!.worker as UrineDeviceWorker;
  69. connectStatus = urinaly!.connectStatus;
  70. loadListeners();
  71. worker!.connect();
  72. }
  73. Future<void> initUrinlysis() async {
  74. await initData();
  75. }
  76. void loadListeners() {
  77. worker!.successEvent.addListener(_onSuccess);
  78. worker!.connectErrorEvent.addListener(_onConnectFail);
  79. worker!.connectedEvent.addListener(_onConnectSuccess);
  80. worker!.disconnectedEvent.addListener(_onDisconnected);
  81. // worker!.errorEvent.addListener(_onError);
  82. }
  83. Future<void> initData() async {
  84. if (controller.diagnosisDataValue.isNotEmpty &&
  85. controller.diagnosisDataValue.containsKey(deviceType)) {
  86. _value = controller.diagnosisDataValue[deviceType!];
  87. } else {
  88. _value = {};
  89. }
  90. setState(() {});
  91. }
  92. void releaseListeners() {
  93. if (worker != null) {
  94. worker!.connectErrorEvent.removeListener(_onConnectFail);
  95. worker!.connectedEvent.removeListener(_onConnectSuccess);
  96. worker!.successEvent.removeListener(_onSuccess);
  97. worker!.disconnectedEvent.removeListener(_onDisconnected);
  98. // worker!.errorEvent.removeListener(_onError);
  99. }
  100. }
  101. /// 尝试重连
  102. Future<void> tryReconnect() async {
  103. //await worker.disconnect();
  104. //5s后在执行,是为了防止重连与断开事件一直相互触发
  105. Future.delayed(const Duration(seconds: 5), () async {
  106. //如果5秒后仍然是未连接状态,则重试连接
  107. if (connectStatus == WorkerStatus.disconnected ||
  108. connectStatus == WorkerStatus.connectionFailed ||
  109. connectStatus == WorkerStatus.connecting) {
  110. await disconnect();
  111. await connect();
  112. }
  113. });
  114. }
  115. Future<void> disconnect() async {
  116. await worker?.disconnect();
  117. }
  118. Future<void> connect() async {
  119. await worker?.connect();
  120. }
  121. @override
  122. void dispose() async {
  123. controller.changePatient.removeListener(_onPatientChange);
  124. urinaly?.dispose();
  125. releaseListeners();
  126. disconnect();
  127. worker?.dispose();
  128. super.dispose();
  129. }
  130. void _onSuccess(_, UrineExamData e) {
  131. setState(() {
  132. isAutoTesting = false;
  133. urineExamData = e;
  134. controller.diagnosisDataValue[deviceType!] =
  135. UrineExamDataMapper.convertUrineExamDataToMap(urineExamData!);
  136. _value = controller.diagnosisDataValue[deviceType!];
  137. controller.saveCachedRecord();
  138. connectStatus = WorkerStatus.connected;
  139. });
  140. }
  141. void _onConnectFail(sender, e) {
  142. print('连接设备失败');
  143. logger.i("设备连接失败:${worker!.mac}");
  144. if (errorCount < 3) {
  145. errorCount++;
  146. tryReconnect();
  147. } else {
  148. isConnectFail = true;
  149. }
  150. connectStatus = WorkerStatus.connectionFailed;
  151. setState(() {});
  152. }
  153. void _onConnectSuccess(sender, e) {
  154. logger.i("设备连接成功:${worker!.mac}");
  155. isConnectFail = false;
  156. errorCount = 0;
  157. connectStatus = WorkerStatus.connected;
  158. setState(() {});
  159. }
  160. void _onError(sender, e) async {
  161. PromptBox.toast("测试失败,请确定放好试纸并且试纸已浸湿尿液");
  162. isAutoTesting = false;
  163. setState(() {});
  164. }
  165. void _onDisconnected(sender, e) {
  166. print('设备连接中断 errorCount:$errorCount');
  167. logger.i("设备连接中断:${worker!.mac} errorCount:$errorCount");
  168. if (errorCount < 3) {
  169. errorCount++;
  170. tryReconnect();
  171. } else {
  172. isConnectFail = true;
  173. }
  174. connectStatus = WorkerStatus.disconnected;
  175. setState(() {});
  176. }
  177. @override
  178. Widget build(BuildContext context) {
  179. return Stack(
  180. children: [
  181. _buildUrinalysis(),
  182. if (!isConnectFail)
  183. DeviceStatusPosition(
  184. deviceStatus: DeviceStatus(connectStatus: connectStatus),
  185. )
  186. else
  187. _buildErrorButton(),
  188. // _buildAutoTest(),
  189. ],
  190. );
  191. }
  192. Widget _buildAutoTest() {
  193. Widget autoTestStatus;
  194. if (isAutoTesting) {
  195. autoTestStatus = const SizedBox();
  196. // CountdownPage(
  197. // seconds: 60,
  198. // title: "等待测试",
  199. // );
  200. } else {
  201. autoTestStatus = VButton(
  202. onTap: () {
  203. worker!.autoTest();
  204. isAutoTesting = true;
  205. _value = {};
  206. setState(() {});
  207. },
  208. child: const Center(
  209. child: Text(
  210. "采样",
  211. style: TextStyle(fontSize: 24),
  212. ),
  213. ),
  214. );
  215. }
  216. if (connectStatus == WorkerStatus.connected) {
  217. return Positioned(
  218. top: 16,
  219. left: 120,
  220. child: SizedBox(
  221. width: 200,
  222. height: 50,
  223. child: autoTestStatus,
  224. ),
  225. );
  226. } else {
  227. return const SizedBox();
  228. }
  229. }
  230. Widget _buildErrorButton() {
  231. return DeviceStatusPosition(
  232. deviceStatus: Row(
  233. children: [
  234. const Text(
  235. '请确认设备是否启动',
  236. style: TextStyle(fontSize: 24, color: Colors.red),
  237. ),
  238. IconButton(
  239. onPressed: () {
  240. tryReconnect();
  241. setState(() {
  242. connectStatus = WorkerStatus.connecting;
  243. isConnectFail = false;
  244. });
  245. },
  246. icon: const Icon(Icons.refresh),
  247. iconSize: 32,
  248. ),
  249. ],
  250. ),
  251. );
  252. }
  253. Widget _buildUrinalysis() {
  254. return ExamCard(
  255. title: '尿常规',
  256. content: Container(
  257. alignment: Alignment.center,
  258. padding: const EdgeInsets.only(
  259. bottom: 20,
  260. right: 30,
  261. left: 40,
  262. ),
  263. constraints: const BoxConstraints(minHeight: 50),
  264. child: GridView.count(
  265. shrinkWrap: true,
  266. childAspectRatio: 4,
  267. crossAxisCount: 2, // 列数为2,即两列布局
  268. children: urinalysis.map((item) {
  269. return _buildUrineItem(item);
  270. }).toList(),
  271. ),
  272. ),
  273. );
  274. }
  275. Widget _buildUrineItem(Map<String, String> urine) {
  276. return InkWell(
  277. onTap: () {
  278. _input(urine['name']!, urine['key']!);
  279. },
  280. child: Row(
  281. mainAxisAlignment: MainAxisAlignment.start,
  282. crossAxisAlignment: CrossAxisAlignment.center,
  283. children: [
  284. Container(
  285. alignment: Alignment.centerLeft,
  286. width: 100,
  287. height: 55,
  288. child: Column(
  289. crossAxisAlignment: CrossAxisAlignment.start,
  290. children: [
  291. Text(
  292. urine['key']!,
  293. style: const TextStyle(
  294. fontSize: 22,
  295. color: Colors.black,
  296. ),
  297. ),
  298. Text(
  299. '(${urine['name']!})',
  300. // text: _value.isEmpty ? '--' : _value,
  301. textAlign: TextAlign.center,
  302. style: const TextStyle(
  303. fontSize: 16,
  304. ),
  305. ),
  306. ],
  307. ),
  308. ),
  309. Container(
  310. height: 65,
  311. width: 140,
  312. alignment: Alignment.centerLeft,
  313. child: Text(
  314. _value[urine['key']!] ?? '',
  315. // text: _value.isEmpty ? '--' : _value,
  316. maxLines: 2,
  317. style: const TextStyle(
  318. fontSize: 30,
  319. ),
  320. ),
  321. )
  322. ],
  323. ),
  324. );
  325. }
  326. Future<void> _input(String name, String key) async {
  327. String? result = await VDialogInput(
  328. title: name,
  329. initialValue: _value[key],
  330. ).show();
  331. if (result?.isNotEmpty ?? false) {
  332. _value[key] = result ?? '';
  333. }
  334. controller.diagnosisDataValue[deviceType!] = _value;
  335. controller.saveCachedRecord();
  336. // widget.urinalysis(_value);
  337. setState(() {});
  338. }
  339. void _onPatientChange(Object sender, String e) {
  340. setState(() {
  341. _value = {};
  342. controller.diagnosisDataValue[deviceType!] = {};
  343. });
  344. }
  345. }