picture_components.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  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_ui/index.dart';
  5. import 'package:flutter/material.dart';
  6. class TerminalImage {
  7. String? previewUrl;
  8. String? imageUrl;
  9. String? coverImageUrl;
  10. TerminalImage({
  11. this.previewUrl,
  12. this.imageUrl,
  13. this.coverImageUrl,
  14. });
  15. factory TerminalImage.fromJson(Map<String, dynamic> map) {
  16. return TerminalImage(
  17. previewUrl: map['PreviewUrl'],
  18. imageUrl: map['ImageUrl'],
  19. coverImageUrl: map['CoverImageUrl'],
  20. );
  21. }
  22. Map<String, dynamic> toJson() {
  23. final map = Map<String, dynamic>();
  24. if (previewUrl != null) map['PreviewUrl'] = previewUrl;
  25. if (imageUrl != null) map['ImageUrl'] = imageUrl;
  26. if (coverImageUrl != null) map['CoverImageUrl'] = coverImageUrl;
  27. return map;
  28. }
  29. }
  30. /// 所有图片的入口
  31. class FContentImage extends StatelessWidget implements FWidget {
  32. /// 图片参数
  33. final RemedicalInfoDTO? remedicalInfo;
  34. /// 是否是测量页面
  35. final bool? isMeasure;
  36. /// 改变图片地址
  37. final VoidCallback? onDoubleTap;
  38. /// 图片单机事件
  39. final VoidCallback? onTap;
  40. /// AI 良恶性
  41. late final DiagnosisConclusionEnum diagnosisConclusion =
  42. remedicalInfo?.diagnosisConclusion ?? DiagnosisConclusionEnum.NotRequired;
  43. /// 图片类型
  44. late RemedicalFileDataTypeEnum? fileDataType;
  45. /// 图片地址
  46. late TerminalImage? terminalImage;
  47. /// AI测量的器官
  48. late final List<DiagnosisOrganEnum>? diagnosisOrgans =
  49. remedicalInfo?.diagnosisOrgans ?? [DiagnosisOrganEnum.Null];
  50. bool get isCarotidResult => remedicalInfo?.carotidResult != null;
  51. /// 判断是否有AI器官
  52. bool get hasDiagnosisOrgans => diagnosisOrgans?.length != 0;
  53. /// 判断是否展示ai字
  54. bool get isShowDiagnosisOrgans => [
  55. DiagnosisConclusionEnum.NoObviousLesion,
  56. DiagnosisConclusionEnum.Benign,
  57. DiagnosisConclusionEnum.Malignant,
  58. DiagnosisConclusionEnum.BenignAndMalignant
  59. ].contains(diagnosisConclusion);
  60. /// 是否是选择状态
  61. final bool isPureImage;
  62. ///是否显示左上角序号
  63. final bool? ifShowIndex;
  64. ///图像宽度
  65. final double contentWidth;
  66. ///图像高度
  67. final double contentHeight;
  68. ///序号
  69. final int serialNo;
  70. ///图片文字描述
  71. final String? description;
  72. /// 图像定位描述描述
  73. final String? locationDescription;
  74. FContentImage({
  75. Key? key,
  76. this.terminalImage,
  77. this.fileDataType,
  78. this.remedicalInfo,
  79. this.isMeasure = false,
  80. this.onDoubleTap,
  81. this.onTap,
  82. this.isPureImage = false,
  83. this.ifShowIndex = true,
  84. this.serialNo = 0,
  85. this.description = '',
  86. this.locationDescription = '',
  87. this.contentHeight = 200,
  88. this.contentWidth = 300,
  89. }) : super(key: key);
  90. @override
  91. FWidget build(BuildContext context) {
  92. if (terminalImage == null) {
  93. terminalImage = new TerminalImage(
  94. previewUrl: remedicalInfo?.terminalImages?.previewUrl ?? '',
  95. imageUrl: remedicalInfo?.terminalImages?.imageUrl ?? '',
  96. coverImageUrl: remedicalInfo?.terminalImages?.coverImageUrl ?? '');
  97. }
  98. if (fileDataType == null) {
  99. fileDataType = remedicalInfo?.fileDataType;
  100. }
  101. if (isPureImage) {
  102. return FContainer(
  103. color: Colors.black,
  104. child: FImage.network(
  105. terminalImage!.coverImageUrl!,
  106. width: contentWidth,
  107. height: contentHeight,
  108. errorBuilder: ((context, error, stackTrace) {
  109. return Container(
  110. child: Text(i18nBook.common.error.t),
  111. );
  112. }),
  113. ),
  114. );
  115. } else
  116. return _buildFContentImage(
  117. fileDataType!,
  118. terminalImage!.previewUrl!,
  119. );
  120. }
  121. FWidget _buildIndex() {
  122. return FPositioned(
  123. left: 5,
  124. child: FText(
  125. (serialNo).toString(),
  126. style: TextStyle(
  127. color: Colors.white,
  128. ),
  129. ),
  130. );
  131. }
  132. FWidget _buildFContentImage(
  133. RemedicalFileDataTypeEnum fileDataType,
  134. String previewUrl,
  135. ) {
  136. switch (fileDataType) {
  137. case RemedicalFileDataTypeEnum.Image:
  138. case RemedicalFileDataTypeEnum.ThirdVidSingle:
  139. case RemedicalFileDataTypeEnum.VinnoVidSingle:
  140. return _buildImageCard(
  141. previewUrl,
  142. );
  143. case RemedicalFileDataTypeEnum.ThirdVidMovie:
  144. case RemedicalFileDataTypeEnum.VinnoVidMovie:
  145. return _buildVidMovieCard(
  146. previewUrl,
  147. );
  148. default:
  149. return FContainer(
  150. child: FText(i18nBook.common.error.t),
  151. );
  152. }
  153. }
  154. ///判断是否是肝脏、胆囊、肾脏、脾脏、腹部器官
  155. bool _ifAbdominalOrgans(DiagnosisOrganEnum diagnosisOrgan) {
  156. final abdominalOrgansList = [
  157. DiagnosisOrganEnum.Liver,
  158. DiagnosisOrganEnum.Cholecyst,
  159. DiagnosisOrganEnum.Kidney,
  160. DiagnosisOrganEnum.Spleen,
  161. DiagnosisOrganEnum.Abdomen
  162. ];
  163. return abdominalOrgansList.contains(diagnosisOrgan);
  164. }
  165. FWidget _buildLeftAIOrCarotidLogo() {
  166. final List<FWidget> aIDiagnosticOrgansIconList = [];
  167. if (isCarotidResult) {
  168. var conclustionTemp = diagnosisConclusion;
  169. conclustionTemp =
  170. (conclustionTemp.index > DiagnosisConclusionEnum.Benign.index)
  171. ? DiagnosisConclusionEnum.NoObviousLesion
  172. : conclustionTemp;
  173. var colorCarotid = _buildAITextColor(
  174. conclustionTemp,
  175. );
  176. aIDiagnosticOrgansIconList.add(FIcon(
  177. FIcons.arteria_carotis,
  178. color: colorCarotid,
  179. ));
  180. if (diagnosisOrgans != null && diagnosisOrgans!.isNotEmpty) {
  181. var hasThyroid = diagnosisOrgans!.contains(DiagnosisOrganEnum.Thyroid);
  182. if (hasThyroid) {
  183. aIDiagnosticOrgansIconList
  184. .add(_buildAIDiagnosticOrgans(DiagnosisOrganEnum.Thyroid));
  185. }
  186. }
  187. return FPositioned(
  188. bottom: 5,
  189. left: 5,
  190. child: FRow(
  191. children: aIDiagnosticOrgansIconList,
  192. ),
  193. );
  194. } else if (hasDiagnosisOrgans) {
  195. int count = 0;
  196. for (var i = 0; i < diagnosisOrgans!.length; i++) {
  197. if (_ifAbdominalOrgans(diagnosisOrgans![i])) {
  198. //当diagnosisOrgans返回值为肝脏、胆囊、肾脏、脾脏,都只显示一个“腹部”图标
  199. if (count <= 0) {
  200. aIDiagnosticOrgansIconList.add(
  201. _buildAIDiagnosticOrgans(DiagnosisOrganEnum.Abdomen),
  202. );
  203. }
  204. count++;
  205. } else {
  206. aIDiagnosticOrgansIconList.add(
  207. _buildAIDiagnosticOrgans(
  208. diagnosisOrgans?[i],
  209. ),
  210. );
  211. }
  212. }
  213. return FPositioned(
  214. bottom: 5,
  215. left: 5,
  216. child: FRow(
  217. children: aIDiagnosticOrgansIconList,
  218. ),
  219. );
  220. }
  221. return FPositioned(
  222. bottom: 5,
  223. left: 5,
  224. right: 5,
  225. child: _buildApplication(),
  226. );
  227. }
  228. ///构建描述字段
  229. FWidget _buildApplication() {
  230. return FContainer(
  231. child: FText(
  232. description ?? "",
  233. softWrap: true,
  234. style: TextStyle(
  235. color: Colors.white,
  236. fontSize: 10,
  237. ),
  238. ),
  239. );
  240. }
  241. /// 构建图像定位描述
  242. FWidget _buildImageLocationDescription() {
  243. return FPositioned(
  244. right: 5,
  245. child: FText(
  246. locationDescription ?? "",
  247. style: TextStyle(
  248. color: Colors.white,
  249. fontSize: 10,
  250. overflow: TextOverflow.ellipsis,
  251. ),
  252. ),
  253. );
  254. }
  255. FWidget _buildRigthAIText() {
  256. var cDNMdlFile = remedicalInfo?.carotidResult?.cDNMdlFile;
  257. var surfaceFile = remedicalInfo?.carotidResult?.surfaceFile;
  258. return FPositioned(
  259. bottom: 5,
  260. right: 5,
  261. child: FRow(
  262. children: [
  263. if (isCarotidResult &&
  264. cDNMdlFile.isNotNullOrEmpty &&
  265. surfaceFile.isNotNullOrEmpty) ...[
  266. FIcon(
  267. FIcons.three_dimensional,
  268. color: Colors.lightBlue,
  269. ),
  270. ],
  271. if (isShowDiagnosisOrgans) ...[
  272. const FSizedBox(
  273. width: 5,
  274. ),
  275. FMaterialTooltip(
  276. textStyle: TextStyle(
  277. fontSize: 16,
  278. color: Colors.white,
  279. ),
  280. message: _buildAIText(diagnosisConclusion),
  281. child: FText(
  282. 'AI',
  283. style: TextStyle(
  284. color: _buildAITextColor(
  285. diagnosisConclusion,
  286. ),
  287. fontSize: 20,
  288. ),
  289. ),
  290. ),
  291. ],
  292. ],
  293. ),
  294. );
  295. }
  296. FWidget _buildVidMovieCard(String previewUrl) {
  297. return FContainer(
  298. width: 190,
  299. height: 160,
  300. color: Colors.black,
  301. child: FStack(
  302. children: [
  303. FCenter(
  304. child: FImage.network(
  305. previewUrl,
  306. errorBuilder: ((context, error, stackTrace) {
  307. return Container(
  308. child: Text(i18nBook.common.error.t),
  309. );
  310. }),
  311. ),
  312. ),
  313. FInkWell(
  314. onDoubleTap: () {
  315. onDoubleTap?.call();
  316. },
  317. onTap: () {
  318. onTap?.call();
  319. },
  320. child: Center(
  321. child: Container(
  322. child: Icon(
  323. Icons.play_circle_outline_rounded,
  324. color: Colors.white,
  325. size: 50,
  326. ),
  327. ),
  328. ),
  329. ),
  330. _buildLeftAIOrCarotidLogo(),
  331. _buildRigthAIText(),
  332. _buildImageLocationDescription(),
  333. if (ifShowIndex!) _buildIndex(),
  334. ],
  335. ),
  336. );
  337. }
  338. FWidget _buildImageCard(
  339. String previewUrl,
  340. ) {
  341. return FInkWell(
  342. onDoubleTap: () {
  343. onDoubleTap?.call();
  344. },
  345. onTap: () {
  346. onTap?.call();
  347. },
  348. child: Container(
  349. width: 190,
  350. height: 160,
  351. color: Colors.black,
  352. child: Stack(
  353. children: [
  354. Center(
  355. child: Image.network(
  356. previewUrl,
  357. errorBuilder: ((context, error, stackTrace) {
  358. return Container(
  359. child: Text(i18nBook.common.error.t),
  360. );
  361. }),
  362. ),
  363. ),
  364. _buildLeftAIOrCarotidLogo(),
  365. _buildImageLocationDescription(),
  366. _buildRigthAIText(),
  367. if (ifShowIndex!) _buildIndex(),
  368. ],
  369. ),
  370. ),
  371. );
  372. }
  373. String _buildAIText(DiagnosisConclusionEnum diagnosisConclusion) {
  374. switch (diagnosisConclusion) {
  375. case DiagnosisConclusionEnum.NotRequired:
  376. return i18nBook.remedical.noNeedAIYet.t;
  377. case DiagnosisConclusionEnum.InProcess:
  378. return i18nBook.remedical.didNotHandle.t;
  379. case DiagnosisConclusionEnum.Unrecognized:
  380. return i18nBook.remedical.didNotIdentify.t;
  381. case DiagnosisConclusionEnum.NoObviousLesion:
  382. return i18nBook.remedical.noAbnormal.t;
  383. case DiagnosisConclusionEnum.Benign:
  384. return i18nBook.remedical.benign.t;
  385. case DiagnosisConclusionEnum.Malignant:
  386. return i18nBook.remedical.benignWithMalignant.t;
  387. case DiagnosisConclusionEnum.BenignAndMalignant:
  388. return i18nBook.remedical.benignWithMalignant.t;
  389. default:
  390. return i18nBook.remedical.didNotIdentify.t;
  391. }
  392. }
  393. dynamic _buildAITextColor(DiagnosisConclusionEnum diagnosisConclusion) {
  394. switch (diagnosisConclusion) {
  395. case DiagnosisConclusionEnum.NoObviousLesion:
  396. return Colors.lightBlue;
  397. case DiagnosisConclusionEnum.Benign:
  398. return Colors.greenAccent;
  399. case DiagnosisConclusionEnum.Malignant:
  400. return Colors.orangeAccent;
  401. case DiagnosisConclusionEnum.BenignAndMalignant:
  402. return Colors.orangeAccent;
  403. default:
  404. return Colors.transparent;
  405. }
  406. }
  407. FWidget _buildAIDiagnosticOrgans(DiagnosisOrganEnum? diagnosisOrganEnum) {
  408. var isCarotird = diagnosisOrganEnum == DiagnosisOrganEnum.CarotidArtery;
  409. var realConculsion = (isCarotird &&
  410. diagnosisConclusion.index > DiagnosisConclusionEnum.Benign.index)
  411. ? DiagnosisConclusionEnum.NoObviousLesion
  412. : diagnosisConclusion;
  413. switch (diagnosisOrganEnum) {
  414. case DiagnosisOrganEnum.Breast:
  415. return FIcon(
  416. FIcons.fis_breast,
  417. color: _buildAITextColor(
  418. realConculsion,
  419. ),
  420. );
  421. case DiagnosisOrganEnum.Abdomen:
  422. return FIcon(
  423. FIcons.fis_abdomen,
  424. color: _buildAITextColor(
  425. realConculsion,
  426. ),
  427. );
  428. case DiagnosisOrganEnum.Liver:
  429. return FIcon(
  430. FIcons.fis_liver,
  431. color: _buildAITextColor(realConculsion),
  432. );
  433. case DiagnosisOrganEnum.Cholecyst:
  434. return FIcon(
  435. FIcons.fis_gallbladder,
  436. color: _buildAITextColor(
  437. realConculsion,
  438. ),
  439. );
  440. case DiagnosisOrganEnum.Kidney:
  441. return FIcon(
  442. FIcons.fis_kidney,
  443. color: _buildAITextColor(
  444. realConculsion,
  445. ),
  446. );
  447. case DiagnosisOrganEnum.Spleen:
  448. return FIcon(
  449. FIcons.fis_spleen,
  450. color: _buildAITextColor(
  451. realConculsion,
  452. ),
  453. );
  454. case DiagnosisOrganEnum.CarotidArtery:
  455. return FIcon(
  456. FIcons.arteria_carotis,
  457. color: _buildAITextColor(
  458. realConculsion,
  459. ),
  460. );
  461. case DiagnosisOrganEnum.Thyroid:
  462. return FIcon(
  463. FIcons.fis_thyroid,
  464. color: _buildAITextColor(
  465. realConculsion,
  466. ),
  467. );
  468. case DiagnosisOrganEnum.Neck:
  469. return FIcon(
  470. //图标待定
  471. FIcons.fis_thyroid,
  472. color: _buildAITextColor(
  473. realConculsion,
  474. ),
  475. );
  476. case DiagnosisOrganEnum.Null:
  477. if (diagnosisOrgans?.length == 1) {
  478. //如果diagnosisOrgans只有一个值且该值为null值,就返回文字,否则返回空
  479. return _buildApplication();
  480. } else {
  481. return FText('');
  482. }
  483. default:
  484. return _buildApplication();
  485. }
  486. }
  487. }