import 'package:fis_common/logger/logger.dart'; import 'package:fis_i18n/i18n.dart'; import 'package:fis_jsonrpc/rpc.dart'; import 'package:fis_measure/define.dart'; import 'package:fis_measure/interfaces/process/items/item.dart'; import 'package:fis_measure/interfaces/process/items/item_metas.dart'; import 'package:fis_measure/interfaces/process/items/terms.dart'; import 'package:fis_measure/interfaces/process/items/types.dart'; import 'package:fis_measure/interfaces/process/player/play_controller.dart'; import 'package:fis_measure/interfaces/process/workspace/application.dart'; import 'package:fis_measure/process/language/measure_language.dart'; import 'package:fis_measure/process/workspace/measure_data_controller.dart'; import 'package:fis_measure/process/workspace/measure_data_helper.dart'; import 'package:fis_measure/process/workspace/measure_handler.dart'; import 'package:fis_measure/view/measure/combo_widget.dart'; import 'package:fis_measure/view/measure/measure_view_controller.dart'; import 'package:fis_measure/view/player/controller.dart'; import 'package:fis_ui/index.dart'; import 'package:fis_ui/interface/interactive_container.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:vid/us/vid_us_image.dart'; import 'custom_item_buttons/combo.dart'; /// 测量项页面 class LeftSiderSelectMeasure extends FStatefulWidget implements FInteractiveContainer { const LeftSiderSelectMeasure({Key? key}) : super(key: key); @override final String pageName = 'LeftSiderSelectMeasure'; @override FState createState() => LeftSiderSelectMeasureState(); } class LeftSiderSelectMeasureState extends FState { /// 数据 IApplication get application => Get.find(); VidPlayerController get playerController => Get.find() as VidPlayerController; final measureHandler = Get.find(); final measureData = Get.find(); final measureMetaController = Get.find(); /// 测量语言包 final measureLanguage = MeasureLanguage(); /// 当前选中的测量项下标 int activeItemIndex = 0; /// 当前选中的子测量项下标 int activeChildItemIndex = 0; /// 上一个测量项 int lastActiveItemIndex = 0; /// 【TODO】 翻译 String selectedType = "手动"; String selectedLocation = "上"; /// Styles late String fontFamily; static const Color buttonBackgroundColor = Color.fromRGBO(70, 70, 70, 1); static const Color buttonBorderColor = Color.fromRGBO(124, 124, 124, 1); static const Color buttonBorderHighlight = Color.fromRGBO(124, 124, 124, 1); static const Color buttonTextColor = Colors.white; static const Color childButtonHighlight = Color.fromRGBO(84, 144, 249, 1); static const Color childContainerBackground = Color.fromRGBO(60, 60, 60, 1); static const double buttonBorderHighlightWidth = 1.0; /// 触发测量项选中事件 void selectItem(int selectIndex) { if (selectIndex == activeItemIndex) return; final itemMeta = measureData.curItemMetaList[selectIndex]; /// 【TODO】 暂时屏蔽 buyStatus // if (itemMeta.buyStatus == WorkingItemStatusEnum.Unpaid) { // PromptBox.toast("如需继续使用该测量项,请联系Vinno申请试用或购买。"); // return; // } changeItem(itemMeta); // setState(() { // activeItemIndex = selectIndex; // activeChildItemIndex = 0; // }); } /// 触发子测量项选中事件 void selectChildItem(int selectIndex) { if (selectIndex == activeChildItemIndex) return; final item = application.activeMeasureItem!; if (item is ITopMeasureItem) { item.switchChild(selectIndex); activeChildItemIndex = selectIndex; } setState(() {}); } /// 切换测量项 void changeItem(ItemMeta itemMeta) { int itemIndex = measureData.curItemMetaList.indexWhere((e) => e.name == itemMeta.name); int childItemIndex = activeChildItemIndex; try { // IMeasureItem? item = application.activeMeasureItem; // if (item != null && // item.meta.name == itemMeta.name && // item is ITopMeasureItem && // item.isCrossFrameMode) { // // 跨帧测量,测量项无变化时,自动切到下一个子项 // childItemIndex = item.workingChildIndex + 1; // if (childItemIndex < item.childItems.length) { // item.switchChild(childItemIndex); // item.workingChildChanged.addListener(_onWorkingChildChanged); // if (mounted) { // setState(() { // activeChildItemIndex = childItemIndex; // }); // } // return; // } // } application.switchItem(itemMeta); final item = application.activeMeasureItem!; if (item is ITopMeasureItem) { item.switchChild(0); item.workingChildChanged.addListener(_onWorkingChildChanged); } } catch (e) { logger.e("changeItem failed", e); } if (mounted) { setState(() { activeItemIndex = itemIndex; activeChildItemIndex = childItemIndex; }); } } /// 子测量项变化事件监听 void _onWorkingChildChanged(sender, int e) { setState(() { activeChildItemIndex = e; }); } void _onItemMetaListChanged(Object s, dynamic e) { try { if (e != null) { setState(() {}); } } catch (e) { debugPrint("ItemMetaListChanged fail " + e.toString()); } } /// 首帧加载完成事件监听: 获取当前图像的测量项,从第一帧取 void _onFirstFrameLoaded(Object sender, VidUsImage e) async { if (!mounted) return; _getMeasureItemsList(e); _getAnnotationList(); } //初始化时尝试加载第一项测量项(如果有的话) void _loadFirstItem() { if (measureData.curItemMetaList.isNotEmpty) { if (_checkAndHostCrossFrameMeasureItem()) { return; } final itemMeta = measureData.curItemMetaList.first; changeItem(itemMeta); // setState(() { // activeItemIndex = 0; // activeChildItemIndex = 0; // }); } } /// 注释获取 void _getAnnotationList() async { List annotationList = []; var measureCommentItemResult = await MeasureDataHelper.getCommentsByApplicationAsync( application.applicationName, application.categoryName, ); measureData.measureCommentItemResult = measureCommentItemResult?.commentItems ?? []; measureCommentItemResult?.commentItems?.forEach((element) { annotationList.add(element.text ?? ''); }); measureData.annotationList = annotationList; measureHandler.onAnnotationsLoaded(); } void _getMeasureItemsList(VidUsImage e) async { List getModes = []; for (var element in e.visuals[0].modes) { getModes.add(element.type.toString().split('.')[1]); } measureData.applicationModes = e.visuals[0].modes; measureData.currentMode = e.visuals[0].modes.first.type.name; var measureModeSelection = MeasureModeSelection( application.applicationName, application.categoryName, application.isThirdPart ? ['TPPTissue'] : getModes, ); measureHandler.measureModeChanged = measureModeSelection; var measureApplicationDTO = await MeasureDataHelper.getMeasureApplication(measureModeSelection); if (measureApplicationDTO != null) { measureData.measureApplicationVersion = measureApplicationDTO.version ?? ''; measureData.availableModes = measureApplicationDTO.availableModes ?? []; if (application.isThirdPart) { measureData.currentMode = 'TPPTissue'; } measureMetaController.setAvailableModes(measureData.currentMode); setState(() {}); } } /// 测量项列表变化事件监听 void _onCurItemMetaListChanged(sender, e) { if (measureData.curItemMetaList.isNotEmpty) { if (_checkAndHostCrossFrameMeasureItem()) { return; } changeItem(measureData.curItemMetaList[0]); } // setState(() { // activeItemIndex = 0; // activeChildItemIndex = 0; // }); } bool _checkAndHostCrossFrameMeasureItem() { final crossCxt = application.crossFrameContext; if (crossCxt != null && !crossCxt.isOver) { final activeItem = application.activeMeasureItem; if (activeItem != null && activeItem is ITopMeasureItem && activeItem.isCrossFrameMode) { // 跨帧时&当前是跨帧测量项,则保持此测量项 changeItem(activeItem.meta); return true; } } return false; } /// 是否显示测量项翻译事件变化 void _onShowItemTransStateChanged(_, e) { setState(() {}); } /// 鼠标右键结束测量事件【即切换到空测量项】,此时缓存被结束的测量项,如果再次收到右击事件,恢复被结束的测量项 void _onRightClickFinishMeasure(_, e) { if (activeItemIndex == -1) { changeItem(measureData.curItemMetaList[lastActiveItemIndex]); setState(() { activeItemIndex = lastActiveItemIndex; activeChildItemIndex = 0; }); } else { lastActiveItemIndex = activeItemIndex; final emptyItem = ItemMeta( "EmptyItem", measureType: MeasureTypes.Empty, description: "", outputs: [], ); changeItem(emptyItem); setState(() { activeItemIndex = -1; activeChildItemIndex = 0; }); } } @override void initState() { measureData.curItemMetaListChanged.addListener(_onCurItemMetaListChanged); measureData.itemMetaListChanged.addListener(_onItemMetaListChanged); measureData.showItemTransStateChanged .addListener(_onShowItemTransStateChanged); measureHandler.onRightClickFinishMeasure .addListener(_onRightClickFinishMeasure); WidgetsBinding.instance.addPostFrameCallback((call) { _loadFirstItem(); playerController.firstFrameLoaded.addListener(_onFirstFrameLoaded); }); super.initState(); } @override dispose() { super.dispose(); playerController.firstFrameLoaded.removeListener(_onFirstFrameLoaded); measureData.curItemMetaListChanged .removeListener(_onCurItemMetaListChanged); measureData.itemMetaListChanged.removeListener(_onItemMetaListChanged); measureData.showItemTransStateChanged .removeListener(_onShowItemTransStateChanged); measureHandler.onRightClickFinishMeasure .removeListener(_onRightClickFinishMeasure); final item = application.activeMeasureItem; if (item != null && item is ITopMeasureItem) { item.workingChildChanged.removeListener(_onWorkingChildChanged); } } @override FWidget build(BuildContext context) { fontFamily = Theme.of(context).textTheme.labelLarge!.fontFamily!; return buildItemsListView(measureData.curItemMetaList); } /// 构建测量项列表 FWidget buildItemsListView(List itemMetaList) { return FListView( shrinkWrap: true, controller: ScrollController(), children: List.generate(itemMetaList.length, (i) { return _buildItemContainer(i); })); } /// 构建测量项容器 FWidget _buildItemContainer(int itemMetaIndex) { final itemMeta = measureData.curItemMetaList[itemMetaIndex]; final isActived = itemMetaIndex == activeItemIndex; // 是否选中 final hasChildItem = itemMeta.childItems.isNotEmpty; //是否为组合测量项 return FContainer( decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), border: isActived && hasChildItem ? Border.all( color: buttonBorderHighlight, width: buttonBorderHighlightWidth) : null, ), margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: FStack( children: [ FColumn(children: [ if (isActived && hasChildItem) ...[ _buildItemTitle(itemMetaIndex), _buildChildItemContainer(itemMeta), ] else FContainer( width: double.infinity, child: _buildItemButton(itemMetaIndex), ), ]), // if (itemMeta.buyStatus != null) // FPositioned( // top: 0, // right: 0, // child: _buildItemBuyStatusName( // itemMeta.buyStatus ?? WorkingItemStatusEnum.Free, // ), // ) ], )); } // 构建测量项是否需要购买 FWidget _buildItemBuyStatusName(WorkingItemStatusEnum buyStatus) { String name = ""; Color textColor = Colors.white; switch (buyStatus) { case WorkingItemStatusEnum.Probation: name = i18nBook.measure.probation.t; textColor = Colors.yellow; break; case WorkingItemStatusEnum.Unpaid: name = i18nBook.measure.unpaid.t; textColor = Colors.red; break; case WorkingItemStatusEnum.Purchased: name = i18nBook.measure.purchased.t; textColor = childButtonHighlight; break; case WorkingItemStatusEnum.Expired: name = i18nBook.measure.expired.t; textColor = Colors.red; break; default: break; } if (name.isNotEmpty) { return QuickFWidget( RawChip( backgroundColor: textColor.withOpacity(0.8), label: FText( name, style: TextStyle( color: Colors.white, fontFamily: fontFamily, fontSize: 12, ), ), // isEnabled: false,//禁止点选状态 labelPadding: const EdgeInsets.symmetric(horizontal: 4), shape: OutlinedBorder.lerp( RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), ), RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), ), 0, ) // padding: EdgeInsets.only(left: 10, right: 10, top: 5), ), ); } return const FSizedBox(); } // 构建测量项按钮 FWidget _buildItemButton(int itemIndex) { final itemMeta = measureData.curItemMetaList[itemIndex]; final transValue = measureLanguage.t('measure', itemMeta.description); // 非英文环境下显示中英文,否则只显示英文 final ifShowCN = !i18nBook.isCurrentEnglish && transValue != itemMeta.description && measureData.measureSystemSetting.showAnnotation; final isActived = itemIndex == activeItemIndex; if (AutomaticMeasureSupportedTerms.items.contains(itemMeta.description)) { return _buildItemAutomaticMeasure( itemIndex, itemMeta, ifShowCN, transValue, isActived, ); } return FElevatedButton( name: itemMeta.name, businessParent: widget, onPressed: () => selectItem(itemIndex), child: FColumn( mainAxisAlignment: MainAxisAlignment.center, children: [ if (ifShowCN) FText( transValue, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: isActived ? null : buttonTextColor, fontFamily: fontFamily, fontSize: 12, ), ), FText( itemMeta.description, style: TextStyle( color: isActived ? null : buttonTextColor, ), ), ], ), style: ElevatedButton.styleFrom( backgroundColor: isActived ? null : buttonBackgroundColor, fixedSize: const Size.fromHeight( 50, ), side: BorderSide( color: isActived ? Colors.transparent : buttonBorderColor, ), ), ); } /// 构建特殊的自动测量项 FWidget _buildItemAutomaticMeasure( int itemIndex, ItemMeta itemMeta, bool ifShowCN, String transValue, bool isActived, ) { return FColumn( children: [ FRow( mainAxisSize: MainAxisSize.max, children: [ FExpanded( child: FElevatedButton( name: itemMeta.name, businessParent: widget, onPressed: () => selectItem(itemIndex), child: FColumn( mainAxisAlignment: MainAxisAlignment.center, children: [ if (ifShowCN) FText( transValue + "($selectedType)", maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: isActived ? null : buttonTextColor, fontFamily: fontFamily, fontSize: 12, ), ), FText( itemMeta.description, style: TextStyle( color: isActived ? null : buttonTextColor, ), ), ], ), style: ElevatedButton.styleFrom( backgroundColor: isActived ? null : buttonBackgroundColor, fixedSize: const Size.fromHeight( 50, ), side: BorderSide( color: isActived ? Colors.transparent : buttonBorderColor, ), ), ), ), ], ), if (isActived) ...[ FContainer( width: double.maxFinite, decoration: const BoxDecoration( color: childContainerBackground, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(5), bottomRight: Radius.circular(5), ), ), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 5), child: FColumn( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// 【TODO】 翻译 测量方式 FContainer( margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), child: const FText( '测量方式', style: TextStyle( fontSize: 12, color: buttonTextColor, ), ), ), FRow( children: ["自动", "半自动", "手动"] .map((e) => _buildMeasureButton(e, true)) .toList(), ), if (selectedType == "自动") ...[ FContainer( margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), child: const FText( '跟踪方向', style: TextStyle( fontSize: 12, color: buttonTextColor, ), ), ), FRow( children: ["上", "下", "全部"] .map((e) => _buildMeasureButton(e, false)) .toList(), ), ] ], ), ), ] ], ); } FWidget _buildMeasureButton(String type, bool isType) { return FExpanded( child: FInkWell( onTap: () { if (isType) { selectedType = type; } else { selectedLocation = type; } setState(() {}); }, child: FContainer( height: 40, margin: const EdgeInsets.symmetric(horizontal: 2), decoration: BoxDecoration( color: (isType ? selectedType == type : selectedLocation == type) ? const Color.fromARGB(255, 101, 211, 71) : const Color.fromRGBO(70, 70, 70, 1), border: Border.all(color: const Color.fromRGBO(124, 124, 124, 1)), borderRadius: BorderRadius.circular(5), ), // margin: const EdgeInsets.symmetric(horizontal: 11, vertical: 5), child: FCenter( child: FText( type.toString(), style: const TextStyle( color: Colors.white, ), ), ), ), ), ); } // 构建测量项标签【用于组合测量项顶部】 FWidget _buildItemTitle(int itemIndex) { final itemMeta = measureData.curItemMetaList[itemIndex]; final transValue = measureLanguage.t('measure', itemMeta.description); // 非英文环境下显示中英文,否则只显示英文 final ifShowCN = !i18nBook.isCurrentEnglish && transValue != itemMeta.description && measureData.measureSystemSetting.showAnnotation; return FContainer( decoration: const BoxDecoration( border: Border( bottom: BorderSide( color: buttonBorderHighlight, width: buttonBorderHighlightWidth), ), ), child: FSizedBox( height: 50, width: double.infinity, child: FColumn( mainAxisAlignment: MainAxisAlignment.center, children: [ if (ifShowCN) FText( transValue, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: buttonTextColor, fontFamily: fontFamily, fontSize: 12, ), ), FText( itemMeta.description, style: const TextStyle( color: buttonTextColor, ), ), ], ), ), ); } /// 子测量项容器 FWidget _buildChildItemContainer(ItemMeta itemMeta) { final activeName = itemMeta.description; final isSpecial = MeasurespecialsupportedTerms.items.contains(activeName); if (isSpecial) { //是否为特殊组合测量项 return const SpecialItemHR(); } else if (CustomComboItemGroup.specialItemTypes .contains(itemMeta.measureType)) { return CustomComboItemGroup( activeIndex: activeChildItemIndex, businessParent: widget, itemMeta: itemMeta, onChildClick: (int childItemIndex) { selectChildItem(childItemIndex); }, ); } else { return FContainer( decoration: const BoxDecoration( color: childContainerBackground, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(5), bottomRight: Radius.circular(5)), ), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 5), child: FColumn( children: (itemMeta.childItems) .asMap() .entries .map( (e) => _buildChildItemButton( e.value, e.key, ), ) .toList(), ), ); } } // 构建子测量项按钮 FWidget _buildChildItemButton(ItemMeta itemMeta, int childItemIndex) { final isActived = childItemIndex == activeChildItemIndex; return FContainer( width: double.infinity, margin: const EdgeInsetsDirectional.all(5), child: FElevatedButton( businessParent: widget, name: "selectChildItemIdnex:$childItemIndex", onPressed: () { selectChildItem(childItemIndex); }, child: FText( itemMeta.description, ), style: ElevatedButton.styleFrom( backgroundColor: isActived ? null : buttonBackgroundColor, side: BorderSide( color: isActived ? Colors.transparent : buttonBorderColor, ), ), )); } }