measure_view.dart 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. import 'package:fis_i18n/i18n.dart';
  2. import 'package:fis_jsonrpc/rpc.dart';
  3. import 'package:fis_measure/interfaces/enums/annotation.dart';
  4. import 'package:fis_measure/interfaces/process/player/play_controller.dart';
  5. import 'package:fis_measure/interfaces/process/workspace/application.dart';
  6. import 'package:fis_measure/interfaces/process/workspace/exam_info.dart';
  7. import 'package:fis_measure/interfaces/process/workspace/measure_3d_view_controller.dart';
  8. import 'package:fis_measure/process/language/measure_language.dart';
  9. import 'package:fis_measure/process/layout/configuration.dart';
  10. import 'package:fis_measure/process/workspace/measure_controller.dart';
  11. import 'package:fis_measure/process/workspace/measure_data_controller.dart';
  12. import 'package:fis_measure/process/workspace/measure_handler.dart';
  13. import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart';
  14. import 'package:fis_measure/view/gesture/positioned_cursor.dart';
  15. import 'package:fis_measure/view/measure/measure_config/widgets/measure_configuration_style.dart';
  16. import 'package:fis_measure/view/measure/measure_images_bar.dart';
  17. import 'package:fis_measure/view/measure/measure_left_annotation.dart';
  18. import 'package:fis_measure/view/measure/measure_player.dart';
  19. import 'package:fis_measure/view/measure/measure_search_input.dart';
  20. import 'package:fis_measure/view/measure/measure_tool.dart';
  21. import 'package:fis_measure/view/measure/carotid_measure_tool.dart';
  22. import 'package:fis_measure/view/measure/measure_tools_title.dart';
  23. import 'package:fis_measure/view/measure/measure_view_controller.dart';
  24. import 'package:fis_measure/view/measure/tool_chest_title.dart';
  25. import 'package:fis_measure/view/player/control_board/operate_bar.dart';
  26. import 'package:fis_ui/base_define/page.dart';
  27. import 'package:fis_ui/index.dart';
  28. import 'package:fis_ui/widgets/layout/offstage.dart';
  29. import 'package:flutter/material.dart';
  30. import 'package:get/get.dart';
  31. /// 测量主页面
  32. class MeasureMainPage extends StatefulWidget implements FWidget {
  33. const MeasureMainPage(
  34. this.token, this.recordCode, this.patientCode, this.remedicalCode,
  35. {this.needRouterBack, Key? key})
  36. : super(key: key);
  37. final String token;
  38. final String patientCode;
  39. final String remedicalCode;
  40. final String recordCode;
  41. final bool? needRouterBack; // 需要返回按钮【一版用于返回到报告编辑】
  42. @override
  43. State<StatefulWidget> createState() => _MeasureMainPageState();
  44. }
  45. class _MeasureMainPageState extends State<MeasureMainPage> {
  46. /// 病人及图像信息
  47. String _curToken = '';
  48. String _curPatientCode = '';
  49. String _curRemedicalCode = '';
  50. String _curRecordCode = '';
  51. bool? _curNeedRouterBack;
  52. /// 数据
  53. final measureData = Get.find<MeasureDataController>();
  54. /// webview 控制器
  55. final measure3DViewController = Get.find<Measure3DViewController>();
  56. /// 测量项控制器
  57. final measureMetaController = Get.put(MeasureMetaController());
  58. /// 隐藏全屏loadding
  59. bool _hideFullScreenLoading = false;
  60. /// 图片loadding
  61. late bool imageLoaded = false;
  62. late final measureHandler = Get.find<MeasureHandler>();
  63. /// 测量控制器
  64. late MeasureController measureController;
  65. /// 响应图片切换事件通知
  66. void _onChangeImage(sender, e) {
  67. imageLoaded = e;
  68. setState(() {});
  69. }
  70. /// 响应全屏状态切换事件
  71. void _onChangeFullScreenState(sender, e) {
  72. setState(() {});
  73. }
  74. /// 在此处动态刷新测量窗口,而不是重置窗口
  75. void _onUpdateState(sender, Map<String, String> parameters) async {
  76. _curNeedRouterBack = false;
  77. bool needUpdate = false;
  78. if (_curPatientCode == (parameters['patientCode'] ?? '')) {
  79. if (_curRemedicalCode == (parameters['remedicalCode'] ?? '')) {
  80. } else {
  81. measureHandler.changeImageByRemedicalCode
  82. .emit(this, parameters['remedicalCode'] ?? '');
  83. }
  84. } else {
  85. needUpdate = true;
  86. }
  87. _curToken = parameters['token'] ?? '';
  88. _curPatientCode = parameters['patientCode'] ?? '';
  89. _curRemedicalCode = parameters['remedicalCode'] ?? '';
  90. _curRecordCode = parameters['recordCode'] ?? '';
  91. if (needUpdate) {
  92. await LayoutConfiguration.ins.loadData();
  93. List<RemedicalInfoDTO> remedicals = await _getCurRemedicals();
  94. _initMouseModuel();
  95. _initCarotidModuel(remedicals);
  96. measureData.remedicalList = remedicals;
  97. var remedicalInfo =
  98. await measureData.getImageInfo.call(_curRemedicalCode, _curToken);
  99. if (remedicalInfo != null) {
  100. measureData.aiResults = remedicalInfo.diagnosisResult ?? '';
  101. if (remedicalInfo.terminalImages != null) {
  102. _hideFullScreenLoading = true;
  103. measureData.itemCurrentImage =
  104. remedicalInfo.terminalImages!.imageUrl ?? '';
  105. getExamImageInfoList(remedicals);
  106. measureHandler.changeImageList.emit(this, remedicals);
  107. }
  108. }
  109. measureController.imageLoaded.removeListener(_onImageLoaded);
  110. measureController.imageLoaded.addListener(_onImageLoaded);
  111. }
  112. _setCurImageData();
  113. }
  114. /// 将当前的图像信息同步到 measureData 中
  115. void _setCurImageData() {
  116. measureData.measureImageData = MeasureImageData(
  117. patientCode: _curPatientCode,
  118. recordCode: _curRecordCode,
  119. remedicalCode: _curRemedicalCode,
  120. );
  121. }
  122. /// 先加载图像布局配置,然后加载全局数据
  123. void _loadLayoutConfig() async {
  124. await LayoutConfiguration.ins.loadData();
  125. setState(() {
  126. _initData();
  127. });
  128. }
  129. @override
  130. void initState() {
  131. _curToken = widget.token;
  132. _curPatientCode = widget.patientCode;
  133. _curRemedicalCode = widget.remedicalCode;
  134. _curRecordCode = widget.recordCode;
  135. _curNeedRouterBack = widget.needRouterBack;
  136. _hideFullScreenLoading = false;
  137. _loadLayoutConfig();
  138. _setCurImageData();
  139. measureData.curMeasureDataChanged.addListener(_onUpdateState);
  140. measureHandler.onChangeImageLoaded.addListener(_onChangeImage);
  141. measureHandler.onChangeFullScreenState
  142. .addListener(_onChangeFullScreenState);
  143. super.initState();
  144. }
  145. @override
  146. void dispose() {
  147. measureData.curMeasureDataChanged.removeListener(_onUpdateState);
  148. measureHandler.onChangeImageLoaded.removeListener(_onChangeImage);
  149. measureHandler.onChangeFullScreenState
  150. .removeListener(_onChangeFullScreenState);
  151. super.dispose();
  152. }
  153. @override
  154. FWidget build(BuildContext context) {
  155. FWidget body;
  156. if (!_hideFullScreenLoading) {
  157. const loadingWidget = FCenter(child: FCircularProgressIndicator());
  158. body = FRow(
  159. children: const [
  160. FExpanded(
  161. child: loadingWidget,
  162. ),
  163. ],
  164. );
  165. } else {
  166. body = FRow(
  167. children: [
  168. FOffstage(
  169. child: _MeasureLeftBoard(
  170. needRouterBack: _curNeedRouterBack,
  171. ),
  172. offstage: measureHandler.fullScreenState),
  173. const FVerticalDivider(),
  174. FExpanded(
  175. child: FColumn(
  176. mainAxisSize: MainAxisSize.max,
  177. children: [
  178. FExpanded(
  179. child: FStack(
  180. children: [
  181. if (!imageLoaded)
  182. MeasureRightBoard(
  183. key: ValueKey(measureData.itemCurrentImage),
  184. ),
  185. if (imageLoaded)
  186. const FCenter(
  187. child: FCircularProgressIndicator(),
  188. ),
  189. ],
  190. )),
  191. FOffstage(
  192. child: const MeasureImagesBar(),
  193. offstage: measureHandler.fullScreenState),
  194. // if (!measureHandler.fullScreenState) const MeasureImagesBar(),
  195. ],
  196. ),
  197. ),
  198. ],
  199. );
  200. }
  201. return FCenter(
  202. child: FContainer(
  203. color: const Color.fromRGBO(70, 70, 70, 1),
  204. child: body,
  205. ),
  206. );
  207. }
  208. /// 获取当前状态下的图像集
  209. Future<List<RemedicalInfoDTO>> _getCurRemedicals() async {
  210. List<RemedicalInfoDTO> remedicals = [];
  211. var value = await measureData.getRemedicalList.call(
  212. _curPatientCode,
  213. _curRecordCode,
  214. _curToken,
  215. );
  216. for (var remedicalItemList in value) {
  217. remedicals.addAll(remedicalItemList.remedicalList ?? []);
  218. }
  219. return remedicals;
  220. }
  221. /// 初始化颈动脉模块
  222. /// [Carotid] ✅遍历出颈动脉信息列表,传给壳子
  223. void _initCarotidModuel(List<RemedicalInfoDTO> remedicals) {
  224. List<Ultra3DResourceInfo> ultra3DResourceInfos = [];
  225. for (var remedical in remedicals) {
  226. if (remedical.carotidResult != null) {
  227. ultra3DResourceInfos.add(Ultra3DResourceInfo(remedical.carotidResult!));
  228. }
  229. }
  230. measure3DViewController.ultra3DResourceInfoList = ultra3DResourceInfos;
  231. measure3DViewController.recordId = _curRecordCode;
  232. measure3DViewController.notifyShellLoadAllModel();
  233. }
  234. /// 初始化卡尺样式部分
  235. Future<void> _initMouseModuel() async {
  236. final mouseState = Get.put<IMouseState>(MouseState());
  237. final result = await measureData.getMeasureSystemSettingAsync()
  238. as MeasureSystemSettingDTO;
  239. measureData.measureSystemSetting = result;
  240. mouseState.cursorType =
  241. getMeasureSystemSettingCursorType(result.cursorType);
  242. mouseState.cursorSize = result.cursorSize.toDouble();
  243. }
  244. /// 测量页全局数据首次初始化
  245. void _initData() async {
  246. List<RemedicalInfoDTO> remedicals = await _getCurRemedicals();
  247. _initMouseModuel();
  248. _initCarotidModuel(remedicals);
  249. measureData.remedicalList = remedicals;
  250. var remedicalInfo =
  251. await measureData.getImageInfo.call(_curRemedicalCode, _curToken);
  252. if (remedicalInfo != null) {
  253. measureData.aiResults = remedicalInfo.diagnosisResult ?? '';
  254. if (remedicalInfo.terminalImages != null) {
  255. _hideFullScreenLoading = true;
  256. measureData.itemCurrentImage =
  257. remedicalInfo.terminalImages!.imageUrl ?? '';
  258. getExamImageInfoList(remedicals);
  259. }
  260. }
  261. measureController.imageLoaded.removeListener(_onImageLoaded);
  262. measureController.imageLoaded.addListener(_onImageLoaded);
  263. }
  264. /// 获取测量图片所需的图片组 并且写入控制器中 加载
  265. void getExamImageInfoList(List<RemedicalInfoDTO> remedicals) async {
  266. /// 检查中图片信息表
  267. List<ExamImageInfo> examImageInfoList = [];
  268. for (var element in remedicals) {
  269. examImageInfoList.add(
  270. ExamImageInfo(
  271. element.terminalImages!.imageUrl!,
  272. element.terminalImages!.previewUrl!,
  273. ),
  274. );
  275. }
  276. Get.delete<MeasureController>();
  277. measureController = Get.put(MeasureController(
  278. "",
  279. imagesFetchFunc: (code) async {
  280. return examImageInfoList;
  281. },
  282. ));
  283. await measureController.load();
  284. int selectedImageIndex = -1;
  285. if (examImageInfoList
  286. .any((element) => element.url == measureData.itemCurrentImage)) {
  287. ExamImageInfo selectedImage = examImageInfoList.firstWhere(
  288. (element) => element.url == measureData.itemCurrentImage,
  289. );
  290. selectedImageIndex = examImageInfoList.indexOf(selectedImage);
  291. measureController.examInfo.selectedImageIndex = selectedImageIndex;
  292. }
  293. MeasureLanguage.load(measureData.measureLanguage);
  294. }
  295. /// vid 切换时会触发到这里
  296. void _onImageLoaded(Object sender, ExamImageInfo? e) async {
  297. // measureHandler.changeImageLoaded = true;
  298. if (!mounted) return;
  299. final currentImage = measureData.remedicalList.firstWhereOrNull(
  300. (element) => element.terminalImages!.imageUrl == e!.url,
  301. );
  302. if (currentImage != null) {
  303. /// 获取图片详细信息
  304. var remedicalInfo = await measureData.getImageInfo(
  305. currentImage.remedicalCode ?? '',
  306. _curToken,
  307. );
  308. if (remedicalInfo != null) {
  309. measureData.aiResults = remedicalInfo.diagnosisResult ?? '';
  310. measure3DViewController.initParams();
  311. /// ai 良恶性 判断是否有ai
  312. measureData.diagnosisConclusion = remedicalInfo.diagnosisConclusion;
  313. if (remedicalInfo.carotidResult != null) {
  314. /// [Carotid] ✅详情传入测量信息
  315. measure3DViewController.carotidResult = remedicalInfo.carotidResult!;
  316. /// [Carotid] ✅需要在此通知 controller 存在颈动脉信息
  317. measure3DViewController.exist3DData = true;
  318. measure3DViewController
  319. .handleChangeCarotid2DImage(remedicalInfo.recordCode!);
  320. } else {
  321. measure3DViewController.exist3DData = false;
  322. }
  323. // [Carotid] ✅只要更换图片都要切换到Vid 2D模式
  324. if (measure3DViewController.curMeasureMode != MeasureMode.vidMode) {
  325. measure3DViewController.backToVidMode();
  326. }
  327. try {
  328. if (e != null) {
  329. Future.delayed(const Duration(milliseconds: 100), () {
  330. measureController.playerController.play();
  331. });
  332. setState(() {
  333. _hideFullScreenLoading = true;
  334. measureHandler.changeImageLoaded = false;
  335. });
  336. }
  337. } catch (error) {
  338. setState(() {
  339. _hideFullScreenLoading = true;
  340. measureHandler.changeImageLoaded = false;
  341. });
  342. }
  343. }
  344. }
  345. }
  346. }
  347. /// 测量左边操作页面
  348. class _MeasureLeftBoard extends StatefulWidget implements FPage {
  349. const _MeasureLeftBoard({Key? key, this.needRouterBack}) : super(key: key);
  350. @override
  351. final String pageName = 'MeasureLeftBoard';
  352. final bool? needRouterBack;
  353. @override
  354. State<_MeasureLeftBoard> createState() => _MeasureLeftBoardState();
  355. }
  356. class _MeasureLeftBoardState extends State<_MeasureLeftBoard> {
  357. final measureHandler = Get.find<MeasureHandler>();
  358. final playerController = Get.find<IPlayerController>();
  359. final measure3DViewController = Get.find<Measure3DViewController>();
  360. /// 数据
  361. late final measureData = Get.find<MeasureDataController>();
  362. /// 测量项控制器
  363. final measureMetaController = Get.find<MeasureMetaController>();
  364. /// 是否显示颈动脉2D指定的测量项
  365. bool showCarotid2DSelectMeasure = false;
  366. /// 是否显示测量项
  367. bool hideMeasureItems = false;
  368. bool isMeasureTool = true;
  369. bool get isArrowMeasureAnnotationType =>
  370. measureHandler.changedAnnotationType == AnnotationType.arrow;
  371. TabEnum curSelectedTab = TabEnum.tabMeasureTool;
  372. void _onChangedTab(
  373. Object sender,
  374. TabEnum e,
  375. ) {
  376. setState(() {
  377. curSelectedTab = e;
  378. });
  379. }
  380. void _onCurItemMetaListChanged(sender, e) {
  381. if (mounted) {
  382. setState(() {});
  383. }
  384. }
  385. ///图像发生变化
  386. void _onChangeImage(sender, e) {
  387. setState(() {});
  388. }
  389. @override
  390. void initState() {
  391. measureHandler.onChangedTab.addListener(_onChangedTab);
  392. measure3DViewController.updatePlayerMode.addListener(_onModeChanged);
  393. measureData.curItemMetaListChanged.addListener(_onCurItemMetaListChanged);
  394. measureHandler.onChangeImageLoaded.addListener(_onChangeImage);
  395. super.initState();
  396. }
  397. @override
  398. void dispose() {
  399. measureHandler.onChangedTab.removeListener(_onChangedTab);
  400. measure3DViewController.updatePlayerMode.removeListener(_onModeChanged);
  401. measureData.curItemMetaListChanged
  402. .removeListener(_onCurItemMetaListChanged);
  403. measureHandler.onChangeImageLoaded.removeListener(_onChangeImage);
  404. super.dispose();
  405. }
  406. /// 模式改变触发更新
  407. void _onModeChanged(Object s, MeasureMode mode) {
  408. switch (mode) {
  409. case MeasureMode.vidMode:
  410. final playerController = Get.find<IPlayerController>();
  411. // 通过帧加载完成事件通知,去更新测量项
  412. if (playerController.currentFrame != null) {
  413. playerController.firstFrameLoaded
  414. .emit(this, playerController.currentFrame!);
  415. }
  416. setState(() {
  417. showCarotid2DSelectMeasure = false;
  418. hideMeasureItems = false;
  419. });
  420. break;
  421. case MeasureMode.carotid2DMode:
  422. setState(() {
  423. showCarotid2DSelectMeasure = true;
  424. hideMeasureItems = false;
  425. });
  426. break;
  427. case MeasureMode.carotid3DMode:
  428. setState(() {
  429. showCarotid2DSelectMeasure = true;
  430. hideMeasureItems = true;
  431. });
  432. break;
  433. }
  434. }
  435. bool get hideMeasureTab => (curSelectedTab == TabEnum.tabNodesTool);
  436. bool get hideCommentTab => !hideMeasureTab;
  437. @override
  438. FWidget build(BuildContext context) {
  439. final newPlayerController = Get.find<IPlayerController>();
  440. return FColumn(
  441. mainAxisSize: MainAxisSize.max,
  442. crossAxisAlignment: CrossAxisAlignment.start,
  443. children: [
  444. FOffstage(
  445. offstage: hideMeasureItems,
  446. child: LeftSiderHold(ifHideConfig: showCarotid2DSelectMeasure),
  447. ),
  448. FOffstage(
  449. offstage: hideMeasureItems || hideCommentTab,
  450. child: FContainer(
  451. width: 300,
  452. key: UniqueKey(),
  453. child: LeftSelectInput(),
  454. ),
  455. ),
  456. FOffstage(
  457. offstage: hideMeasureItems || hideMeasureTab,
  458. child: const LeftMeasureTools(),
  459. ),
  460. FExpanded(
  461. child: FStack(
  462. fit: StackFit.passthrough,
  463. children: [
  464. FOffstage(
  465. offstage: hideMeasureItems || hideMeasureTab,
  466. child: FContainer(
  467. width: 300,
  468. child: showCarotid2DSelectMeasure
  469. ? const CarotidLeftSiderSelectMeasure()
  470. : FContainer(
  471. key: Key(newPlayerController.url),
  472. child: const LeftSiderSelectMeasure()),
  473. ),
  474. ),
  475. FOffstage(
  476. offstage: hideMeasureItems || hideCommentTab,
  477. child: FContainer(
  478. width: 300,
  479. key: UniqueKey(),
  480. child: const MeasureLeftAnnotation(),
  481. ),
  482. ),
  483. ],
  484. ),
  485. ),
  486. FOffstage(
  487. offstage: hideMeasureItems || hideCommentTab,
  488. child: _MeasureArrow()),
  489. FOffstage(
  490. offstage: !hideMeasureItems,
  491. child: FContainer(
  492. width: 300,
  493. child: FCenter(
  494. child: FElevatedButton(
  495. name: "backToVidMode",
  496. businessParent: widget,
  497. onPressed: () => measure3DViewController.backToVidMode(),
  498. child: FText(
  499. i18nBook.measure.vidMode.t,
  500. style: const TextStyle(color: Colors.white),
  501. ),
  502. ),
  503. ),
  504. ),
  505. ),
  506. const OperateBar(),
  507. widget.needRouterBack ?? false
  508. ? FContainer(
  509. width: 300,
  510. padding: const EdgeInsets.only(bottom: 20),
  511. child: FIconButton(
  512. businessParent: widget,
  513. name: "back",
  514. onPressed: () {
  515. Get.back();
  516. },
  517. icon: const FIcon(
  518. Icons.arrow_back,
  519. color: Colors.white,
  520. size: 36,
  521. ),
  522. ),
  523. )
  524. : const FSizedBox()
  525. ],
  526. );
  527. }
  528. }
  529. class _MeasureArrow extends FStatefulWidget {
  530. @override
  531. FState<_MeasureArrow> createState() => _MeasureArrowState();
  532. }
  533. class _MeasureArrowState extends FState<_MeasureArrow> {
  534. final measureHandler = Get.find<MeasureHandler>();
  535. bool get isArrowMeasureAnnotationType =>
  536. measureHandler.changedAnnotationType == AnnotationType.arrow;
  537. void onChangedAnnotationType(
  538. Object sender,
  539. AnnotationType? e,
  540. ) {
  541. setState(() {});
  542. }
  543. @override
  544. void initState() {
  545. measureHandler.onChangedAnnotationType.addListener(onChangedAnnotationType);
  546. super.initState();
  547. }
  548. @override
  549. void dispose() {
  550. measureHandler.onChangedAnnotationType
  551. .removeListener(onChangedAnnotationType);
  552. super.dispose();
  553. }
  554. @override
  555. FWidget build(BuildContext context) {
  556. return FContainer(
  557. height: 60,
  558. width: 300,
  559. child: FCenter(
  560. child: FColumn(
  561. children: [
  562. FInk(
  563. child: FInkWell(
  564. onTap: () {
  565. measureHandler.changedAnnotationType = AnnotationType.arrow;
  566. final application = Get.find<IApplication>();
  567. application.switchAnnotation(AnnotationType.arrow);
  568. },
  569. child: FIcon(
  570. Icons.call_made_rounded,
  571. color:
  572. isArrowMeasureAnnotationType ? Colors.blue : Colors.white,
  573. size: 30,
  574. ),
  575. ),
  576. ),
  577. FText(
  578. i18nBook.measure.arrow.t,
  579. style: isArrowMeasureAnnotationType
  580. ? const TextStyle(
  581. color: Colors.blue,
  582. fontSize: 14,
  583. )
  584. : const TextStyle(
  585. color: Colors.white,
  586. fontSize: 14,
  587. ),
  588. ),
  589. ],
  590. ),
  591. ),
  592. );
  593. }
  594. }