measure_tool.dart 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. import 'package:fis_common/logger/logger.dart';
  2. import 'package:fis_i18n/i18n.dart';
  3. import 'package:fis_jsonrpc/rpc.dart';
  4. import 'package:fis_measure/define.dart';
  5. import 'package:fis_measure/interfaces/process/items/item.dart';
  6. import 'package:fis_measure/interfaces/process/items/item_metas.dart';
  7. import 'package:fis_measure/interfaces/process/items/terms.dart';
  8. import 'package:fis_measure/interfaces/process/items/types.dart';
  9. import 'package:fis_measure/interfaces/process/player/play_controller.dart';
  10. import 'package:fis_measure/interfaces/process/workspace/application.dart';
  11. import 'package:fis_measure/process/language/measure_language.dart';
  12. import 'package:fis_measure/process/workspace/measure_data_controller.dart';
  13. import 'package:fis_measure/process/workspace/measure_data_helper.dart';
  14. import 'package:fis_measure/process/workspace/measure_handler.dart';
  15. import 'package:fis_measure/utils/prompt_box.dart';
  16. import 'package:fis_measure/view/measure/combo_widget.dart';
  17. import 'package:fis_measure/view/measure/measure_view_controller.dart';
  18. import 'package:fis_measure/view/player/controller.dart';
  19. import 'package:fis_ui/index.dart';
  20. import 'package:fis_ui/interface/interactive_container.dart';
  21. import 'package:flutter/material.dart';
  22. import 'package:get/get.dart';
  23. import 'package:vid/us/vid_us_image.dart';
  24. import 'custom_item_buttons/combo.dart';
  25. /// 测量项页面
  26. class LeftSiderSelectMeasure extends FStatefulWidget
  27. implements FInteractiveContainer {
  28. const LeftSiderSelectMeasure({Key? key}) : super(key: key);
  29. @override
  30. final String pageName = 'LeftSiderSelectMeasure';
  31. @override
  32. FState<LeftSiderSelectMeasure> createState() => LeftSiderSelectMeasureState();
  33. }
  34. class LeftSiderSelectMeasureState extends FState<LeftSiderSelectMeasure> {
  35. /// 数据
  36. IApplication get application => Get.find<IApplication>();
  37. VidPlayerController get playerController =>
  38. Get.find<IPlayerController>() as VidPlayerController;
  39. final measureHandler = Get.find<MeasureHandler>();
  40. final measureData = Get.find<MeasureDataController>();
  41. final measureMetaController = Get.find<MeasureMetaController>();
  42. /// 测量语言包
  43. final measureLanguage = MeasureLanguage();
  44. /// 当前选中的测量项下标
  45. int activeItemIndex = 0;
  46. /// 当前选中的子测量项下标
  47. int activeChildItemIndex = 0;
  48. /// 上一个测量项
  49. int lastActiveItemIndex = 0;
  50. /// Styles
  51. late String fontFamily;
  52. static const Color buttonBackgroundColor = Color.fromRGBO(70, 70, 70, 1);
  53. static const Color buttonBorderColor = Color.fromRGBO(124, 124, 124, 1);
  54. static const Color buttonBorderHighlight = Color.fromRGBO(124, 124, 124, 1);
  55. static const Color buttonTextColor = Colors.white;
  56. static const Color childButtonHighlight = Color.fromRGBO(84, 144, 249, 1);
  57. static const Color childContainerBackground = Color.fromRGBO(60, 60, 60, 1);
  58. static const double buttonBorderHighlightWidth = 1.0;
  59. /// 触发测量项选中事件
  60. void selectItem(int selectIndex) {
  61. if (selectIndex == activeItemIndex) return;
  62. final itemMeta = measureData.curItemMetaList[selectIndex];
  63. if (itemMeta.buyStatus == WorkingItemStatusEnum.Unpaid) {
  64. PromptBox.toast("如需继续使用该测量项,请联系Vinno申请试用或购买。");
  65. return;
  66. }
  67. changeItem(itemMeta);
  68. // setState(() {
  69. // activeItemIndex = selectIndex;
  70. // activeChildItemIndex = 0;
  71. // });
  72. }
  73. /// 触发子测量项选中事件
  74. void selectChildItem(int selectIndex) {
  75. if (selectIndex == activeChildItemIndex) return;
  76. final item = application.activeMeasureItem!;
  77. if (item is ITopMeasureItem) {
  78. item.switchChild(selectIndex);
  79. activeChildItemIndex = selectIndex;
  80. }
  81. setState(() {});
  82. }
  83. /// 切换测量项
  84. void changeItem(ItemMeta itemMeta) {
  85. int itemIndex =
  86. measureData.curItemMetaList.indexWhere((e) => e.name == itemMeta.name);
  87. int childItemIndex = activeChildItemIndex;
  88. try {
  89. // IMeasureItem? item = application.activeMeasureItem;
  90. // if (item != null &&
  91. // item.meta.name == itemMeta.name &&
  92. // item is ITopMeasureItem &&
  93. // item.isCrossFrameMode) {
  94. // // 跨帧测量,测量项无变化时,自动切到下一个子项
  95. // childItemIndex = item.workingChildIndex + 1;
  96. // if (childItemIndex < item.childItems.length) {
  97. // item.switchChild(childItemIndex);
  98. // item.workingChildChanged.addListener(_onWorkingChildChanged);
  99. // if (mounted) {
  100. // setState(() {
  101. // activeChildItemIndex = childItemIndex;
  102. // });
  103. // }
  104. // return;
  105. // }
  106. // }
  107. application.switchItem(itemMeta);
  108. final item = application.activeMeasureItem!;
  109. if (item is ITopMeasureItem) {
  110. item.switchChild(0);
  111. item.workingChildChanged.addListener(_onWorkingChildChanged);
  112. }
  113. } catch (e) {
  114. logger.e("changeItem failed", e);
  115. }
  116. if (mounted) {
  117. setState(() {
  118. activeItemIndex = itemIndex;
  119. activeChildItemIndex = childItemIndex;
  120. });
  121. }
  122. }
  123. /// 子测量项变化事件监听
  124. void _onWorkingChildChanged(sender, int e) {
  125. setState(() {
  126. activeChildItemIndex = e;
  127. });
  128. }
  129. void _onItemMetaListChanged(Object s, dynamic e) {
  130. try {
  131. if (e != null) {
  132. setState(() {});
  133. }
  134. } catch (e) {
  135. debugPrint("ItemMetaListChanged fail " + e.toString());
  136. }
  137. }
  138. /// 首帧加载完成事件监听: 获取当前图像的测量项,从第一帧取
  139. void _onFirstFrameLoaded(Object sender, VidUsImage e) async {
  140. if (!mounted) return;
  141. _getMeasureItemsList(e);
  142. _getAnnotationList();
  143. }
  144. //初始化时尝试加载第一项测量项(如果有的话)
  145. void _loadFirstItem() {
  146. if (measureData.curItemMetaList.isNotEmpty) {
  147. if (_checkAndHostCrossFrameMeasureItem()) {
  148. return;
  149. }
  150. final itemMeta = measureData.curItemMetaList.first;
  151. changeItem(itemMeta);
  152. // setState(() {
  153. // activeItemIndex = 0;
  154. // activeChildItemIndex = 0;
  155. // });
  156. }
  157. }
  158. /// 注释获取
  159. void _getAnnotationList() async {
  160. List<String> annotationList = [];
  161. var measureCommentItemResult =
  162. await MeasureDataHelper.getCommentsByApplicationAsync(
  163. application.applicationName,
  164. application.categoryName,
  165. );
  166. measureData.measureCommentItemResult =
  167. measureCommentItemResult?.commentItems ?? [];
  168. measureCommentItemResult?.commentItems?.forEach((element) {
  169. annotationList.add(element.text ?? '');
  170. });
  171. measureData.annotationList = annotationList;
  172. measureHandler.onAnnotationsLoaded();
  173. }
  174. void _getMeasureItemsList(VidUsImage e) async {
  175. List<String> getModes = [];
  176. for (var element in e.visuals[0].modes) {
  177. getModes.add(element.type.toString().split('.')[1]);
  178. }
  179. measureData.applicationModes = e.visuals[0].modes;
  180. measureData.currentMode = e.visuals[0].modes.first.type.name;
  181. var measureModeSelection = MeasureModeSelection(
  182. application.applicationName,
  183. application.categoryName,
  184. application.isThirdPart ? ['TPPTissue'] : getModes,
  185. );
  186. measureHandler.measureModeChanged = measureModeSelection;
  187. var measureApplicationDTO =
  188. await MeasureDataHelper.getMeasureApplication(measureModeSelection);
  189. if (measureApplicationDTO != null) {
  190. measureData.measureApplicationVersion =
  191. measureApplicationDTO.version ?? '';
  192. measureData.availableModes = measureApplicationDTO.availableModes ?? [];
  193. // final lvdMassItem = measureData.availableModes.first.availableGroups!
  194. // .first.availableFolders!.first.availableItems!
  195. // .where((e) => e.measureTypeName == "LvdMass")
  196. // .first;
  197. measureData.availableModes.first.availableGroups!.first.availableFolders!
  198. .first.workingItemNames!
  199. .add("LV Mass");
  200. if (application.isThirdPart) {
  201. measureData.currentMode = 'TPPTissue';
  202. }
  203. measureMetaController.setAvailableModes(measureData.currentMode);
  204. setState(() {});
  205. }
  206. }
  207. /// 测量项列表变化事件监听
  208. void _onCurItemMetaListChanged(sender, e) {
  209. if (measureData.curItemMetaList.isNotEmpty) {
  210. if (_checkAndHostCrossFrameMeasureItem()) {
  211. return;
  212. }
  213. changeItem(measureData.curItemMetaList[0]);
  214. }
  215. // setState(() {
  216. // activeItemIndex = 0;
  217. // activeChildItemIndex = 0;
  218. // });
  219. }
  220. bool _checkAndHostCrossFrameMeasureItem() {
  221. final crossCxt = application.crossFrameContext;
  222. if (crossCxt != null && !crossCxt.isOver) {
  223. final activeItem = application.activeMeasureItem;
  224. if (activeItem != null &&
  225. activeItem is ITopMeasureItem &&
  226. activeItem.isCrossFrameMode) {
  227. // 跨帧时&当前是跨帧测量项,则保持此测量项
  228. changeItem(activeItem.meta);
  229. return true;
  230. }
  231. }
  232. return false;
  233. }
  234. /// 是否显示测量项翻译事件变化
  235. void _onShowItemTransStateChanged(_, e) {
  236. setState(() {});
  237. }
  238. /// 鼠标右键结束测量事件【即切换到空测量项】,此时缓存被结束的测量项,如果再次收到右击事件,恢复被结束的测量项
  239. void _onRightClickFinishMeasure(_, e) {
  240. if (activeItemIndex == -1) {
  241. changeItem(measureData.curItemMetaList[lastActiveItemIndex]);
  242. setState(() {
  243. activeItemIndex = lastActiveItemIndex;
  244. activeChildItemIndex = 0;
  245. });
  246. } else {
  247. lastActiveItemIndex = activeItemIndex;
  248. final emptyItem = ItemMeta(
  249. "EmptyItem",
  250. measureType: MeasureTypes.Empty,
  251. description: "",
  252. outputs: [],
  253. );
  254. changeItem(emptyItem);
  255. setState(() {
  256. activeItemIndex = -1;
  257. activeChildItemIndex = 0;
  258. });
  259. }
  260. }
  261. @override
  262. void initState() {
  263. measureData.curItemMetaListChanged.addListener(_onCurItemMetaListChanged);
  264. measureData.itemMetaListChanged.addListener(_onItemMetaListChanged);
  265. measureData.showItemTransStateChanged
  266. .addListener(_onShowItemTransStateChanged);
  267. measureHandler.onRightClickFinishMeasure
  268. .addListener(_onRightClickFinishMeasure);
  269. WidgetsBinding.instance.addPostFrameCallback((call) {
  270. _loadFirstItem();
  271. playerController.firstFrameLoaded.addListener(_onFirstFrameLoaded);
  272. });
  273. super.initState();
  274. }
  275. @override
  276. dispose() {
  277. super.dispose();
  278. playerController.firstFrameLoaded.removeListener(_onFirstFrameLoaded);
  279. measureData.curItemMetaListChanged
  280. .removeListener(_onCurItemMetaListChanged);
  281. measureData.itemMetaListChanged.removeListener(_onItemMetaListChanged);
  282. measureData.showItemTransStateChanged
  283. .removeListener(_onShowItemTransStateChanged);
  284. measureHandler.onRightClickFinishMeasure
  285. .removeListener(_onRightClickFinishMeasure);
  286. final item = application.activeMeasureItem;
  287. if (item != null && item is ITopMeasureItem) {
  288. item.workingChildChanged.removeListener(_onWorkingChildChanged);
  289. }
  290. }
  291. @override
  292. FWidget build(BuildContext context) {
  293. fontFamily = Theme.of(context).textTheme.labelLarge!.fontFamily!;
  294. return buildItemsListView(measureData.curItemMetaList);
  295. }
  296. /// 构建测量项列表
  297. FWidget buildItemsListView(List<ItemMeta> itemMetaList) {
  298. return FListView(
  299. shrinkWrap: true,
  300. controller: ScrollController(),
  301. children: List.generate(itemMetaList.length, (i) {
  302. return _buildItemContainer(i);
  303. }));
  304. }
  305. /// 构建测量项容器
  306. FWidget _buildItemContainer(int itemMetaIndex) {
  307. final itemMeta = measureData.curItemMetaList[itemMetaIndex];
  308. final isActived = itemMetaIndex == activeItemIndex; // 是否选中
  309. final hasChildItem = itemMeta.childItems.isNotEmpty; //是否为组合测量项
  310. return FContainer(
  311. decoration: BoxDecoration(
  312. borderRadius: BorderRadius.circular(5),
  313. border: isActived && hasChildItem
  314. ? Border.all(
  315. color: buttonBorderHighlight,
  316. width: buttonBorderHighlightWidth)
  317. : null,
  318. ),
  319. margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
  320. child: FStack(
  321. children: [
  322. FColumn(children: [
  323. if (isActived && hasChildItem) ...[
  324. _buildItemTitle(itemMetaIndex),
  325. _buildChildItemContainer(itemMeta),
  326. ] else
  327. FContainer(
  328. width: double.infinity,
  329. child: _buildItemButton(itemMetaIndex),
  330. ),
  331. ]),
  332. // if (itemMeta.buyStatus != null)
  333. // FPositioned(
  334. // top: 0,
  335. // right: 0,
  336. // child: _buildItemBuyStatusName(
  337. // itemMeta.buyStatus ?? WorkingItemStatusEnum.Free,
  338. // ),
  339. // )
  340. ],
  341. ));
  342. }
  343. // 构建测量项是否需要购买
  344. FWidget _buildItemBuyStatusName(WorkingItemStatusEnum buyStatus) {
  345. String name = "";
  346. Color textColor = Colors.white;
  347. switch (buyStatus) {
  348. case WorkingItemStatusEnum.Probation:
  349. name = i18nBook.measure.probation.t;
  350. textColor = Colors.yellow;
  351. break;
  352. case WorkingItemStatusEnum.Unpaid:
  353. name = i18nBook.measure.unpaid.t;
  354. textColor = Colors.red;
  355. break;
  356. case WorkingItemStatusEnum.Purchased:
  357. name = i18nBook.measure.purchased.t;
  358. textColor = childButtonHighlight;
  359. break;
  360. case WorkingItemStatusEnum.Expired:
  361. name = i18nBook.measure.expired.t;
  362. textColor = Colors.red;
  363. break;
  364. default:
  365. break;
  366. }
  367. if (name.isNotEmpty) {
  368. return QuickFWidget(
  369. RawChip(
  370. backgroundColor: textColor.withOpacity(0.8),
  371. label: FText(
  372. name,
  373. style: TextStyle(
  374. color: Colors.white,
  375. fontFamily: fontFamily,
  376. fontSize: 12,
  377. ),
  378. ),
  379. // isEnabled: false,//禁止点选状态
  380. labelPadding: const EdgeInsets.symmetric(horizontal: 4),
  381. shape: OutlinedBorder.lerp(
  382. RoundedRectangleBorder(
  383. borderRadius: BorderRadius.circular(4),
  384. ),
  385. RoundedRectangleBorder(
  386. borderRadius: BorderRadius.circular(4),
  387. ),
  388. 0,
  389. )
  390. // padding: EdgeInsets.only(left: 10, right: 10, top: 5),
  391. ),
  392. );
  393. }
  394. return const FSizedBox();
  395. }
  396. // 构建测量项按钮
  397. FWidget _buildItemButton(int itemIndex) {
  398. final itemMeta = measureData.curItemMetaList[itemIndex];
  399. final transValue = measureLanguage.t('measure', itemMeta.description);
  400. // 非英文环境下显示中英文,否则只显示英文
  401. final ifShowCN = !i18nBook.isCurrentEnglish &&
  402. transValue != itemMeta.description &&
  403. measureData.measureSystemSetting.showAnnotation;
  404. final isActived = itemIndex == activeItemIndex;
  405. return FElevatedButton(
  406. name: itemMeta.name,
  407. businessParent: widget,
  408. onPressed: () => selectItem(itemIndex),
  409. child: FColumn(
  410. mainAxisAlignment: MainAxisAlignment.center,
  411. children: [
  412. if (ifShowCN)
  413. FText(
  414. transValue,
  415. maxLines: 1,
  416. overflow: TextOverflow.ellipsis,
  417. style: TextStyle(
  418. color: isActived ? null : buttonTextColor,
  419. fontFamily: fontFamily,
  420. fontSize: 12,
  421. ),
  422. ),
  423. FText(
  424. itemMeta.description,
  425. style: TextStyle(
  426. color: isActived ? null : buttonTextColor,
  427. ),
  428. ),
  429. ],
  430. ),
  431. style: ElevatedButton.styleFrom(
  432. backgroundColor: isActived ? null : buttonBackgroundColor,
  433. fixedSize: const Size.fromHeight(
  434. 50,
  435. ),
  436. side: BorderSide(
  437. color: isActived ? Colors.transparent : buttonBorderColor,
  438. ),
  439. ),
  440. );
  441. }
  442. // 构建测量项标签【用于组合测量项顶部】
  443. FWidget _buildItemTitle(int itemIndex) {
  444. final itemMeta = measureData.curItemMetaList[itemIndex];
  445. final transValue = measureLanguage.t('measure', itemMeta.description);
  446. // 非英文环境下显示中英文,否则只显示英文
  447. final ifShowCN = !i18nBook.isCurrentEnglish &&
  448. transValue != itemMeta.description &&
  449. measureData.measureSystemSetting.showAnnotation;
  450. return FContainer(
  451. decoration: const BoxDecoration(
  452. border: Border(
  453. bottom: BorderSide(
  454. color: buttonBorderHighlight, width: buttonBorderHighlightWidth),
  455. ),
  456. ),
  457. child: FSizedBox(
  458. height: 50,
  459. width: double.infinity,
  460. child: FColumn(
  461. mainAxisAlignment: MainAxisAlignment.center,
  462. children: [
  463. if (ifShowCN)
  464. FText(
  465. transValue,
  466. maxLines: 1,
  467. overflow: TextOverflow.ellipsis,
  468. style: TextStyle(
  469. color: buttonTextColor,
  470. fontFamily: fontFamily,
  471. fontSize: 12,
  472. ),
  473. ),
  474. FText(
  475. itemMeta.description,
  476. style: const TextStyle(
  477. color: buttonTextColor,
  478. ),
  479. ),
  480. ],
  481. ),
  482. ),
  483. );
  484. }
  485. /// 子测量项容器
  486. FWidget _buildChildItemContainer(ItemMeta itemMeta) {
  487. final activeName = itemMeta.description;
  488. final isSpecial = MeasurespecialsupportedTerms.items.contains(activeName);
  489. if (isSpecial) {
  490. //是否为特殊组合测量项
  491. return const SpecialItemHR();
  492. } else if (CustomComboItemGroup.specialItemTypes
  493. .contains(itemMeta.measureType)) {
  494. return CustomComboItemGroup(
  495. activeIndex: activeChildItemIndex,
  496. businessParent: widget,
  497. itemMeta: itemMeta,
  498. onChildClick: (int childItemIndex) {
  499. selectChildItem(childItemIndex);
  500. },
  501. );
  502. } else {
  503. return FContainer(
  504. decoration: const BoxDecoration(
  505. color: childContainerBackground,
  506. borderRadius: BorderRadius.only(
  507. bottomLeft: Radius.circular(5), bottomRight: Radius.circular(5)),
  508. ),
  509. padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 5),
  510. child: FColumn(
  511. children: (itemMeta.childItems)
  512. .asMap()
  513. .entries
  514. .map(
  515. (e) => _buildChildItemButton(
  516. e.value,
  517. e.key,
  518. ),
  519. )
  520. .toList(),
  521. ),
  522. );
  523. }
  524. }
  525. // 构建子测量项按钮
  526. FWidget _buildChildItemButton(ItemMeta itemMeta, int childItemIndex) {
  527. final isActived = childItemIndex == activeChildItemIndex;
  528. return FContainer(
  529. width: double.infinity,
  530. margin: const EdgeInsetsDirectional.all(5),
  531. child: FElevatedButton(
  532. businessParent: widget,
  533. name: "selectChildItemIdnex:$childItemIndex",
  534. onPressed: () {
  535. selectChildItem(childItemIndex);
  536. },
  537. child: FText(
  538. itemMeta.description,
  539. ),
  540. style: ElevatedButton.styleFrom(
  541. backgroundColor: isActived ? null : buttonBackgroundColor,
  542. side: BorderSide(
  543. color: isActived ? Colors.transparent : buttonBorderColor,
  544. ),
  545. ),
  546. ));
  547. }
  548. }