measure_page_test.dart 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. import 'dart:typed_data';
  2. import 'package:fis_jsonrpc/rpc.dart';
  3. import 'package:fis_measure/interfaces/enums/annotation.dart';
  4. import 'package:fis_measure/interfaces/process/items/types.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/exam_info.dart';
  9. import 'package:fis_measure/interfaces/process/workspace/measure_controller.dart';
  10. import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart';
  11. import 'package:fis_measure/process/workspace/measure_controller.dart';
  12. import 'package:fis_measure/process/workspace/measure_data_controller.dart';
  13. import 'package:fis_measure/view/measure/measure_main_view.dart';
  14. import 'package:flutter/material.dart';
  15. import 'package:get/get.dart';
  16. class MeasureDataTester {
  17. static Future<List<RemedicalItemList>> getRemedicalList(
  18. String patientCode,
  19. String recordCode,
  20. String token,
  21. ) async {
  22. return [];
  23. }
  24. static Future<RemedicalInfoDTO?> getImageInfo(
  25. String remedicalCode,
  26. String token,
  27. ) async {
  28. return null;
  29. }
  30. static Future<MeasureApplicationDTO?> getMeasureApplication(
  31. dynamic args) async {
  32. return null;
  33. }
  34. static Future<MeasureApplicationDTO?> saveUserDefinedMeasureApplicationAsync(
  35. dynamic args) async {
  36. return null;
  37. }
  38. static Future<bool?> saveImage(
  39. Uint8List imageBytes,
  40. String patientCode,
  41. String recordCode,
  42. String remedicalCode,
  43. String measuredData,
  44. ) async {
  45. return null;
  46. }
  47. static Future<void> saveMeasureSystemSettingAsync(
  48. MeasureSystemSettingDTO measureSystemSetting) async {
  49. return;
  50. }
  51. static Future<MeasureSystemSettingDTO?> getMeasureSystemSettingAsync() async {
  52. return null;
  53. }
  54. static Future<String> shareImage(String vid) async {
  55. return '';
  56. }
  57. static Future<CommentItemResultDTO?> getCommentsByApplicationAsync(
  58. String applicationName, String categoryName) async {
  59. return null;
  60. }
  61. static Future<bool?> saveUserDefinedCommentsAsync(String applicationName,
  62. String categoryName, List<CommentItemDTO> commentItems) async {
  63. return null;
  64. }
  65. }
  66. class MeasureTestPage extends StatefulWidget {
  67. const MeasureTestPage({Key? key}) : super(key: key);
  68. @override
  69. State<StatefulWidget> createState() => _MeasureTestPageState();
  70. }
  71. class _MeasureTestPageState extends State<MeasureTestPage> {
  72. static const C_LINEAR_TISSUE =
  73. // "http://cdn-bj.fis.plus/9F066341FA874E21B48CDE247C13D495.vid"; //B TVI TD
  74. // "http://cdn-bj.fis.plus/974BABA5113640639FD749E06DD7DA5B.vid"; //B CF CW
  75. // "http://cdn-bj.fis.plus/0B344F48BA574ECD82B7FEDB8848421A.vid";//单幅TM
  76. // "http://cdn-bj.fis.plus/F26C6E5D57A7472A97E9EB543DF0D16C.vid"; // 单幅Convex
  77. // "http://cdn-bj.fis.plus/6B6E069659D14E7299EB9F6EFCDE9C8C.vid"; //双幅单TissueConvex
  78. // "http://cdn-bj.fis.plus/062643B82365437DB95F3811580AF3ED.vid"; //四幅单模式
  79. // "http://cdn-bj.fis.plus/EA90D146049D416E8E466B7446E00001.vid"; //四幅Doppler
  80. "http://cdn-bj.fis.plus/3rd_linearTvTissue2.vid"; //魔盒
  81. // "http://cdn-bj.fis.plus/81FFF8E5E078473FA687FBE81C4869B1.vid"; // 魔盒TV
  82. // "http://cdn-bj.fis.plus/7B450708A2784B1490304C82787349BE.vid";// 胎儿Zoom
  83. static const C_CONVEX_TISSUE =
  84. "http://cdn-bj.fis.plus/FEB1AAE5D9C24839BEE31B16E8CB450A.vid";
  85. final _3dc = Get.put<Measure3DViewController>(Measure3DViewController());
  86. final datac = Get.put<MeasureDataController>(MeasureDataController(
  87. MeasureDataTester.getRemedicalList,
  88. MeasureDataTester.getImageInfo,
  89. MeasureDataTester.getMeasureApplication,
  90. MeasureDataTester.saveUserDefinedMeasureApplicationAsync,
  91. MeasureDataTester.saveImage,
  92. MeasureDataTester.saveMeasureSystemSettingAsync,
  93. MeasureDataTester.getMeasureSystemSettingAsync,
  94. MeasureDataTester.shareImage,
  95. MeasureDataTester.getCommentsByApplicationAsync,
  96. MeasureDataTester.saveUserDefinedCommentsAsync,
  97. ));
  98. final controller = Get.put<IMeasureController>(MeasureController(
  99. "12345",
  100. imagesFetchFunc: (code) async {
  101. return <ExamImageInfo>[
  102. ExamImageInfo(C_LINEAR_TISSUE, C_LINEAR_TISSUE),
  103. ExamImageInfo(C_CONVEX_TISSUE, C_CONVEX_TISSUE)
  104. ];
  105. },
  106. ));
  107. bool loaded = false;
  108. int opType = 0;
  109. bool useArrowAnnotation = false;
  110. @override
  111. void initState() {
  112. controller.load().then((value) {
  113. // 加载指定图像
  114. controller.examInfo.selectedImageIndex = 0;
  115. });
  116. controller.imageLoaded.addListener(onImageLoaded);
  117. super.initState();
  118. }
  119. @override
  120. void dispose() {
  121. controller.imageLoaded.removeListener(onImageLoaded);
  122. controller.dispose();
  123. Get.delete<IMeasureController>();
  124. super.dispose();
  125. }
  126. void onImageLoaded(Object sender, ExamImageInfo? e) {
  127. if (!mounted) return;
  128. if (e != null) {
  129. Future.delayed(const Duration(milliseconds: 100), () {
  130. controller.playerController.play();
  131. });
  132. setState(() {
  133. loaded = true;
  134. });
  135. }
  136. }
  137. @override
  138. Widget build(BuildContext context) {
  139. Widget body;
  140. if (!loaded) {
  141. const loadingWidget = Center(child: CircularProgressIndicator());
  142. body = Row(
  143. children: const [
  144. SizedBox(
  145. width: 300,
  146. child: loadingWidget,
  147. ),
  148. VerticalDivider(),
  149. Expanded(child: loadingWidget),
  150. ],
  151. );
  152. } else {
  153. body = Row(
  154. key: ValueKey(controller.examInfo.selectedImageIndex),
  155. children: [
  156. opType == 1
  157. ? const _MeasureLeftAnnotation()
  158. : const _MeasureLeftBoard(),
  159. const VerticalDivider(),
  160. const Expanded(
  161. child: MeasureRightBoard(),
  162. ),
  163. ],
  164. );
  165. }
  166. return Scaffold(
  167. backgroundColor: const Color.fromARGB(255, 53, 55, 51),
  168. appBar: AppBar(
  169. actions: [
  170. TextButton(
  171. onPressed: () {
  172. Get.find<IApplication>().clearRecords();
  173. },
  174. child: const Text(
  175. "清空",
  176. style: TextStyle(
  177. color: Colors.white,
  178. ),
  179. ),
  180. ),
  181. TextButton(
  182. onPressed: () {
  183. Get.find<IApplication>().undoRecord();
  184. },
  185. child: const Text(
  186. "撤销",
  187. style: TextStyle(
  188. color: Colors.white,
  189. ),
  190. ),
  191. ),
  192. TextButton(
  193. onPressed: () {
  194. final c = Get.find<IStandardLineCalibrationController>();
  195. if (c.isEditing) {
  196. c.cancelEdit();
  197. } else {
  198. c.enterEditMode();
  199. }
  200. },
  201. child: const Text(
  202. "校准线",
  203. style: TextStyle(
  204. color: Colors.white,
  205. ),
  206. ),
  207. ),
  208. TextButton.icon(
  209. onPressed: () {
  210. setState(() {
  211. if (useArrowAnnotation) {
  212. useArrowAnnotation = false;
  213. controller.workingApplication.switchAnnotation();
  214. } else {
  215. useArrowAnnotation = true;
  216. controller.workingApplication
  217. .switchAnnotation(AnnotationType.arrow);
  218. }
  219. });
  220. },
  221. icon: const Icon(Icons.arrow_right_alt_sharp),
  222. label: Text(
  223. "箭头",
  224. style: TextStyle(
  225. color: useArrowAnnotation ? Colors.amber : Colors.white,
  226. ),
  227. ),
  228. ),
  229. TextButton(
  230. onPressed: () {
  231. setState(() {
  232. opType = opType == 1 ? 0 : 1;
  233. });
  234. },
  235. child: Text(
  236. opType == 1 ? "注释" : "测量",
  237. style: const TextStyle(
  238. color: Colors.white,
  239. ),
  240. ),
  241. ),
  242. TextButton(
  243. onPressed: () {
  244. if (controller.examInfo.selectedImageIndex == 0) return;
  245. controller.examInfo.selectedImageIndex = 0;
  246. },
  247. child: Text(
  248. '线阵',
  249. style: TextStyle(
  250. color: controller.examInfo.selectedImageIndex == 0
  251. ? Colors.amber
  252. : Colors.white,
  253. ),
  254. ),
  255. ),
  256. TextButton(
  257. onPressed: () {
  258. if (controller.examInfo.selectedImageIndex == 1) return;
  259. controller.examInfo.selectedImageIndex = 1;
  260. },
  261. child: Text(
  262. '扇阵',
  263. style: TextStyle(
  264. color: controller.examInfo.selectedImageIndex == 1
  265. ? Colors.amber
  266. : Colors.white,
  267. ),
  268. ),
  269. ),
  270. ],
  271. leading: IconButton(
  272. onPressed: () {
  273. Navigator.of(context).pop();
  274. },
  275. icon: const Icon(Icons.arrow_back),
  276. ),
  277. ),
  278. body: body,
  279. floatingActionButton: _ModeTips(),
  280. floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
  281. );
  282. }
  283. }
  284. class MeasureRightBoard extends StatefulWidget {
  285. const MeasureRightBoard({Key? key}) : super(key: key);
  286. @override
  287. State<StatefulWidget> createState() => _MeasureRightBoardState();
  288. }
  289. class _MeasureRightBoardState extends State<MeasureRightBoard> {
  290. final playerController = Get.find<IPlayerController>();
  291. @override
  292. Widget build(BuildContext context) {
  293. return Container(
  294. padding: const EdgeInsets.all(8).copyWith(left: 0),
  295. child: const MeasureMainView(),
  296. );
  297. }
  298. }
  299. class _MeasureLeftBoard extends StatefulWidget {
  300. const _MeasureLeftBoard({Key? key}) : super(key: key);
  301. @override
  302. State<StatefulWidget> createState() => _MeasureLeftBoardState();
  303. }
  304. class _MeasureLeftBoardState extends State<_MeasureLeftBoard> {
  305. // ignore: non_constant_identifier_names
  306. static final C_SUPPORTED_ITEMS = <String>[
  307. MeasureTypes.Distance,
  308. MeasureTypes.Perimeter,
  309. MeasureTypes.Area,
  310. MeasureTypes.Depth,
  311. MeasureTypes.Volume,
  312. ];
  313. final scrollController = ScrollController();
  314. final application = Get.find<IApplication>();
  315. int activeIndex = 0;
  316. @override
  317. void initState() {
  318. application.switchItemByName(C_SUPPORTED_ITEMS[0]);
  319. application.canMeasureChanged.addListener(_onCanMeasureChanged);
  320. super.initState();
  321. }
  322. @override
  323. dispose() {
  324. application.canMeasureChanged.removeListener(_onCanMeasureChanged);
  325. super.dispose();
  326. }
  327. _onCanMeasureChanged(Object sender, bool e) {
  328. if (e && mounted) {
  329. changeItem(0);
  330. }
  331. }
  332. @override
  333. Widget build(BuildContext context) {
  334. return Container(
  335. width: 300,
  336. padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
  337. child: Scrollbar(
  338. controller: scrollController,
  339. isAlwaysShown: true,
  340. child: ListView.separated(
  341. controller: scrollController,
  342. itemCount: C_SUPPORTED_ITEMS.length,
  343. itemBuilder: (BuildContext context, int index) {
  344. final name = C_SUPPORTED_ITEMS[index];
  345. final active = index == activeIndex;
  346. return active
  347. ? ElevatedButton(
  348. onPressed: () => changeItem(index),
  349. child: Text(name),
  350. style: ElevatedButton.styleFrom(
  351. fixedSize: const Size.fromHeight(50),
  352. ),
  353. )
  354. : OutlinedButton(
  355. onPressed: () => changeItem(index),
  356. child: Text(name),
  357. style: OutlinedButton.styleFrom(
  358. fixedSize: const Size.fromHeight(50),
  359. ),
  360. );
  361. },
  362. separatorBuilder: (BuildContext context, int index) {
  363. return const SizedBox(height: 8);
  364. },
  365. ),
  366. ),
  367. );
  368. }
  369. void buildItems() {
  370. final count = C_SUPPORTED_ITEMS.length;
  371. final children = <Widget>[];
  372. for (var i = 0; i < count; i++) {
  373. final name = C_SUPPORTED_ITEMS[i];
  374. final active = i == activeIndex;
  375. final widget = active
  376. ? ElevatedButton(
  377. onPressed: () => changeItem(i),
  378. child: Text(name),
  379. style: ElevatedButton.styleFrom(
  380. fixedSize: const Size.fromHeight(50),
  381. ),
  382. )
  383. : OutlinedButton(
  384. onPressed: () => changeItem(i),
  385. child: Text(name),
  386. style: OutlinedButton.styleFrom(
  387. fixedSize: const Size.fromHeight(50),
  388. ),
  389. );
  390. if (i > 0) {
  391. children.add(const SizedBox(height: 8));
  392. }
  393. children.add(widget);
  394. }
  395. }
  396. void changeItem(int index) {
  397. setState(() {
  398. activeIndex = index;
  399. });
  400. final name = C_SUPPORTED_ITEMS[index];
  401. application.switchItemByName(name);
  402. }
  403. }
  404. class _MeasureLeftAnnotation extends StatefulWidget {
  405. const _MeasureLeftAnnotation({Key? key}) : super(key: key);
  406. @override
  407. State<StatefulWidget> createState() => _MeasureLeftAnnotationState();
  408. }
  409. class _MeasureLeftAnnotationState extends State<_MeasureLeftAnnotation> {
  410. // ignore: non_constant_identifier_names
  411. static final C_SUPPORTED_TEXTS = <String>[
  412. "肝左叶",
  413. "胆囊",
  414. "脾脏",
  415. "结石",
  416. "积液",
  417. ];
  418. final scrollController = ScrollController();
  419. final application = Get.find<IApplication>();
  420. @override
  421. void initState() {
  422. // application.switchAnnotation(AnnotationType.label, C_SUPPORTED_TEXTS[0]);
  423. // application.switchAnnotation(AnnotationType.arrow);
  424. application.switchAnnotation(AnnotationType.input);
  425. super.initState();
  426. }
  427. @override
  428. Widget build(BuildContext context) {
  429. return Container(
  430. width: 300,
  431. padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
  432. child: Scrollbar(
  433. controller: scrollController,
  434. isAlwaysShown: true,
  435. child: ListView.separated(
  436. controller: scrollController,
  437. itemCount: C_SUPPORTED_TEXTS.length,
  438. itemBuilder: (BuildContext context, int index) {
  439. final name = C_SUPPORTED_TEXTS[index];
  440. const style = TextStyle(color: Colors.white, fontSize: 16);
  441. const dragStyle = TextStyle(color: Colors.amber, fontSize: 18);
  442. // TODO: melon - set drag cursor after version updated up then 3.0
  443. // https://github.com/flutter/flutter/pull/100475
  444. return Draggable<String>(
  445. data: name,
  446. dragAnchorStrategy: (data, context, offset) {
  447. // return offset - Offset(120, 14);
  448. return Offset.zero;
  449. },
  450. child: OutlinedButton(
  451. child: Text(name, style: style),
  452. onPressed: () {
  453. application.switchAnnotation(AnnotationType.label, name);
  454. },
  455. style: OutlinedButton.styleFrom(
  456. shape: RoundedRectangleBorder(
  457. borderRadius: BorderRadius.circular(4),
  458. ),
  459. side: BorderSide(color: Colors.grey.shade100),
  460. fixedSize: const Size.fromHeight(44),
  461. ),
  462. ),
  463. feedback: Material(
  464. color: Colors.transparent,
  465. child: Text(name, style: dragStyle),
  466. ),
  467. onDragStarted: () {
  468. application.switchAnnotation(AnnotationType.label, name);
  469. },
  470. );
  471. },
  472. separatorBuilder: (BuildContext context, int index) {
  473. return const SizedBox(height: 8);
  474. },
  475. ),
  476. ),
  477. );
  478. }
  479. }
  480. class _ModeTips extends StatefulWidget {
  481. @override
  482. State<StatefulWidget> createState() => _ModeTipsState();
  483. }
  484. class _ModeTipsState extends State<_ModeTips> {
  485. IApplication? application;
  486. @override
  487. void initState() {
  488. Future.delayed(const Duration(seconds: 2), () {
  489. application = Get.find<IApplication>();
  490. application!.visualsLoaded.addListener(_onVisualsLoaded);
  491. if (mounted) {
  492. setState(() {});
  493. }
  494. });
  495. super.initState();
  496. }
  497. @override
  498. void dispose() {
  499. application!.visualsLoaded.removeListener(_onVisualsLoaded);
  500. super.dispose();
  501. }
  502. void _onVisualsLoaded(Object sender, void e) {
  503. setState(() {});
  504. }
  505. @override
  506. Widget build(BuildContext context) {
  507. if (application == null) return const Text("Wait");
  508. return Material(
  509. child: Text(application!.avaliableModes.map((e) => e.name).join('/')),
  510. );
  511. }
  512. }