measure_page_test.dart 17 KB

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