measure_page_test.dart 13 KB

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