menu_button_group.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. import 'package:fis_i18n/i18n.dart';
  2. import 'package:fis_jsonrpc/rpc.dart';
  3. import 'package:fis_lib_qrcode/qr_flutter.dart';
  4. import 'package:fis_measure/interfaces/process/player/play_controller.dart';
  5. import 'package:fis_measure/interfaces/process/standard_line/calibration.dart';
  6. import 'package:fis_measure/interfaces/process/workspace/application.dart';
  7. import 'package:fis_measure/interfaces/process/workspace/measure_3d_view_controller.dart';
  8. import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart';
  9. import 'package:fis_measure/process/workspace/measure_data_controller.dart';
  10. import 'package:fis_measure/process/workspace/measure_handler.dart';
  11. import 'package:fis_measure/utils/prompt_box.dart';
  12. import 'package:fis_measure/view/loadding/loadding.dart';
  13. import 'package:fis_measure/view/measure/operate_type_change_button.dart';
  14. import 'package:fis_measure/view/paint/ai_patint_controller.dart';
  15. import 'package:fis_measure/view/player/controller.dart';
  16. import 'package:fis_ui/base_define/page.dart';
  17. import 'package:fis_ui/index.dart';
  18. import 'package:flutter/material.dart';
  19. import 'package:get/get.dart';
  20. // import 'package:url_launcher/url_launcher.dart';
  21. class FMenuButtonGroup extends FStatefulWidget {
  22. final VoidCallback capturePng;
  23. const FMenuButtonGroup({
  24. Key? key,
  25. required this.capturePng,
  26. required this.businessParent,
  27. }) : super(key: key);
  28. final FPage businessParent;
  29. @override
  30. FState<FMenuButtonGroup> createState() => _FMenuButtonGroupState();
  31. }
  32. class _FMenuButtonGroupState extends FState<FMenuButtonGroup> {
  33. // 单个按钮高度
  34. static const double buttonHeight = 30;
  35. // 按钮外边距
  36. static const double buttonMargin = 10;
  37. // 单个按钮总高度 = Content(42)+Margin(10)
  38. // static const double singleButtonHeight = buttonHeight + buttonMargin;
  39. // 按钮组宽度
  40. static const double buttonGroupWidth = 60;
  41. // 按钮组容器底部内边距 30 + 30
  42. static const double groupContainerPadding = buttonGroupWidth / 2;
  43. /// 获取当前组件最大高度
  44. get maxGroupHeight => Get.height - 200;
  45. final application = Get.find<IApplication>();
  46. /// 数据
  47. final measureData = Get.find<MeasureDataController>();
  48. final playerController = Get.find<IPlayerController>() as VidPlayerController;
  49. final measure3DViewController = Get.find<Measure3DViewController>();
  50. late final measureHandler = Get.find<MeasureHandler>();
  51. late final aiPatintController = Get.find<AiPatintController>();
  52. late bool canShowAI = [
  53. DiagnosisConclusionEnum.Benign,
  54. DiagnosisConclusionEnum.Malignant,
  55. DiagnosisConclusionEnum.BenignAndMalignant
  56. ].contains(measureData.diagnosisConclusion);
  57. double groupHeight = 0;
  58. bool isExpanded = true;
  59. bool ifShowModeButton = false;
  60. bool ifShowVidModeButton = true;
  61. get isShell => measure3DViewController.isShell;
  62. get exist3DData => measure3DViewController.exist3DData;
  63. @override
  64. void initState() {
  65. super.initState();
  66. groupHeight = maxGroupHeight;
  67. measure3DViewController.updatePlayerMode.addListener(_onModeChanged);
  68. }
  69. @override
  70. void dispose() {
  71. measure3DViewController.updatePlayerMode.removeListener(_onModeChanged);
  72. super.dispose();
  73. }
  74. /// 模式改变触发更新
  75. void _onModeChanged(Object s, MeasureMode mode) {
  76. switch (mode) {
  77. case MeasureMode.vidMode:
  78. setState(() {
  79. ifShowModeButton = false;
  80. ifShowVidModeButton = true;
  81. groupHeight = maxGroupHeight;
  82. isExpanded = true;
  83. });
  84. break;
  85. case MeasureMode.carotid2DMode:
  86. setState(() {
  87. ifShowModeButton = true;
  88. ifShowVidModeButton = false;
  89. groupHeight = maxGroupHeight;
  90. isExpanded = true;
  91. });
  92. break;
  93. case MeasureMode.carotid3DMode:
  94. setState(() {
  95. ifShowModeButton = false;
  96. ifShowVidModeButton = false;
  97. });
  98. break;
  99. }
  100. }
  101. List<FWidget> buildTitleButtonList() {
  102. return [
  103. _buildTitleButton(
  104. FIcons.fis_quash,
  105. i18nBook.common.revoke.t,
  106. () => application.undoRecord(),
  107. ),
  108. _buildTitleButton(
  109. FIcons.fis_purge,
  110. i18nBook.measure.clear.t,
  111. () => application.clearRecords(),
  112. ),
  113. OperateTypeChangeButton(businessParent: widget.businessParent),
  114. _buildTitleButton(
  115. measureHandler.fullScreenState
  116. ? FIcons.fis_full_screen_reduction
  117. : FIcons.fis_full_screen,
  118. measureHandler.fullScreenState
  119. ? i18nBook.common.cancel.t + i18nBook.measure.fullScreen.t
  120. : i18nBook.measure.fullScreen.t,
  121. () {
  122. measureHandler.fullScreenState = !measureHandler.fullScreenState;
  123. setState(() {});
  124. },
  125. ),
  126. _buildTitleButton(
  127. FIcons.fis_save,
  128. i18nBook.common.save.t,
  129. widget.capturePng,
  130. ),
  131. _buildTitleButton(
  132. FIcons.fis_share,
  133. i18nBook.remedical.share.t,
  134. () async {
  135. var itemCurrentImage = await measureData.shareImage.call(
  136. measureData.itemCurrentImage,
  137. );
  138. Get.dialog(_buildShareQRCode(itemCurrentImage));
  139. },
  140. ),
  141. //第三方图像需要校准线
  142. if (application.isThirdPart) ...[
  143. _buildTitleButton(
  144. FIcons.device,
  145. i18nBook.remedical.calibrationLine.t,
  146. () {
  147. Get.find<IStandardLineCalibrationController>().enterEditMode();
  148. },
  149. ),
  150. ],
  151. //AI结果隐藏按钮
  152. if (canShowAI) ...[
  153. _buildTitleButton(
  154. !aiPatintController.state.ifShowAi
  155. ? FIcons.fis_eye_display
  156. : FIcons.fis_eye_hidden,
  157. !aiPatintController.state.ifShowAi
  158. ? i18nBook.measure.showAi.t
  159. : i18nBook.measure.hideAi.t,
  160. () {
  161. aiPatintController.state.ifShowAi =
  162. !aiPatintController.state.ifShowAi;
  163. setState(() {});
  164. },
  165. ),
  166. ],
  167. //进入颈动脉3D模式
  168. if (isShell && exist3DData && ifShowVidModeButton) ...[
  169. _buildTitleButton(
  170. FIcons.three_dimensional,
  171. i18nBook.measure.carotid3DMode.t,
  172. () {
  173. measure3DViewController.changeModeTo3DMode();
  174. },
  175. ),
  176. ],
  177. //从颈动脉2D返回到【测量】或【3D】
  178. if (isShell && exist3DData && ifShowModeButton) ...[
  179. _buildTitleButton(
  180. FIcons.three_dimensional,
  181. i18nBook.measure.carotid3DMode.t,
  182. () {
  183. measure3DViewController.backTo3DMode();
  184. },
  185. ),
  186. _buildTitleButton(
  187. Icons.play_circle_outline_rounded,
  188. i18nBook.measure.vidMode.t,
  189. () {
  190. measure3DViewController.backToVidMode();
  191. playerController.resetCurrentFrame();
  192. },
  193. ),
  194. ]
  195. ];
  196. }
  197. @override
  198. FWidget build(BuildContext context) {
  199. return FRow(
  200. children: [
  201. FExpanded(child: FContainer()),
  202. FColumn(
  203. verticalDirection: VerticalDirection.up,
  204. mainAxisAlignment: MainAxisAlignment.end,
  205. children: [
  206. QuickFWidget(
  207. Transform.translate(
  208. offset: const Offset(0, -buttonGroupWidth / 2),
  209. child: FAnimatedContainer(
  210. duration: const Duration(milliseconds: 300),
  211. padding: const EdgeInsets.only(
  212. top: groupContainerPadding,
  213. bottom: groupContainerPadding / 2,
  214. ),
  215. decoration: BoxDecoration(
  216. borderRadius: const BorderRadius.only(
  217. bottomLeft: Radius.circular(buttonGroupWidth / 2),
  218. bottomRight: Radius.circular(buttonGroupWidth / 2),
  219. ),
  220. color: Colors.grey.withOpacity(0.6),
  221. ),
  222. clipBehavior: Clip.antiAlias,
  223. width: buttonGroupWidth,
  224. // height: groupHeight,
  225. constraints: BoxConstraints(
  226. maxHeight: groupHeight,
  227. ),
  228. child: FListView(
  229. shrinkWrap: true,
  230. children: buildTitleButtonList(),
  231. ))),
  232. ),
  233. _buildDropDownButton(),
  234. const FSizedBox(height: 10),
  235. ],
  236. )
  237. ],
  238. );
  239. }
  240. FWidget _buildTitleButton(IconData icon, String title, Function onClick) {
  241. return FCustomLeftTooltip(
  242. textStyle: const TextStyle(fontSize: 16, color: Colors.white),
  243. height: 36,
  244. message: title,
  245. verticalOffset: 0,
  246. margin: const EdgeInsets.all(0),
  247. decoration: BoxDecoration(
  248. color: Colors.black.withOpacity(1.0),
  249. borderRadius: BorderRadius.circular(4),
  250. ),
  251. child: FContainer(
  252. height: buttonHeight,
  253. margin: const EdgeInsets.only(
  254. top: buttonMargin,
  255. ),
  256. child: FInkWell(
  257. onTap: () => onClick.call(),
  258. child: Icon(
  259. icon,
  260. color: Colors.white,
  261. ),
  262. ),
  263. ),
  264. );
  265. }
  266. FWidget _buildDropDownButton() {
  267. return FInkWell(
  268. onHover: (isHover) {
  269. if (isHover && !isExpanded) {
  270. setState(() {
  271. groupHeight = maxGroupHeight;
  272. isExpanded = true;
  273. });
  274. }
  275. },
  276. onTap: () {
  277. if (isExpanded) {
  278. setState(() {
  279. groupHeight = groupContainerPadding;
  280. isExpanded = false;
  281. });
  282. } else {
  283. setState(() {
  284. groupHeight = maxGroupHeight;
  285. isExpanded = true;
  286. });
  287. }
  288. },
  289. child: FContainer(
  290. decoration: const BoxDecoration(
  291. borderRadius:
  292. BorderRadius.all(Radius.circular(buttonGroupWidth / 2)),
  293. color: Colors.white,
  294. ),
  295. padding: const EdgeInsets.only(top: 15),
  296. height: buttonGroupWidth,
  297. width: buttonGroupWidth,
  298. child: FColumn(
  299. children: [
  300. if (isExpanded) ...[
  301. FText(i18nBook.measure.collapseGroup.t),
  302. const FIcon(Icons.arrow_drop_up)
  303. ] else ...[
  304. FText(i18nBook.measure.expandGroup.t),
  305. const FIcon(Icons.arrow_drop_down)
  306. ]
  307. ],
  308. ),
  309. ));
  310. }
  311. FWidget _buildShareQRCode(String itemCurrentImage) {
  312. return FSimpleDialog(
  313. title: FText(
  314. i18nBook.remedical.share.t,
  315. style: const TextStyle(
  316. color: Colors.white,
  317. fontSize: 18,
  318. ),
  319. ),
  320. isDefault: false,
  321. children: [
  322. FContainer(
  323. width: 360,
  324. child: FContainer(
  325. padding: const EdgeInsets.only(top: 20, bottom: 20),
  326. child: QRCodeWithLogo(
  327. itemCurrentImage,
  328. codeStatement: i18nBook.remedical.scanQrCodeToShareImage.t,
  329. operationStatement: i18nBook.remedical.copyLink.t,
  330. operationSuccessCallback: () =>
  331. {PromptBox.toast(i18nBook.remedical.copySuccess.t)},
  332. ),
  333. ),
  334. ),
  335. ],
  336. );
  337. }
  338. }
  339. /// TODO:[Gavin] 定制组件,可与主项目的 FCustomRightTooltip 合并整合进 ui lib
  340. class FCustomLeftTooltip extends FStatefulWidget {
  341. const FCustomLeftTooltip({
  342. super.key,
  343. this.message,
  344. this.height,
  345. this.padding,
  346. this.margin,
  347. this.verticalOffset,
  348. this.decoration,
  349. this.textStyle,
  350. this.child,
  351. }) : super();
  352. final String? message;
  353. final double? height;
  354. final EdgeInsetsGeometry? padding;
  355. final EdgeInsetsGeometry? margin;
  356. final double? verticalOffset;
  357. final Decoration? decoration;
  358. final TextStyle? textStyle;
  359. final FWidget? child;
  360. @override
  361. FState<FCustomLeftTooltip> createState() => FCustomLeftTooltipState();
  362. }
  363. class FCustomLeftTooltipState extends FState<FCustomLeftTooltip> {
  364. OverlayEntry? _entry;
  365. void _showTooltip() {
  366. _createNewEntry();
  367. }
  368. void _hideTooltip() {
  369. _entry?.remove();
  370. _entry = null;
  371. }
  372. void _createNewEntry() {
  373. final OverlayState overlayState = Overlay.of(
  374. context,
  375. debugRequiredFor: widget,
  376. );
  377. final RenderBox box = context.findRenderObject()! as RenderBox;
  378. final RenderBox boxConatiner =
  379. overlayState.context.findRenderObject() as RenderBox;
  380. final Offset target = box.localToGlobal(
  381. box.size.center(Offset.zero),
  382. ancestor: overlayState.context.findRenderObject(),
  383. );
  384. final Offset leftSide = Offset(
  385. boxConatiner.size.width - (target.dx - box.size.width / 2),
  386. target.dy - (widget.height ?? 26) / 2,
  387. );
  388. final Widget overlay = Directionality(
  389. textDirection: Directionality.of(context),
  390. child: Positioned(
  391. right: leftSide.dx,
  392. top: leftSide.dy,
  393. child: Container(
  394. decoration: widget.decoration ??
  395. BoxDecoration(
  396. color: Colors.black.withOpacity(0.7),
  397. borderRadius: BorderRadius.circular(4),
  398. ),
  399. height: widget.height ?? 26,
  400. padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: 8),
  401. margin: widget.margin ?? const EdgeInsets.only(left: 10),
  402. child: Center(
  403. child: Text(
  404. widget.message ?? '',
  405. style: widget.textStyle,
  406. ),
  407. ),
  408. ),
  409. ),
  410. );
  411. _entry = OverlayEntry(builder: (BuildContext context) => overlay);
  412. overlayState.insert(_entry!);
  413. }
  414. @override
  415. FWidget build(BuildContext context) {
  416. return FMouseRegion(
  417. onEnter: (event) {
  418. _showTooltip();
  419. },
  420. onExit: (event) {
  421. _hideTooltip();
  422. },
  423. child: FContainer(
  424. child: widget.child,
  425. ),
  426. );
  427. }
  428. @override
  429. void dispose() {
  430. super.dispose();
  431. if (_entry != null) {
  432. _entry?.remove();
  433. _entry = null;
  434. }
  435. }
  436. }