measure_tool.dart 18 KB

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