measure_page_test.dart 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  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/item_metas.dart';
  6. import 'package:fis_measure/interfaces/process/items/terms.dart';
  7. import 'package:fis_measure/interfaces/process/items/types.dart';
  8. import 'package:fis_measure/interfaces/process/player/play_controller.dart';
  9. import 'package:fis_measure/interfaces/process/standard_line/calibration.dart';
  10. import 'package:fis_measure/interfaces/process/visuals/visual_area.dart';
  11. import 'package:fis_measure/interfaces/process/workspace/application.dart';
  12. import 'package:fis_measure/interfaces/process/workspace/exam_info.dart';
  13. import 'package:fis_measure/interfaces/process/workspace/measure_controller.dart';
  14. import 'package:fis_measure/process/items/item_meta_convert.dart';
  15. import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart';
  16. import 'package:fis_measure/process/workspace/measure_controller.dart';
  17. import 'package:fis_measure/process/workspace/measure_data_controller.dart';
  18. import 'package:fis_measure/view/measure/measure_main_view.dart';
  19. import 'package:flutter/material.dart';
  20. import 'package:get/get.dart';
  21. import 'package:vid/us/vid_us_mode.dart';
  22. import 'item_create_test.dart';
  23. import 'process/workspace/measure_handler.dart';
  24. class MeasureDataTester {
  25. static Future<List<RemedicalItemList>> getRemedicalList(
  26. String patientCode,
  27. String recordCode,
  28. String token,
  29. ) async {
  30. return [];
  31. }
  32. static Future<RemedicalInfoDTO?> getImageInfo(
  33. String remedicalCode,
  34. String token,
  35. String? remedicalAISelectedInfoCode,
  36. ) async {
  37. return null;
  38. }
  39. static Future<MeasureApplicationDTO?> getMeasureApplication(
  40. dynamic args) async {
  41. return null;
  42. }
  43. static Future<bool> saveUserDefinedMeasureApplicationAsync(
  44. dynamic args) async {
  45. return true;
  46. }
  47. static Future<bool?> saveImage(
  48. Uint8List imageBytes,
  49. String patientCode,
  50. String recordCode,
  51. String remedicalCode,
  52. String measuredData,
  53. ) async {
  54. return null;
  55. }
  56. static Future<void> saveMeasureSystemSettingAsync(
  57. MeasureSystemSettingDTO measureSystemSetting) async {
  58. return;
  59. }
  60. static Future<MeasureSystemSettingDTO?> getMeasureSystemSettingAsync() async {
  61. return null;
  62. }
  63. static Future<String> shareImage(String vid) async {
  64. return '';
  65. }
  66. static Future<CommentItemResultDTO?> getCommentsByApplicationAsync(
  67. String applicationName, String categoryName) async {
  68. return null;
  69. }
  70. static Future<PresetCommentItemResultDTO?> getPresetCommentsAsync() async {
  71. return null;
  72. }
  73. ///参数1:remedicalCode,参数2:token,参数3:patientCode,参数4:图像位置
  74. static Future<RemedicalInfoDTO?> getImageInfoByIndex(
  75. String remedicalCode, String token, String patientCode, int index) async {
  76. return null;
  77. }
  78. static Future<bool?> resetUserCommentsAsync(
  79. String applicationName, String categoryName) async {
  80. return null;
  81. }
  82. static Future<bool?> saveUserDefinedCommentsAsync(
  83. String applicationName,
  84. String categoryName,
  85. List<CommentItemDTO>? add,
  86. List<CommentItemDTO>? delete,
  87. List<UpdateCommentItemDTO>? update) async {
  88. return null;
  89. }
  90. }
  91. class MeasureTestPage extends StatefulWidget {
  92. const MeasureTestPage({Key? key}) : super(key: key);
  93. // ignore: non_constant_identifier_names
  94. static List<dynamic> MetaDTOList = [];
  95. @override
  96. State<StatefulWidget> createState() => _MeasureTestPageState();
  97. }
  98. class _MeasureTestPageState extends State<MeasureTestPage> {
  99. static const C_LINEAR_TISSUE =
  100. // "http://cdn-bj.fis.plus/9F066341FA874E21B48CDE247C13D495.vid"; //B TVI TD
  101. // "http://cdn-bj.fis.plus/974BABA5113640639FD749E06DD7DA5B.vid"; //B CF CW
  102. "http://cdn-bj.fis.plus/0B344F48BA574ECD82B7FEDB8848421A.vid"; //单幅TM
  103. // "http://cdn-bj.fis.plus/3379F38302884C2991D90FBDFB0DEA7E.dat"; //单幅TM(2)
  104. // "http://cdn-bj.fis.plus/6A99AD2530864616B64355A8EA9AE3EC.vid";
  105. // "http://cdn-bj.fis.plus/F26C6E5D57A7472A97E9EB543DF0D16C.vid"; // 单幅Convex
  106. // "http://cdn-bj.fis.plus/6B6E069659D14E7299EB9F6EFCDE9C8C.vid"; //双幅单TissueConvex
  107. // "http://cdn-bj.fis.plus/062643B82365437DB95F3811580AF3ED.vid"; //四幅单模式
  108. // "http://cdn-bj.fis.plus/EA90D146049D416E8E466B7446E00001.vid"; //四幅Doppler
  109. // "http://cdn-bj.fis.plus/3rd_linearTvTissue2.vid"; //魔盒
  110. // "http://cdn-bj.fis.plus/81FFF8E5E078473FA687FBE81C4869B1.vid"; // 魔盒TV
  111. // "http://cdn-bj.fis.plus/7B450708A2784B1490304C82787349BE.vid";// 胎儿Zoom
  112. static const C_CONVEX_TISSUE =
  113. "http://cdn-bj.fis.plus/FEB1AAE5D9C24839BEE31B16E8CB450A.vid";
  114. final _3dc = Get.put<Measure3DViewController>(Measure3DViewController());
  115. final datac = Get.put<MeasureDataController>(MeasureDataController(
  116. MeasureDataTester.getRemedicalList,
  117. MeasureDataTester.getImageInfo,
  118. MeasureDataTester.getMeasureApplication,
  119. MeasureDataTester.saveUserDefinedMeasureApplicationAsync,
  120. MeasureDataTester.saveImage,
  121. MeasureDataTester.saveMeasureSystemSettingAsync,
  122. MeasureDataTester.getMeasureSystemSettingAsync,
  123. MeasureDataTester.shareImage,
  124. MeasureDataTester.getCommentsByApplicationAsync,
  125. MeasureDataTester.saveUserDefinedCommentsAsync,
  126. MeasureDataTester.resetUserCommentsAsync,
  127. MeasureDataTester.getPresetCommentsAsync,
  128. MeasureDataTester.getImageInfoByIndex,
  129. ));
  130. final measureHandler = Get.put(MeasureHandler());
  131. final controller = Get.put<IMeasureController>(MeasureController(
  132. "12345",
  133. imagesFetchFunc: (code) async {
  134. return <ExamImageInfo>[
  135. ExamImageInfo(C_LINEAR_TISSUE, C_LINEAR_TISSUE),
  136. ExamImageInfo(C_CONVEX_TISSUE, C_CONVEX_TISSUE)
  137. ];
  138. },
  139. ));
  140. bool loaded = false;
  141. int opType = 0;
  142. bool useArrowAnnotation = false;
  143. @override
  144. void initState() {
  145. measureHandler.changeImageLoaded;
  146. controller.load().then((value) {
  147. // 加载指定图像
  148. controller.examInfo.selectedImageIndex = 0;
  149. });
  150. controller.imageLoaded.addListener(onImageLoaded);
  151. super.initState();
  152. }
  153. @override
  154. void dispose() {
  155. controller.imageLoaded.removeListener(onImageLoaded);
  156. controller.dispose();
  157. Get.delete<IMeasureController>();
  158. super.dispose();
  159. }
  160. void onImageLoaded(Object sender, ExamImageInfo? e) {
  161. if (!mounted) return;
  162. if (e != null) {
  163. Future.delayed(const Duration(milliseconds: 100), () {
  164. controller.playerController.play();
  165. });
  166. setState(() {
  167. loaded = true;
  168. });
  169. }
  170. }
  171. @override
  172. Widget build(BuildContext context) {
  173. Widget body;
  174. if (!loaded) {
  175. const loadingWidget = Center(child: CircularProgressIndicator());
  176. body = Row(
  177. children: const [
  178. SizedBox(
  179. width: 300,
  180. child: loadingWidget,
  181. ),
  182. VerticalDivider(),
  183. Expanded(child: loadingWidget),
  184. ],
  185. );
  186. } else {
  187. body = Row(
  188. key: ValueKey(controller.examInfo.selectedImageIndex),
  189. children: [
  190. opType == 1
  191. ? const _MeasureLeftAnnotation()
  192. : const _MeasureLeftBoard(),
  193. const VerticalDivider(),
  194. const Expanded(
  195. child: MeasureRightBoard(),
  196. ),
  197. ],
  198. );
  199. }
  200. return Scaffold(
  201. backgroundColor: const Color.fromARGB(255, 53, 55, 51),
  202. appBar: AppBar(
  203. actions: [
  204. TextButton(
  205. onPressed: () {
  206. Get.find<IApplication>().clearRecords();
  207. },
  208. child: const Text(
  209. "清空",
  210. style: TextStyle(
  211. color: Colors.white,
  212. ),
  213. ),
  214. ),
  215. TextButton(
  216. onPressed: () {
  217. Get.find<IApplication>().undoRecord();
  218. },
  219. child: const Text(
  220. "撤销",
  221. style: TextStyle(
  222. color: Colors.white,
  223. ),
  224. ),
  225. ),
  226. TextButton(
  227. onPressed: () {
  228. final c = Get.find<IStandardLineCalibrationController>();
  229. if (c.isEditing) {
  230. c.cancelEdit();
  231. } else {
  232. c.enterEditMode();
  233. }
  234. },
  235. child: const Text(
  236. "校准线",
  237. style: TextStyle(
  238. color: Colors.white,
  239. ),
  240. ),
  241. ),
  242. TextButton.icon(
  243. onPressed: () {
  244. setState(() {
  245. if (useArrowAnnotation) {
  246. useArrowAnnotation = false;
  247. controller.workingApplication.switchAnnotation();
  248. } else {
  249. useArrowAnnotation = true;
  250. controller.workingApplication
  251. .switchAnnotation(AnnotationType.arrow);
  252. }
  253. });
  254. },
  255. icon: const Icon(Icons.arrow_right_alt_sharp),
  256. label: Text(
  257. "箭头",
  258. style: TextStyle(
  259. color: useArrowAnnotation ? Colors.amber : Colors.white,
  260. ),
  261. ),
  262. ),
  263. TextButton(
  264. onPressed: () {
  265. setState(() {
  266. opType = opType == 1 ? 0 : 1;
  267. });
  268. },
  269. child: Text(
  270. opType == 1 ? "注释" : "测量",
  271. style: const TextStyle(
  272. color: Colors.white,
  273. ),
  274. ),
  275. ),
  276. TextButton(
  277. onPressed: () {
  278. if (controller.examInfo.selectedImageIndex == 0) return;
  279. controller.examInfo.selectedImageIndex = 0;
  280. },
  281. child: Text(
  282. '线阵',
  283. style: TextStyle(
  284. color: controller.examInfo.selectedImageIndex == 0
  285. ? Colors.amber
  286. : Colors.white,
  287. ),
  288. ),
  289. ),
  290. TextButton(
  291. onPressed: () {
  292. if (controller.examInfo.selectedImageIndex == 1) return;
  293. controller.examInfo.selectedImageIndex = 1;
  294. },
  295. child: Text(
  296. '扇阵',
  297. style: TextStyle(
  298. color: controller.examInfo.selectedImageIndex == 1
  299. ? Colors.amber
  300. : Colors.white,
  301. ),
  302. ),
  303. ),
  304. ],
  305. leading: IconButton(
  306. onPressed: () {
  307. Navigator.of(context).pop();
  308. },
  309. icon: const Icon(Icons.arrow_back),
  310. ),
  311. ),
  312. body: body,
  313. floatingActionButton: _ModeTips(),
  314. floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
  315. );
  316. }
  317. }
  318. class MeasureRightBoard extends StatefulWidget {
  319. const MeasureRightBoard({Key? key}) : super(key: key);
  320. @override
  321. State<StatefulWidget> createState() => _MeasureRightBoardState();
  322. }
  323. class _MeasureRightBoardState extends State<MeasureRightBoard> {
  324. final playerController = Get.find<IPlayerController>();
  325. @override
  326. Widget build(BuildContext context) {
  327. return Container(
  328. padding: const EdgeInsets.all(8).copyWith(left: 0),
  329. child: Stack(
  330. children: const [
  331. MeasureMainView(),
  332. _PlayerTips(),
  333. ],
  334. ),
  335. );
  336. }
  337. }
  338. class _PlayerTips extends StatefulWidget {
  339. const _PlayerTips({Key? key}) : super(key: key);
  340. @override
  341. State<StatefulWidget> createState() => _PlayerTipsState();
  342. }
  343. class _PlayerTipsState extends State<_PlayerTips> {
  344. final playerController = Get.find<IPlayerController>();
  345. bool loading = false;
  346. String content = 'xxxxxxxxxxxxxxxxxxxx';
  347. @override
  348. void initState() {
  349. playerController.frameLoadStateChanged.addListener(_onLoadStateChanged);
  350. super.initState();
  351. }
  352. @override
  353. void dispose() {
  354. playerController.frameLoadStateChanged.removeListener(_onLoadStateChanged);
  355. super.dispose();
  356. }
  357. void _onLoadStateChanged(sender, bool e) {
  358. loading = e;
  359. if (loading) {
  360. Future.delayed(
  361. const Duration(milliseconds: 100),
  362. () {
  363. setState(() {
  364. content = loading ? "Loading。。。" : "";
  365. });
  366. },
  367. );
  368. } else {
  369. setState(() {
  370. content = "";
  371. });
  372. }
  373. }
  374. @override
  375. Widget build(BuildContext context) {
  376. return Positioned(
  377. child: Center(
  378. child: Text(
  379. content,
  380. style: const TextStyle(
  381. color: Colors.white,
  382. fontSize: 28,
  383. ),
  384. ),
  385. ),
  386. top: Get.size.height * 0.44,
  387. left: 0,
  388. right: 0,
  389. );
  390. }
  391. }
  392. class _MeasureLeftBoard extends StatefulWidget {
  393. const _MeasureLeftBoard({Key? key}) : super(key: key);
  394. @override
  395. State<StatefulWidget> createState() => _MeasureLeftBoardState();
  396. }
  397. class _MeasureLeftBoardState extends State<_MeasureLeftBoard> {
  398. // ignore: non_constant_identifier_names
  399. static final C_SUPPORTED_ITEMS = <String>[
  400. MeasureTerms.Distance,
  401. MeasureTerms.Perimeter,
  402. MeasureTerms.Area,
  403. MeasureTerms.Angle,
  404. MeasureTerms.Depth,
  405. MeasureTerms.Volume,
  406. MeasureTerms.Stenosis,
  407. MeasureTerms.AbRatio,
  408. MeasureTerms.RUV,
  409. //
  410. MeasureTypes.AreaPerimeterEllipse,
  411. MeasureTypes.AreaPerimeterPolyline,
  412. MeasureTypes.AreaPerimeterSpline,
  413. ];
  414. // ignore: non_constant_identifier_names
  415. static final C_SUPPORTED_M_ITEMS = <String>[
  416. MeasureTerms.VerticalDistance,
  417. MeasureTerms.Timespan,
  418. MeasureTerms.Depth,
  419. MeasureTerms.Stenosis,
  420. MeasureTerms.AbRatio,
  421. MeasureTerms.Slope,
  422. MeasureTerms.TAMAX,
  423. MeasureTerms.Velocity,
  424. MeasureTerms.Acceleration,
  425. MeasureTerms.PSED,
  426. MeasureTerms.RI,
  427. MeasureTerms.MaxPG,
  428. "LV TEI Index",
  429. "AV Ratio",
  430. MeasureTerms.HeartRate,
  431. // MeasureTerms.PHT,
  432. "MV PHT",
  433. "Qp/Qs",
  434. ];
  435. late final List<String> passeItems;
  436. final scrollController = ScrollController();
  437. final application = Get.find<IApplication>();
  438. String modeType = 'Tissue';
  439. int activeIndex = 0;
  440. List<ItemMeta> workingItems = [];
  441. @override
  442. void initState() {
  443. // passeItems = C_SUPPORTED_ITEMS;
  444. // passeItems = TestItems.C_DISTANCE_ITEMS;
  445. passeItems = TestItems.C_TEST_ITEMS;
  446. loadItems();
  447. application.visualAreaChanged.addListener(_visualAreaChanged);
  448. super.initState();
  449. }
  450. @override
  451. dispose() {
  452. application.visualAreaChanged.removeListener(_visualAreaChanged);
  453. super.dispose();
  454. }
  455. void loadItems() {
  456. workingItems = [];
  457. var names = (modeType == "TissueTM" || modeType == "Doppler")
  458. ? C_SUPPORTED_M_ITEMS
  459. : passeItems;
  460. final workingItemDtos =
  461. MeasureTestPage.MetaDTOList.where((e) => names.contains(e['Name']));
  462. for (var map in workingItemDtos) {
  463. final dto = ItemMetaDTO.fromJson(map);
  464. final item = ItemMetaConverter(dto).output();
  465. workingItems.add(item);
  466. }
  467. }
  468. void _visualAreaChanged(sender, IVisualArea e) {
  469. if (mounted) {
  470. _setAvailableModes(e.mode.modeType.toString().split('.')[1]);
  471. // changeItemByMeta(0); //暂时不要自动切测量项
  472. setState(() {});
  473. }
  474. }
  475. void _setAvailableModes(String name) {
  476. modeType = name;
  477. loadItems();
  478. }
  479. @override
  480. Widget build(BuildContext context) {
  481. return Container(
  482. width: 300,
  483. padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
  484. child: Scrollbar(
  485. controller: scrollController,
  486. isAlwaysShown: true,
  487. child: ListView.separated(
  488. controller: scrollController,
  489. itemCount: workingItems.length,
  490. itemBuilder: (BuildContext context, int index) {
  491. final meta = workingItems[index];
  492. final active = index == activeIndex;
  493. return active
  494. ? ElevatedButton(
  495. onPressed: () {
  496. changeItemByMeta(index);
  497. },
  498. child: Text(meta.name),
  499. style: ElevatedButton.styleFrom(
  500. fixedSize: const Size.fromHeight(50),
  501. ),
  502. )
  503. : OutlinedButton(
  504. onPressed: () => changeItemByMeta(index),
  505. child: Text(meta.name),
  506. style: OutlinedButton.styleFrom(
  507. fixedSize: const Size.fromHeight(50),
  508. ),
  509. );
  510. },
  511. separatorBuilder: (BuildContext context, int index) {
  512. return const SizedBox(height: 8);
  513. },
  514. ),
  515. ),
  516. );
  517. }
  518. void changeItemByMeta(int index) {
  519. setState(() {
  520. activeIndex = index;
  521. });
  522. final meta = workingItems[index];
  523. application.switchItem(meta);
  524. print(application.activeMeasureItem?.meta.name);
  525. // handle combo item
  526. if (application.activeMeasureItem != null) {
  527. final item = application.activeMeasureItem!;
  528. if (item is ITopMeasureItem) {
  529. item.switchChild(0);
  530. }
  531. }
  532. }
  533. }
  534. class _MeasureLeftAnnotation extends StatefulWidget {
  535. const _MeasureLeftAnnotation({Key? key}) : super(key: key);
  536. @override
  537. State<StatefulWidget> createState() => _MeasureLeftAnnotationState();
  538. }
  539. class _MeasureLeftAnnotationState extends State<_MeasureLeftAnnotation> {
  540. // ignore: non_constant_identifier_names
  541. static final C_SUPPORTED_TEXTS = <String>[
  542. "肝左叶",
  543. "胆囊",
  544. "脾脏",
  545. "结石",
  546. "积液",
  547. ];
  548. final scrollController = ScrollController();
  549. final application = Get.find<IApplication>();
  550. @override
  551. void initState() {
  552. // application.switchAnnotation(AnnotationType.label, C_SUPPORTED_TEXTS[0]);
  553. // application.switchAnnotation(AnnotationType.arrow);
  554. application.switchAnnotation(AnnotationType.input);
  555. super.initState();
  556. }
  557. @override
  558. Widget build(BuildContext context) {
  559. return Container(
  560. width: 300,
  561. padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
  562. child: Scrollbar(
  563. controller: scrollController,
  564. isAlwaysShown: true,
  565. child: ListView.separated(
  566. controller: scrollController,
  567. itemCount: C_SUPPORTED_TEXTS.length,
  568. itemBuilder: (BuildContext context, int index) {
  569. final name = C_SUPPORTED_TEXTS[index];
  570. const style = TextStyle(color: Colors.white, fontSize: 16);
  571. const dragStyle = TextStyle(color: Colors.amber, fontSize: 18);
  572. // TODO: melon - set drag cursor after version updated up then 3.0
  573. // https://github.com/flutter/flutter/pull/100475
  574. return Draggable<String>(
  575. data: name,
  576. dragAnchorStrategy: (data, context, offset) {
  577. // return offset - Offset(120, 14);
  578. return Offset.zero;
  579. },
  580. child: OutlinedButton(
  581. child: Text(name, style: style),
  582. onPressed: () {
  583. application.switchAnnotation(AnnotationType.label, name);
  584. },
  585. style: OutlinedButton.styleFrom(
  586. shape: RoundedRectangleBorder(
  587. borderRadius: BorderRadius.circular(4),
  588. ),
  589. side: BorderSide(color: Colors.grey.shade100),
  590. fixedSize: const Size.fromHeight(44),
  591. ),
  592. ),
  593. feedback: Material(
  594. color: Colors.transparent,
  595. child: Text(name, style: dragStyle),
  596. ),
  597. onDragStarted: () {
  598. application.switchAnnotation(AnnotationType.label, name);
  599. },
  600. );
  601. },
  602. separatorBuilder: (BuildContext context, int index) {
  603. return const SizedBox(height: 8);
  604. },
  605. ),
  606. ),
  607. );
  608. }
  609. }
  610. class _ModeTips extends StatefulWidget {
  611. @override
  612. State<StatefulWidget> createState() => _ModeTipsState();
  613. }
  614. class _ModeTipsState extends State<_ModeTips> {
  615. IApplication? application;
  616. @override
  617. void initState() {
  618. Future.delayed(const Duration(milliseconds: 100), () {
  619. application = Get.find<IApplication>();
  620. application!.visualsLoaded.addListener(_onVisualsLoaded);
  621. });
  622. super.initState();
  623. }
  624. @override
  625. void dispose() {
  626. application!.visualsLoaded.removeListener(_onVisualsLoaded);
  627. super.dispose();
  628. }
  629. void _onVisualsLoaded(Object sender, void e) {
  630. setState(() {});
  631. }
  632. @override
  633. Widget build(BuildContext context) {
  634. if (application == null) return const Text("Wait");
  635. return Material(
  636. child: Text(application!.avaliableModes.map((e) => e.name).join('/')),
  637. );
  638. }
  639. }