import 'package:fis_i18n/i18n.dart'; import 'package:fis_jsonrpc/rpc.dart'; import 'package:fis_measure/interfaces/enums/annotation.dart'; import 'package:fis_measure/interfaces/process/player/play_controller.dart'; import 'package:fis_measure/interfaces/process/workspace/application.dart'; import 'package:fis_measure/interfaces/process/workspace/exam_info.dart'; import 'package:fis_measure/interfaces/process/workspace/measure_3d_view_controller.dart'; import 'package:fis_measure/process/language/measure_language.dart'; import 'package:fis_measure/process/layout/configuration.dart'; import 'package:fis_measure/process/workspace/measure_controller.dart'; import 'package:fis_measure/process/workspace/measure_data_controller.dart'; import 'package:fis_measure/process/workspace/measure_handler.dart'; import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart'; import 'package:fis_measure/view/gesture/positioned_cursor.dart'; import 'package:fis_measure/view/measure/measure_config/widgets/measure_configuration_style.dart'; import 'package:fis_measure/view/measure/measure_images_bar.dart'; import 'package:fis_measure/view/measure/measure_left_annotation.dart'; import 'package:fis_measure/view/measure/measure_player.dart'; import 'package:fis_measure/view/measure/measure_search_input.dart'; import 'package:fis_measure/view/measure/measure_tool.dart'; import 'package:fis_measure/view/measure/carotid_measure_tool.dart'; import 'package:fis_measure/view/measure/measure_tools_title.dart'; import 'package:fis_measure/view/measure/measure_view_controller.dart'; import 'package:fis_measure/view/measure/tool_chest_title.dart'; import 'package:fis_measure/view/player/control_board/operate_bar.dart'; import 'package:fis_ui/base_define/page.dart'; import 'package:fis_ui/index.dart'; import 'package:fis_ui/widgets/layout/offstage.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; /// 测量主页面 class MeasureMainPage extends StatefulWidget implements FWidget { const MeasureMainPage( this.token, this.recordCode, this.patientCode, this.remedicalCode, {this.needRouterBack, Key? key}) : super(key: key); final String token; final String patientCode; final String remedicalCode; final String recordCode; final bool? needRouterBack; // 需要返回按钮【一版用于返回到报告编辑】 @override State createState() => _MeasureMainPageState(); } class _MeasureMainPageState extends State { /// 病人及图像信息 String _curToken = ''; String _curPatientCode = ''; String _curRemedicalCode = ''; String _curRecordCode = ''; bool? _curNeedRouterBack; /// 数据 final measureData = Get.find(); /// webview 控制器 final measure3DViewController = Get.find(); /// 测量项控制器 final measureMetaController = Get.put(MeasureMetaController()); /// 隐藏全屏loadding bool _hideFullScreenLoading = false; /// 图片loadding late bool imageLoaded = false; late final measureHandler = Get.find(); /// 测量控制器 late MeasureController measureController; /// 响应图片切换事件通知 void _onChangeImage(sender, e) { imageLoaded = e; setState(() {}); } /// 响应全屏状态切换事件 void _onChangeFullScreenState(sender, e) { setState(() {}); } /// 在此处动态刷新测量窗口,而不是重置窗口 void _onUpdateState(sender, Map parameters) async { _curNeedRouterBack = false; bool needUpdate = false; if (_curPatientCode == (parameters['patientCode'] ?? '')) { if (_curRemedicalCode == (parameters['remedicalCode'] ?? '')) { } else { measureHandler.changeImageByRemedicalCode .emit(this, parameters['remedicalCode'] ?? ''); } } else { needUpdate = true; } _curToken = parameters['token'] ?? ''; _curPatientCode = parameters['patientCode'] ?? ''; _curRemedicalCode = parameters['remedicalCode'] ?? ''; _curRecordCode = parameters['recordCode'] ?? ''; if (needUpdate) { await LayoutConfiguration.ins.loadData(); List remedicals = await _getCurRemedicals(); _initMouseModuel(); _initCarotidModuel(remedicals); measureData.remedicalList = remedicals; var remedicalInfo = await measureData.getImageInfo.call(_curRemedicalCode, _curToken); if (remedicalInfo != null) { measureData.aiResults = remedicalInfo.diagnosisResult ?? ''; if (remedicalInfo.terminalImages != null) { _hideFullScreenLoading = true; measureData.itemCurrentImage = remedicalInfo.terminalImages!.imageUrl ?? ''; getExamImageInfoList(remedicals); measureHandler.changeImageList.emit(this, remedicals); } } measureController.imageLoaded.removeListener(_onImageLoaded); measureController.imageLoaded.addListener(_onImageLoaded); } _setCurImageData(); } /// 将当前的图像信息同步到 measureData 中 void _setCurImageData() { measureData.measureImageData = MeasureImageData( patientCode: _curPatientCode, recordCode: _curRecordCode, remedicalCode: _curRemedicalCode, ); } /// 先加载图像布局配置,然后加载全局数据 void _loadLayoutConfig() async { await LayoutConfiguration.ins.loadData(); setState(() { _initData(); }); } @override void initState() { _curToken = widget.token; _curPatientCode = widget.patientCode; _curRemedicalCode = widget.remedicalCode; _curRecordCode = widget.recordCode; _curNeedRouterBack = widget.needRouterBack; _hideFullScreenLoading = false; _loadLayoutConfig(); _setCurImageData(); measureData.curMeasureDataChanged.addListener(_onUpdateState); measureHandler.onChangeImageLoaded.addListener(_onChangeImage); measureHandler.onChangeFullScreenState .addListener(_onChangeFullScreenState); super.initState(); } @override void dispose() { measureData.curMeasureDataChanged.removeListener(_onUpdateState); measureHandler.onChangeImageLoaded.removeListener(_onChangeImage); measureHandler.onChangeFullScreenState .removeListener(_onChangeFullScreenState); super.dispose(); } @override FWidget build(BuildContext context) { FWidget body; if (!_hideFullScreenLoading) { const loadingWidget = FCenter(child: FCircularProgressIndicator()); body = FRow( children: const [ FExpanded( child: loadingWidget, ), ], ); } else { body = FRow( children: [ FOffstage( child: _MeasureLeftBoard( needRouterBack: _curNeedRouterBack, ), offstage: measureHandler.fullScreenState), const FVerticalDivider(), FExpanded( child: FColumn( mainAxisSize: MainAxisSize.max, children: [ FExpanded( child: FStack( children: [ if (!imageLoaded) MeasureRightBoard( key: ValueKey(measureData.itemCurrentImage), ), if (imageLoaded) const FCenter( child: FCircularProgressIndicator(), ), ], )), FOffstage( child: const MeasureImagesBar(), offstage: measureHandler.fullScreenState), // if (!measureHandler.fullScreenState) const MeasureImagesBar(), ], ), ), ], ); } return FCenter( child: FContainer( color: const Color.fromRGBO(70, 70, 70, 1), child: body, ), ); } /// 获取当前状态下的图像集 Future> _getCurRemedicals() async { List remedicals = []; var value = await measureData.getRemedicalList.call( _curPatientCode, _curRecordCode, _curToken, ); for (var remedicalItemList in value) { remedicals.addAll(remedicalItemList.remedicalList ?? []); } return remedicals; } /// 初始化颈动脉模块 /// [Carotid] ✅遍历出颈动脉信息列表,传给壳子 void _initCarotidModuel(List remedicals) { List ultra3DResourceInfos = []; for (var remedical in remedicals) { if (remedical.carotidResult != null) { ultra3DResourceInfos.add(Ultra3DResourceInfo(remedical.carotidResult!)); } } measure3DViewController.ultra3DResourceInfoList = ultra3DResourceInfos; measure3DViewController.recordId = _curRecordCode; measure3DViewController.notifyShellLoadAllModel(); } /// 初始化卡尺样式部分 Future _initMouseModuel() async { final mouseState = Get.put(MouseState()); final result = await measureData.getMeasureSystemSettingAsync() as MeasureSystemSettingDTO; measureData.measureSystemSetting = result; mouseState.cursorType = getMeasureSystemSettingCursorType(result.cursorType); mouseState.cursorSize = result.cursorSize.toDouble(); } /// 测量页全局数据首次初始化 void _initData() async { List remedicals = await _getCurRemedicals(); _initMouseModuel(); _initCarotidModuel(remedicals); measureData.remedicalList = remedicals; var remedicalInfo = await measureData.getImageInfo.call(_curRemedicalCode, _curToken); if (remedicalInfo != null) { measureData.aiResults = remedicalInfo.diagnosisResult ?? ''; if (remedicalInfo.terminalImages != null) { _hideFullScreenLoading = true; measureData.itemCurrentImage = remedicalInfo.terminalImages!.imageUrl ?? ''; getExamImageInfoList(remedicals); } } measureController.imageLoaded.removeListener(_onImageLoaded); measureController.imageLoaded.addListener(_onImageLoaded); } /// 获取测量图片所需的图片组 并且写入控制器中 加载 void getExamImageInfoList(List remedicals) async { /// 检查中图片信息表 List examImageInfoList = []; for (var element in remedicals) { examImageInfoList.add( ExamImageInfo( element.terminalImages!.imageUrl!, element.terminalImages!.previewUrl!, ), ); } Get.delete(); measureController = Get.put(MeasureController( "", imagesFetchFunc: (code) async { return examImageInfoList; }, )); await measureController.load(); int selectedImageIndex = -1; if (examImageInfoList .any((element) => element.url == measureData.itemCurrentImage)) { ExamImageInfo selectedImage = examImageInfoList.firstWhere( (element) => element.url == measureData.itemCurrentImage, ); selectedImageIndex = examImageInfoList.indexOf(selectedImage); measureController.examInfo.selectedImageIndex = selectedImageIndex; } MeasureLanguage.load(measureData.measureLanguage); } /// vid 切换时会触发到这里 void _onImageLoaded(Object sender, ExamImageInfo? e) async { // measureHandler.changeImageLoaded = true; if (!mounted) return; final currentImage = measureData.remedicalList.firstWhereOrNull( (element) => element.terminalImages!.imageUrl == e!.url, ); if (currentImage != null) { /// 获取图片详细信息 var remedicalInfo = await measureData.getImageInfo( currentImage.remedicalCode ?? '', _curToken, ); if (remedicalInfo != null) { measureData.aiResults = remedicalInfo.diagnosisResult ?? ''; measure3DViewController.initParams(); /// ai 良恶性 判断是否有ai measureData.diagnosisConclusion = remedicalInfo.diagnosisConclusion; if (remedicalInfo.carotidResult != null) { /// [Carotid] ✅详情传入测量信息 measure3DViewController.carotidResult = remedicalInfo.carotidResult!; /// [Carotid] ✅需要在此通知 controller 存在颈动脉信息 measure3DViewController.exist3DData = true; measure3DViewController .handleChangeCarotid2DImage(remedicalInfo.recordCode!); } else { measure3DViewController.exist3DData = false; } // [Carotid] ✅只要更换图片都要切换到Vid 2D模式 if (measure3DViewController.curMeasureMode != MeasureMode.vidMode) { measure3DViewController.backToVidMode(); } try { if (e != null) { Future.delayed(const Duration(milliseconds: 100), () { measureController.playerController.play(); }); setState(() { _hideFullScreenLoading = true; measureHandler.changeImageLoaded = false; }); } } catch (error) { setState(() { _hideFullScreenLoading = true; measureHandler.changeImageLoaded = false; }); } } } } } /// 测量左边操作页面 class _MeasureLeftBoard extends StatefulWidget implements FPage { const _MeasureLeftBoard({Key? key, this.needRouterBack}) : super(key: key); @override final String pageName = 'MeasureLeftBoard'; final bool? needRouterBack; @override State<_MeasureLeftBoard> createState() => _MeasureLeftBoardState(); } class _MeasureLeftBoardState extends State<_MeasureLeftBoard> { final measureHandler = Get.find(); final playerController = Get.find(); final measure3DViewController = Get.find(); /// 数据 late final measureData = Get.find(); /// 测量项控制器 final measureMetaController = Get.find(); /// 是否显示颈动脉2D指定的测量项 bool showCarotid2DSelectMeasure = false; /// 是否显示测量项 bool hideMeasureItems = false; bool isMeasureTool = true; bool get isArrowMeasureAnnotationType => measureHandler.changedAnnotationType == AnnotationType.arrow; TabEnum curSelectedTab = TabEnum.tabMeasureTool; void _onChangedTab( Object sender, TabEnum e, ) { setState(() { curSelectedTab = e; }); } void _onCurItemMetaListChanged(sender, e) { if (mounted) { setState(() {}); } } ///图像发生变化 void _onChangeImage(sender, e) { setState(() {}); } @override void initState() { measureHandler.onChangedTab.addListener(_onChangedTab); measure3DViewController.updatePlayerMode.addListener(_onModeChanged); measureData.curItemMetaListChanged.addListener(_onCurItemMetaListChanged); measureHandler.onChangeImageLoaded.addListener(_onChangeImage); super.initState(); } @override void dispose() { measureHandler.onChangedTab.removeListener(_onChangedTab); measure3DViewController.updatePlayerMode.removeListener(_onModeChanged); measureData.curItemMetaListChanged .removeListener(_onCurItemMetaListChanged); measureHandler.onChangeImageLoaded.removeListener(_onChangeImage); super.dispose(); } /// 模式改变触发更新 void _onModeChanged(Object s, MeasureMode mode) { switch (mode) { case MeasureMode.vidMode: final playerController = Get.find(); // 通过帧加载完成事件通知,去更新测量项 if (playerController.currentFrame != null) { playerController.firstFrameLoaded .emit(this, playerController.currentFrame!); } setState(() { showCarotid2DSelectMeasure = false; hideMeasureItems = false; }); break; case MeasureMode.carotid2DMode: setState(() { showCarotid2DSelectMeasure = true; hideMeasureItems = false; }); break; case MeasureMode.carotid3DMode: setState(() { showCarotid2DSelectMeasure = true; hideMeasureItems = true; }); break; } } bool get hideMeasureTab => (curSelectedTab == TabEnum.tabNodesTool); bool get hideCommentTab => !hideMeasureTab; @override FWidget build(BuildContext context) { final newPlayerController = Get.find(); return FColumn( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, children: [ FOffstage( offstage: hideMeasureItems, child: LeftSiderHold(ifHideConfig: showCarotid2DSelectMeasure), ), FOffstage( offstage: hideMeasureItems || hideCommentTab, child: FContainer( width: 300, key: UniqueKey(), child: LeftSelectInput(), ), ), FOffstage( offstage: hideMeasureItems || hideMeasureTab, child: const LeftMeasureTools(), ), FExpanded( child: FStack( fit: StackFit.passthrough, children: [ FOffstage( offstage: hideMeasureItems || hideMeasureTab, child: FContainer( width: 300, child: showCarotid2DSelectMeasure ? const CarotidLeftSiderSelectMeasure() : FContainer( key: Key(newPlayerController.url), child: const LeftSiderSelectMeasure()), ), ), FOffstage( offstage: hideMeasureItems || hideCommentTab, child: FContainer( width: 300, key: UniqueKey(), child: const MeasureLeftAnnotation(), ), ), ], ), ), FOffstage( offstage: hideMeasureItems || hideCommentTab, child: _MeasureArrow()), FOffstage( offstage: !hideMeasureItems, child: FContainer( width: 300, child: FCenter( child: FElevatedButton( name: "backToVidMode", businessParent: widget, onPressed: () => measure3DViewController.backToVidMode(), child: FText( i18nBook.measure.vidMode.t, style: const TextStyle(color: Colors.white), ), ), ), ), ), const OperateBar(), widget.needRouterBack ?? false ? FContainer( width: 300, padding: const EdgeInsets.only(bottom: 20), child: FIconButton( businessParent: widget, name: "back", onPressed: () { Get.back(); }, icon: const FIcon( Icons.arrow_back, color: Colors.white, size: 36, ), ), ) : const FSizedBox() ], ); } } class _MeasureArrow extends FStatefulWidget { @override FState<_MeasureArrow> createState() => _MeasureArrowState(); } class _MeasureArrowState extends FState<_MeasureArrow> { final measureHandler = Get.find(); bool get isArrowMeasureAnnotationType => measureHandler.changedAnnotationType == AnnotationType.arrow; void onChangedAnnotationType( Object sender, AnnotationType? e, ) { setState(() {}); } @override void initState() { measureHandler.onChangedAnnotationType.addListener(onChangedAnnotationType); super.initState(); } @override void dispose() { measureHandler.onChangedAnnotationType .removeListener(onChangedAnnotationType); super.dispose(); } @override FWidget build(BuildContext context) { return FContainer( height: 60, width: 300, child: FCenter( child: FColumn( children: [ FInk( child: FInkWell( onTap: () { measureHandler.changedAnnotationType = AnnotationType.arrow; final application = Get.find(); application.switchAnnotation(AnnotationType.arrow); }, child: FIcon( Icons.call_made_rounded, color: isArrowMeasureAnnotationType ? Colors.blue : Colors.white, size: 30, ), ), ), FText( i18nBook.measure.arrow.t, style: isArrowMeasureAnnotationType ? const TextStyle( color: Colors.blue, fontSize: 14, ) : const TextStyle( color: Colors.white, fontSize: 14, ), ), ], ), ), ); } }