menu_button_group.dart 12 KB

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