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/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/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/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/view/measure/measure_main_view.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; class MeasureDataTester { static Future> getRemedicalList( String patientCode, String recordCode, String token, ) async { return []; } static Future getImageInfo( String remedicalCode, String token, ) async { return null; } static Future getMeasureApplication( dynamic args) async { return null; } static Future saveUserDefinedMeasureApplicationAsync( dynamic args) async { return null; } static Future saveImage( Uint8List imageBytes, String patientCode, String recordCode, String remedicalCode, String measuredData, ) 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 saveUserDefinedCommentsAsync(String applicationName, String categoryName, List commentItems) async { return null; } } class MeasureTestPage extends StatefulWidget { const MeasureTestPage({Key? key}) : super(key: key); @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/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, )); 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() { 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: const MeasureMainView(), ); } } 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 = [ MeasureTypes.Distance, MeasureTypes.Perimeter, MeasureTypes.Area, MeasureTypes.Depth, MeasureTypes.Volume, ]; final scrollController = ScrollController(); final application = Get.find(); int activeIndex = 0; @override void initState() { application.switchItemByName(C_SUPPORTED_ITEMS[0]); application.canMeasureChanged.addListener(_onCanMeasureChanged); super.initState(); } @override dispose() { application.canMeasureChanged.removeListener(_onCanMeasureChanged); super.dispose(); } _onCanMeasureChanged(Object sender, bool e) { if (e && mounted) { changeItem(0); } } @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_ITEMS.length, itemBuilder: (BuildContext context, int index) { final name = C_SUPPORTED_ITEMS[index]; final active = index == activeIndex; return active ? ElevatedButton( onPressed: () => changeItem(index), child: Text(name), style: ElevatedButton.styleFrom( fixedSize: const Size.fromHeight(50), ), ) : OutlinedButton( onPressed: () => changeItem(index), child: Text(name), style: OutlinedButton.styleFrom( fixedSize: const Size.fromHeight(50), ), ); }, separatorBuilder: (BuildContext context, int index) { return const SizedBox(height: 8); }, ), ), ); } void buildItems() { final count = C_SUPPORTED_ITEMS.length; final children = []; for (var i = 0; i < count; i++) { final name = C_SUPPORTED_ITEMS[i]; final active = i == activeIndex; final widget = active ? ElevatedButton( onPressed: () => changeItem(i), child: Text(name), style: ElevatedButton.styleFrom( fixedSize: const Size.fromHeight(50), ), ) : OutlinedButton( onPressed: () => changeItem(i), child: Text(name), style: OutlinedButton.styleFrom( fixedSize: const Size.fromHeight(50), ), ); if (i > 0) { children.add(const SizedBox(height: 8)); } children.add(widget); } } void changeItem(int index) { setState(() { activeIndex = index; }); final name = C_SUPPORTED_ITEMS[index]; application.switchItemByName(name); } } 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(seconds: 2), () { application = Get.find(); application!.visualsLoaded.addListener(_onVisualsLoaded); if (mounted) { setState(() {}); } }); 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('/')), ); } }