measure_images_bar.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. import 'dart:convert';
  2. import 'package:fis_common/logger/logger.dart';
  3. import 'package:fis_jsonrpc/rpc.dart';
  4. import 'package:fis_measure/interfaces/process/workspace/exam_info.dart';
  5. import 'package:fis_measure/process/language/measure_language.dart';
  6. import 'package:fis_measure/process/workspace/measure_controller.dart';
  7. import 'package:fis_measure/process/workspace/measure_data_controller.dart';
  8. import 'package:fis_measure/process/workspace/measure_handler.dart';
  9. import 'package:fis_measure/view/measure/measure_images_bar.dart';
  10. export 'package:fis_lib_business_components/index.dart';
  11. import 'package:fis_ui/index.dart';
  12. import 'package:flutter/material.dart';
  13. import 'package:get/get.dart';
  14. class MeasureImagesBar extends StatefulWidget implements FWidget {
  15. const MeasureImagesBar({
  16. Key? key,
  17. this.hasQualityControlPermission = false,
  18. }) : super(key: key);
  19. final bool hasQualityControlPermission;
  20. @override
  21. State<MeasureImagesBar> createState() => _MeasureImagesBarState();
  22. }
  23. class _MeasureImagesBarState extends State<MeasureImagesBar>
  24. with WidgetsBindingObserver {
  25. /// 图片滑动控制器
  26. ScrollController scrollController = ScrollController();
  27. /// 数据
  28. final measureData = Get.find<MeasureDataController>();
  29. /// 全部图片list
  30. final measureController = Get.find<MeasureController>();
  31. /// 当前可滑动的范围宽度
  32. late double width = 0;
  33. ///Listview item的宽度
  34. late double itemWidth = 160;
  35. /// 是否滑到最后
  36. bool _isEnd = false;
  37. /// 计算下标
  38. int _currentPage = 0;
  39. /// 左边能否滑动
  40. bool get isLeftCanScroll => _currentPage <= 0;
  41. /// 右边能否滑动
  42. bool get isRightCanScroll => _isEnd;
  43. /// 滑动区域的key值
  44. final GlobalKey globalKey = GlobalKey();
  45. double get devicePixelRatio =>
  46. kIsMobile ? 1 : MediaQuery.of(context).devicePixelRatio;
  47. ///当前分辨率下ListView里面的item个数
  48. int _itemsPerPage = 0;
  49. /// 图片左划事件
  50. void onLeftMoveImage() async {
  51. _currentPage--;
  52. await scrollController.animateTo(
  53. (160 * _currentPage * _itemsPerPage).toDouble(),
  54. duration: const Duration(milliseconds: 300),
  55. curve: Curves.linear,
  56. );
  57. }
  58. /// 图片右划事件
  59. void onRightMoveImage() async {
  60. if (!isRightCanScroll) {
  61. _currentPage++;
  62. await scrollController.animateTo(
  63. (160 * _currentPage * _itemsPerPage).toDouble(),
  64. duration: const Duration(milliseconds: 300),
  65. curve: Curves.linear,
  66. );
  67. }
  68. }
  69. @override
  70. Widget build(BuildContext context) {
  71. return LayoutBuilder(
  72. builder: (BuildContext context, BoxConstraints constraints) {
  73. return FContainer(
  74. height: 120 / devicePixelRatio,
  75. margin: EdgeInsets.symmetric(
  76. vertical: devicePixelRatio > 1.25
  77. ? 5
  78. : devicePixelRatio > 1
  79. ? 10
  80. : 15,
  81. ),
  82. child: FRow(
  83. children: [
  84. _buildImageListIcon(
  85. FIcons.fis_left_arrow,
  86. isLeftCanScroll,
  87. onLeftMoveImage,
  88. ),
  89. FExpanded(
  90. child: ScrollableImageList(
  91. scrollController: scrollController,
  92. globalKey: globalKey,
  93. remedicalList: measureData.remedicalList,
  94. hasQualityControlPermission:
  95. widget.hasQualityControlPermission,
  96. ),
  97. ),
  98. _buildImageListIcon(
  99. FIcons.fis_right_arrow,
  100. isRightCanScroll,
  101. onRightMoveImage,
  102. ),
  103. ],
  104. ),
  105. );
  106. },
  107. );
  108. }
  109. @override
  110. void didChangeMetrics() {
  111. width = globalKey.currentContext!.size!.width;
  112. print('width:$width,itemWidth:$itemWidth');
  113. _isEnd = width >= measureData.remedicalList.length * itemWidth;
  114. _itemsPerPage = (width / itemWidth).toInt();
  115. var currentIndex = measureController.examInfo.selectedImageIndex;
  116. if (currentIndex * itemWidth > scrollController.position.maxScrollExtent) {
  117. currentIndex = scrollController.position.maxScrollExtent ~/ itemWidth;
  118. }
  119. scrollController.animateTo(
  120. (itemWidth * currentIndex).toDouble(),
  121. duration: const Duration(milliseconds: 300),
  122. curve: Curves.linear,
  123. );
  124. setState(
  125. () {},
  126. );
  127. }
  128. @override
  129. void initState() {
  130. WidgetsBinding.instance.addObserver(this);
  131. scrollController.addListener(
  132. () async {
  133. // 如果滑动到最右边
  134. if (scrollController.position.pixels ==
  135. scrollController.position.maxScrollExtent) {
  136. setState(() {
  137. _isEnd = true;
  138. });
  139. } else {
  140. setState(
  141. () {
  142. _isEnd = false;
  143. },
  144. );
  145. }
  146. },
  147. );
  148. super.initState();
  149. }
  150. @override
  151. void didChangeDependencies() {
  152. super.didChangeDependencies();
  153. WidgetsBinding.instance.addPostFrameCallback(
  154. (mag) {
  155. width = globalKey.currentContext!.size!.width;
  156. setState(
  157. () {
  158. _isEnd = width >= measureData.remedicalList.length * itemWidth;
  159. _itemsPerPage = (width / itemWidth).toInt();
  160. var currentIndex = measureController.examInfo.selectedImageIndex;
  161. if (currentIndex * itemWidth >
  162. scrollController.position.maxScrollExtent) {
  163. currentIndex =
  164. scrollController.position.maxScrollExtent ~/ itemWidth;
  165. }
  166. scrollController.animateTo(
  167. (itemWidth * currentIndex).toDouble(),
  168. duration: const Duration(milliseconds: 300),
  169. curve: Curves.linear,
  170. );
  171. },
  172. );
  173. },
  174. );
  175. }
  176. @override
  177. void dispose() {
  178. WidgetsBinding.instance.removeObserver(this);
  179. super.dispose();
  180. }
  181. FWidget _buildImageListIcon(
  182. IconData iconsData, bool isCanClick, Function onIconTap) {
  183. return FInkWell(
  184. onTap: () => onIconTap.call(),
  185. child: FContainer(
  186. width: 100 / devicePixelRatio,
  187. child: FIcon(
  188. iconsData,
  189. color: isCanClick ? Colors.grey : Colors.white,
  190. size: 30 / devicePixelRatio,
  191. ),
  192. ),
  193. );
  194. }
  195. }
  196. /// 可滑动的图片组,显示当前测量组的所有图片
  197. class ScrollableImageList extends StatefulWidget implements FWidget {
  198. const ScrollableImageList({
  199. required this.scrollController,
  200. required this.globalKey,
  201. required this.remedicalList,
  202. this.hasQualityControlPermission = false,
  203. Key? key,
  204. this.description = '',
  205. }) : super(key: key);
  206. /// 图片滑动控制器
  207. final ScrollController scrollController;
  208. final GlobalKey globalKey;
  209. final List<RemedicalInfoDTO> remedicalList;
  210. final String? description;
  211. final bool hasQualityControlPermission;
  212. @override
  213. State<ScrollableImageList> createState() => _ScrollableImageListState();
  214. }
  215. class _ScrollableImageListState extends State<ScrollableImageList> {
  216. /// 当前测量的图片
  217. // final measureCurrentImage = Get.put(MeasureGetCurrentImage());
  218. double get devicePixelRatio =>
  219. kIsMobile ? 1 : MediaQuery.of(context).devicePixelRatio;
  220. /// 测量AI数据
  221. final measureData = Get.find<MeasureDataController>();
  222. MeasureController measureController = Get.find<MeasureController>();
  223. late final measureHandler = Get.put(MeasureHandler());
  224. /// 测量语言包
  225. final measureLanguage = MeasureLanguage();
  226. /// 图像数据列表
  227. List<RemedicalInfoDTO> remedicalList = [];
  228. /// 获取图片地址
  229. void onChangeImage(
  230. String imageUrl,
  231. String remedicalCode,
  232. ) async {
  233. final selectedIndex = measureController.examInfo.images.indexWhere(
  234. (element) => element.remedicalCode == remedicalCode,
  235. );
  236. if (selectedIndex < 0) {
  237. return;
  238. }
  239. if (measureData.itemCurrentImage == imageUrl &&
  240. measureController.examInfo.selectedImageIndex == selectedIndex) {
  241. return;
  242. }
  243. measureData.itemCurrentImage = imageUrl;
  244. // remedicalAISelectedInfoCode是因为选择ai编辑过的图片code和remedicalCode等级一样
  245. measureHandler.newImageLoading = true;
  246. measureData.measureInfoData = MeasureInfoData(
  247. patientCode: measureData.measureInfoData.patientCode,
  248. recordCode: measureData.measureInfoData.recordCode,
  249. remedicalCode: remedicalCode,
  250. remedicalAISelectedInfoCode: remedicalCode,
  251. );
  252. measureHandler.currSelectedImage = CurrImageInfo(
  253. imageUrl,
  254. remedicalCode,
  255. );
  256. measureController.examInfo.selectedImageIndex = selectedIndex;
  257. setState(() {});
  258. }
  259. /// 获取图像Code来更新图像
  260. void onChangeImageByRemedicalCode(_, String e) {
  261. final url = findImageUrlByRemedicalCode(e);
  262. onChangeImage(url, e);
  263. }
  264. /// 更新图像集合
  265. void onChangeImageList(_, e) {
  266. measureController = Get.find<MeasureController>();
  267. setState(() {
  268. remedicalList = e;
  269. });
  270. }
  271. String findImageUrlByRemedicalCode(String remedicalCode) {
  272. final item =
  273. remedicalList.firstWhere((e) => e.remedicalCode == remedicalCode);
  274. final imgInfo = item.terminalImages!;
  275. return measureData.chooseImageUrl(imgInfo);
  276. }
  277. @override
  278. void initState() {
  279. remedicalList = widget.remedicalList;
  280. measureHandler.changeImageByRemedicalCode
  281. .addListener(onChangeImageByRemedicalCode);
  282. measureHandler.changeImageList.addListener(onChangeImageList);
  283. super.initState();
  284. }
  285. @override
  286. void dispose() {
  287. measureHandler.changeImageByRemedicalCode
  288. .removeListener(onChangeImageByRemedicalCode);
  289. measureHandler.changeImageList.removeListener(onChangeImageList);
  290. super.dispose();
  291. }
  292. @override
  293. FWidget build(BuildContext context) {
  294. return FContainer(
  295. key: ValueKey(measureData.itemCurrentImage),
  296. alignment: Alignment.topCenter,
  297. child: _buildImageList(remedicalList),
  298. );
  299. }
  300. ///翻译图片描述
  301. String _translateDescription(RemedicalInfoDTO remedicalInfo) {
  302. String description = '';
  303. try {
  304. if (remedicalInfo.application != null) {
  305. //判断是否是老数据,老数据里面没有applicationCategory的字段
  306. if (remedicalInfo.applicationCategory != null) {
  307. //如果不是老数据
  308. description = _getDescription(
  309. remedicalInfo.application ?? '',
  310. remedicalInfo.applicationCategory ?? '',
  311. );
  312. } else {
  313. //如果是老数据
  314. description = "Old data";
  315. }
  316. }
  317. } on Exception catch (e) {
  318. logger.e("Picture translation exception" + e.toString());
  319. } catch (e) {
  320. logger.e("Picture translation exception" + e.toString());
  321. }
  322. return description;
  323. }
  324. /// 翻译图片位置的描述
  325. String _translateLocationDescription(RemedicalInfoDTO remedicalInfo) {
  326. String description = '';
  327. try {
  328. description = _getLocationDescription(
  329. remedicalInfo.imageLocation?.quadrant,
  330. remedicalInfo.imageLocation?.position);
  331. } on Exception catch (e) {
  332. logger.e("Picture translation exception" + e.toString());
  333. } catch (e) {
  334. logger.e("Picture translation exception" + e.toString());
  335. }
  336. return description;
  337. }
  338. /// 获取翻译值
  339. String _getLanguageValue(String type, String code) {
  340. return measureLanguage.t(type, code);
  341. }
  342. String _getDescription(String application, String applicationCategory) {
  343. String description = '';
  344. if ([application, applicationCategory].contains('FromSonopost')) {
  345. description = _getLanguageValue('application', 'FromSonopost');
  346. } else {
  347. if (application.isNotEmpty || applicationCategory.isNotEmpty) {
  348. description = _getLanguageValue('application', applicationCategory) +
  349. "-" +
  350. _getLanguageValue('application', application);
  351. } else {
  352. description = '';
  353. }
  354. }
  355. return description;
  356. }
  357. String _getLocationDescription(String? quadrant, String? position) {
  358. String description = '';
  359. if (quadrant != '' || quadrant != '') {
  360. description = _getLanguageValue('location', quadrant ?? '') +
  361. "-" +
  362. _getLanguageValue('location', position ?? '');
  363. } else {
  364. description = '';
  365. }
  366. return description;
  367. }
  368. FWidget _buildImageList(List<RemedicalInfoDTO> remedicalItemList) {
  369. var currentIndex = measureController.examInfo.selectedImageIndex;
  370. return FContainer(
  371. key: widget.globalKey,
  372. child: FListView.builder(
  373. scrollDirection: Axis.horizontal,
  374. shrinkWrap: true,
  375. controller: widget.scrollController,
  376. itemCount: remedicalItemList.toList().length,
  377. itemBuilder: (BuildContext context, int index) {
  378. final item = remedicalItemList[index];
  379. FWidget image = FContentImage(
  380. remedicalInfo: item,
  381. isMeasure: true,
  382. onTap: () {
  383. final code = item.remedicalCode!;
  384. final url = findImageUrlByRemedicalCode(code);
  385. onChangeImage(url, code);
  386. },
  387. serialNo: index + 1,
  388. description: _translateDescription(item),
  389. locationDescription: _translateLocationDescription(item),
  390. isQualityControlled:
  391. widget.hasQualityControlPermission && item.isQualityControlled,
  392. );
  393. bool isCurrentSelect = index == currentIndex;
  394. return FContainer(
  395. key: ValueKey(measureData.itemCurrentImage),
  396. width: 160 / devicePixelRatio,
  397. alignment: Alignment.center,
  398. decoration: BoxDecoration(
  399. border: Border.all(
  400. width: 3,
  401. color: isCurrentSelect ? Colors.blue : Colors.grey,
  402. ),
  403. ),
  404. child: image,
  405. );
  406. },
  407. ),
  408. );
  409. }
  410. }