import 'dart:typed_data'; import 'package:fis_jsonrpc/rpc.dart'; import 'package:fis_measure/interfaces/enums/annotation.dart'; import 'package:fis_measure/interfaces/process/items/item.dart'; import 'package:fis_measure/interfaces/process/items/item_metas.dart'; import 'package:fis_measure/interfaces/process/items/terms.dart'; import 'package:fis_measure/interfaces/process/items/types.dart'; import 'package:fis_measure/interfaces/process/player/play_controller.dart'; import 'package:fis_measure/interfaces/process/standard_line/calibration.dart'; import 'package:fis_measure/interfaces/process/visuals/visual_area.dart'; import 'package:fis_measure/interfaces/process/workspace/application.dart'; import 'package:fis_measure/interfaces/process/workspace/exam_info.dart'; import 'package:fis_measure/interfaces/process/workspace/measure_controller.dart'; import 'package:fis_measure/process/items/item_meta_convert.dart'; import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart'; import 'package:fis_measure/process/workspace/measure_controller.dart'; import 'package:fis_measure/process/workspace/measure_data_controller.dart'; import 'package:fis_measure/process/workspace/rpc_bridge.dart'; import 'package:fis_measure/view/measure/measure_main_view.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'item_create_test.dart'; import 'process/workspace/measure_handler.dart'; class MeasureDataTester { static Future> getRemedicalList( String patientCode, String recordCode, String token, ) async { return []; } static Future getImageInfo( String remedicalCode, String token, String? remedicalAISelectedInfoCode, ) async { return null; } static Future getMeasureApplication( dynamic args) async { return null; } static Future saveUserDefinedMeasureApplicationAsync( dynamic args) async { return true; } static Future saveImage( Uint8List imageBytes, String patientCode, String recordCode, String remedicalCode, String measuredData, VidImageSource source, ) async { return null; } static Future saveMeasureSystemSettingAsync( MeasureSystemSettingDTO measureSystemSetting) async { return; } static Future getMeasureSystemSettingAsync() async { return null; } static Future shareImage(String vid) async { return ''; } static Future getCommentsByApplicationAsync( String applicationName, String categoryName) async { return null; } static Future getPresetCommentsAsync() async { return null; } ///参数1:remedicalCode,参数2:token,参数3:patientCode,参数4:图像位置 static Future getImageInfoByIndex( String remedicalCode, String token, String patientCode, int index) async { return null; } static Future resetUserCommentsAsync( String applicationName, String categoryName) async { return null; } static Future saveUserDefinedCommentsAsync( String applicationName, String categoryName, List? add, List? delete, List? update) async { return null; } } class MeasureTestPage extends StatefulWidget { const MeasureTestPage({Key? key}) : super(key: key); // ignore: non_constant_identifier_names static List MetaDTOList = []; @override State createState() => _MeasureTestPageState(); } class _MeasureTestPageState extends State { static const C_LINEAR_TISSUE = // "http://cdn-bj.fis.plus/9F066341FA874E21B48CDE247C13D495.vid"; //B TVI TD // "http://cdn-bj.fis.plus/974BABA5113640639FD749E06DD7DA5B.vid"; //B CF CW "http://cdn-bj.fis.plus/0B344F48BA574ECD82B7FEDB8848421A.vid"; //单幅TM // "http://cdn-bj.fis.plus/3379F38302884C2991D90FBDFB0DEA7E.dat"; //单幅TM(2) // "http://cdn-bj.fis.plus/6A99AD2530864616B64355A8EA9AE3EC.vid"; // "http://cdn-bj.fis.plus/F26C6E5D57A7472A97E9EB543DF0D16C.vid"; // 单幅Convex // "http://cdn-bj.fis.plus/6B6E069659D14E7299EB9F6EFCDE9C8C.vid"; //双幅单TissueConvex // "http://cdn-bj.fis.plus/062643B82365437DB95F3811580AF3ED.vid"; //四幅单模式 // "http://cdn-bj.fis.plus/EA90D146049D416E8E466B7446E00001.vid"; //四幅Doppler // "http://cdn-bj.fis.plus/3rd_linearTvTissue2.vid"; //魔盒 // "http://cdn-bj.fis.plus/81FFF8E5E078473FA687FBE81C4869B1.vid"; // 魔盒TV // "http://cdn-bj.fis.plus/7B450708A2784B1490304C82787349BE.vid";// 胎儿Zoom static const C_CONVEX_TISSUE = "http://cdn-bj.fis.plus/FEB1AAE5D9C24839BEE31B16E8CB450A.vid"; final _3dc = Get.put(Measure3DViewController()); final datac = Get.put(MeasureDataController( // MeasureDataTester.getRemedicalList, // MeasureDataTester.getImageInfo, // MeasureDataTester.getMeasureApplication, // MeasureDataTester.saveUserDefinedMeasureApplicationAsync, MeasureDataTester.saveImage, // MeasureDataTester.saveMeasureSystemSettingAsync, // MeasureDataTester.getMeasureSystemSettingAsync, // MeasureDataTester.shareImage, // MeasureDataTester.getCommentsByApplicationAsync, // MeasureDataTester.saveUserDefinedCommentsAsync, // MeasureDataTester.resetUserCommentsAsync, // MeasureDataTester.getPresetCommentsAsync, // MeasureDataTester.getImageInfoByIndex, )); final measureHandler = Get.put(MeasureHandler()); final controller = Get.put(MeasureController( "12345", imagesFetchFunc: (code) async { return [ ExamImageInfo(C_LINEAR_TISSUE, C_LINEAR_TISSUE), ExamImageInfo(C_CONVEX_TISSUE, C_CONVEX_TISSUE) ]; }, )); bool loaded = false; int opType = 0; bool useArrowAnnotation = false; @override void initState() { measureHandler.newImageLoading; controller.load().then((value) { // 加载指定图像 controller.examInfo.selectedImageIndex = 0; }); controller.imageLoaded.addListener(onImageLoaded); super.initState(); } @override void dispose() { controller.imageLoaded.removeListener(onImageLoaded); controller.dispose(); Get.delete(); super.dispose(); } void onImageLoaded(Object sender, ExamImageInfo? e) { if (!mounted) return; if (e != null) { Future.delayed(const Duration(milliseconds: 100), () { controller.playerController.play(); }); setState(() { loaded = true; }); } } @override Widget build(BuildContext context) { Widget body; if (!loaded) { const loadingWidget = Center(child: CircularProgressIndicator()); body = Row( children: const [ SizedBox( width: 300, child: loadingWidget, ), VerticalDivider(), Expanded(child: loadingWidget), ], ); } else { body = Row( key: ValueKey(controller.examInfo.selectedImageIndex), children: [ opType == 1 ? const _MeasureLeftAnnotation() : const _MeasureLeftBoard(), const VerticalDivider(), const Expanded( child: MeasureRightBoard(), ), ], ); } return Scaffold( backgroundColor: const Color.fromARGB(255, 53, 55, 51), appBar: AppBar( actions: [ TextButton( onPressed: () { Get.find().clearRecords(); }, child: const Text( "清空", style: TextStyle( color: Colors.white, ), ), ), TextButton( onPressed: () { Get.find().undoRecord(); }, child: const Text( "撤销", style: TextStyle( color: Colors.white, ), ), ), TextButton( onPressed: () { final c = Get.find(); if (c.isEditing) { c.cancelEdit(); } else { c.enterEditMode(); } }, child: const Text( "校准线", style: TextStyle( color: Colors.white, ), ), ), TextButton.icon( onPressed: () { setState(() { if (useArrowAnnotation) { useArrowAnnotation = false; controller.workingApplication.switchAnnotation(); } else { useArrowAnnotation = true; controller.workingApplication .switchAnnotation(AnnotationType.arrow); } }); }, icon: const Icon(Icons.arrow_right_alt_sharp), label: Text( "箭头", style: TextStyle( color: useArrowAnnotation ? Colors.amber : Colors.white, ), ), ), TextButton( onPressed: () { setState(() { opType = opType == 1 ? 0 : 1; }); }, child: Text( opType == 1 ? "注释" : "测量", style: const TextStyle( color: Colors.white, ), ), ), TextButton( onPressed: () { if (controller.examInfo.selectedImageIndex == 0) return; controller.examInfo.selectedImageIndex = 0; }, child: Text( '线阵', style: TextStyle( color: controller.examInfo.selectedImageIndex == 0 ? Colors.amber : Colors.white, ), ), ), TextButton( onPressed: () { if (controller.examInfo.selectedImageIndex == 1) return; controller.examInfo.selectedImageIndex = 1; }, child: Text( '扇阵', style: TextStyle( color: controller.examInfo.selectedImageIndex == 1 ? Colors.amber : Colors.white, ), ), ), ], leading: IconButton( onPressed: () { Navigator.of(context).pop(); }, icon: const Icon(Icons.arrow_back), ), ), body: body, floatingActionButton: _ModeTips(), floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, ); } } class MeasureRightBoard extends StatefulWidget { const MeasureRightBoard({Key? key}) : super(key: key); @override State createState() => _MeasureRightBoardState(); } class _MeasureRightBoardState extends State { final playerController = Get.find(); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(8).copyWith(left: 0), child: Stack( children: const [ MeasureMainView(), _PlayerTips(), ], ), ); } } class _PlayerTips extends StatefulWidget { const _PlayerTips({Key? key}) : super(key: key); @override State createState() => _PlayerTipsState(); } class _PlayerTipsState extends State<_PlayerTips> { final playerController = Get.find(); bool loading = false; String content = 'xxxxxxxxxxxxxxxxxxxx'; @override void initState() { playerController.frameLoadStateChanged.addListener(_onLoadStateChanged); super.initState(); } @override void dispose() { playerController.frameLoadStateChanged.removeListener(_onLoadStateChanged); super.dispose(); } void _onLoadStateChanged(sender, bool e) { loading = e; if (loading) { Future.delayed( const Duration(milliseconds: 100), () { setState(() { content = loading ? "Loading。。。" : ""; }); }, ); } else { setState(() { content = ""; }); } } @override Widget build(BuildContext context) { return Positioned( child: Center( child: Text( content, style: const TextStyle( color: Colors.white, fontSize: 28, ), ), ), top: Get.size.height * 0.44, left: 0, right: 0, ); } } class _MeasureLeftBoard extends StatefulWidget { const _MeasureLeftBoard({Key? key}) : super(key: key); @override State createState() => _MeasureLeftBoardState(); } class _MeasureLeftBoardState extends State<_MeasureLeftBoard> { // ignore: non_constant_identifier_names static final C_SUPPORTED_ITEMS = [ MeasureTerms.Distance, MeasureTerms.Perimeter, MeasureTerms.Area, MeasureTerms.Angle, MeasureTerms.Depth, MeasureTerms.Volume, MeasureTerms.Stenosis, MeasureTerms.AbRatio, MeasureTerms.RUV, // MeasureTypes.AreaPerimeterEllipse, MeasureTypes.AreaPerimeterPolyline, MeasureTypes.AreaPerimeterSpline, ]; // ignore: non_constant_identifier_names static final C_SUPPORTED_M_ITEMS = [ MeasureTerms.VerticalDistance, MeasureTerms.Timespan, MeasureTerms.Depth, MeasureTerms.Stenosis, MeasureTerms.AbRatio, MeasureTerms.Slope, MeasureTerms.TAMAX, MeasureTerms.Velocity, MeasureTerms.Acceleration, MeasureTerms.PSED, MeasureTerms.RI, MeasureTerms.MaxPG, "LV TEI Index", "AV Ratio", MeasureTerms.HeartRate, // MeasureTerms.PHT, "MV PHT", "Qp/Qs", ]; late final List passeItems; final scrollController = ScrollController(); final application = Get.find(); String modeType = 'Tissue'; int activeIndex = 0; List workingItems = []; @override void initState() { // passeItems = C_SUPPORTED_ITEMS; // passeItems = TestItems.C_DISTANCE_ITEMS; passeItems = TestItems.C_TEST_ITEMS; loadItems(); application.visualAreaChanged.addListener(_visualAreaChanged); super.initState(); } @override dispose() { application.visualAreaChanged.removeListener(_visualAreaChanged); super.dispose(); } void loadItems() { workingItems = []; var names = (modeType == "TissueTM" || modeType == "Doppler") ? C_SUPPORTED_M_ITEMS : passeItems; final workingItemDtos = MeasureTestPage.MetaDTOList.where((e) => names.contains(e['Name'])); for (var map in workingItemDtos) { final dto = ItemMetaDTO.fromJson(map); final item = ItemMetaConverter(dto).output(); workingItems.add(item); } } void _visualAreaChanged(sender, IVisualArea e) { if (mounted) { _setAvailableModes(e.mode.modeType.toString().split('.')[1]); // changeItemByMeta(0); //暂时不要自动切测量项 setState(() {}); } } void _setAvailableModes(String name) { modeType = name; loadItems(); } @override Widget build(BuildContext context) { return Container( width: 300, padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12), child: Scrollbar( controller: scrollController, isAlwaysShown: true, child: ListView.separated( controller: scrollController, itemCount: workingItems.length, itemBuilder: (BuildContext context, int index) { final meta = workingItems[index]; final active = index == activeIndex; return active ? ElevatedButton( onPressed: () { changeItemByMeta(index); }, child: Text(meta.name), style: ElevatedButton.styleFrom( fixedSize: const Size.fromHeight(50), ), ) : OutlinedButton( onPressed: () => changeItemByMeta(index), child: Text(meta.name), style: OutlinedButton.styleFrom( fixedSize: const Size.fromHeight(50), ), ); }, separatorBuilder: (BuildContext context, int index) { return const SizedBox(height: 8); }, ), ), ); } void changeItemByMeta(int index) { setState(() { activeIndex = index; }); final meta = workingItems[index]; application.switchItem(meta); print(application.activeMeasureItem?.meta.name); // handle combo item if (application.activeMeasureItem != null) { final item = application.activeMeasureItem!; if (item is ITopMeasureItem) { item.switchChild(0); } } } } class _MeasureLeftAnnotation extends StatefulWidget { const _MeasureLeftAnnotation({Key? key}) : super(key: key); @override State createState() => _MeasureLeftAnnotationState(); } class _MeasureLeftAnnotationState extends State<_MeasureLeftAnnotation> { // ignore: non_constant_identifier_names static final C_SUPPORTED_TEXTS = [ "肝左叶", "胆囊", "脾脏", "结石", "积液", ]; final scrollController = ScrollController(); final application = Get.find(); @override void initState() { // application.switchAnnotation(AnnotationType.label, C_SUPPORTED_TEXTS[0]); // application.switchAnnotation(AnnotationType.arrow); application.switchAnnotation(AnnotationType.input); super.initState(); } @override Widget build(BuildContext context) { return Container( width: 300, padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12), child: Scrollbar( controller: scrollController, isAlwaysShown: true, child: ListView.separated( controller: scrollController, itemCount: C_SUPPORTED_TEXTS.length, itemBuilder: (BuildContext context, int index) { final name = C_SUPPORTED_TEXTS[index]; const style = TextStyle(color: Colors.white, fontSize: 16); const dragStyle = TextStyle(color: Colors.amber, fontSize: 18); // TODO: melon - set drag cursor after version updated up then 3.0 // https://github.com/flutter/flutter/pull/100475 return Draggable( data: name, dragAnchorStrategy: (data, context, offset) { // return offset - Offset(120, 14); return Offset.zero; }, child: OutlinedButton( child: Text(name, style: style), onPressed: () { application.switchAnnotation(AnnotationType.label, name); }, style: OutlinedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), ), side: BorderSide(color: Colors.grey.shade100), fixedSize: const Size.fromHeight(44), ), ), feedback: Material( color: Colors.transparent, child: Text(name, style: dragStyle), ), onDragStarted: () { application.switchAnnotation(AnnotationType.label, name); }, ); }, separatorBuilder: (BuildContext context, int index) { return const SizedBox(height: 8); }, ), ), ); } } class _ModeTips extends StatefulWidget { @override State createState() => _ModeTipsState(); } class _ModeTipsState extends State<_ModeTips> { IApplication? application; @override void initState() { Future.delayed(const Duration(milliseconds: 100), () { application = Get.find(); application!.visualsLoaded.addListener(_onVisualsLoaded); }); super.initState(); } @override void dispose() { application!.visualsLoaded.removeListener(_onVisualsLoaded); super.dispose(); } void _onVisualsLoaded(Object sender, void e) { setState(() {}); } @override Widget build(BuildContext context) { if (application == null) return const Text("Wait"); return Material( child: Text(application!.avaliableModes.map((e) => e.name).join('/')), ); } }