import 'dart:async'; import 'package:fis_i18n/i18n.dart'; import 'package:fis_measure/interfaces/process/player/play_controller.dart'; import 'package:fis_measure/interfaces/process/workspace/application.dart'; import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart'; import 'package:fis_measure/utils/prompt_box.dart'; import 'package:fis_measure/view/measure/carotid_measure_tool.dart'; import 'package:fis_ui/index.dart'; import 'package:flutter/material.dart' hide Image; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:vid/us/vid_us_image.dart'; import 'package:webviewx/webviewx.dart'; import 'dart:convert'; import 'package:fis_measure/utils/js_utils.dart' if (dart.library.io) 'package:fis_measure/utils/js_utils4native.dart' if (dart.library.html) 'package:fis_measure/utils/js_utils.dart'; /// [Carotid] 🚧webview 下 initPage 方法需要 i18n 由 Flutter 触发 /// [Carotid] ✅启用 3D 后的亮度对比度需要通知 Shell 计算 /// [Carotid] ✅需要接入 webview 控制器 /// [Carotid] ✅需要判断是否为壳子,浏览器环境无 3D 操作 /// class Measure3DView extends StatefulWidget implements FWidget { const Measure3DView({ Key? key, }) : super(key: key); @override _Measure3DViewState createState() => _Measure3DViewState(); } class _Measure3DViewState extends State { late WebViewXController webviewController; final measure3DViewController = Get.find(); final application = Get.find(); Size get screenSize => MediaQuery.of(context).size; String documentFromAsset = "

Hello webviewX !

"; Future _preloadHTML() async { documentFromAsset = await _loadHTMLFromAssets( 'assets/webview/3DMeasurePage.html', ); return Future.value(""); } Future _loadHTMLFromAssets(String path) async { try { return await DefaultAssetBundle.of(context).loadString(path); } catch (e) { return Future.error(e); } } @override Widget build(BuildContext context) { return Column( children: [ Expanded( child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return _buildWebViewX(constraints); }), ), SizedBox( height: 30, child: Container( child: measure3DViewController.enable2DMeasure ? ElevatedButton( onPressed: () { _changeTo2DMeasure(); }, child: Text(i18nBook.measure.measureSpecifiedFace.t), ) : Container(), ), ), ], ); } @override void initState() { super.initState(); measure3DViewController.onShellLoadedMdlFile.addListener(_mdlFileLoaded); measure3DViewController.adjustPlaneImage.addListener(_adjustedImage); } @override void dispose() { measure3DViewController.onShellLoadedMdlFile.removeListener(_mdlFileLoaded); measure3DViewController.adjustPlaneImage.removeListener(_adjustedImage); webviewController.dispose(); super.dispose(); } Widget _buildWebViewX(BoxConstraints constraints) { return FutureBuilder( future: _preloadHTML(), builder: (context, AsyncSnapshot snapshot) { if (snapshot.hasData) { return WebViewX( key: const ValueKey('webviewx'), initialContent: documentFromAsset, initialSourceType: SourceType.html, height: constraints.maxHeight, width: constraints.maxWidth, onWebViewCreated: (controller) => webviewController = controller, onPageStarted: (src) => debugPrint('A new page has started loading'), onPageFinished: (src) { debugPrint('The page has finished loading'); _setSurface().then((value) => _notifyShellToLoad()); }, jsContent: { EmbeddedJsContent( js: "var isCurrentChinese = ${i18nBook.isCurrentChinese}") }, dartCallBacks: { DartCallback( name: 'Dart_getClipPlaneData', callBack: (msg) => callShellGetClipPlaneData(msg)), DartCallback( name: 'Dart_deleteClipImageData', callBack: (msg) => _deleteClipImageData(msg), ), DartCallback( name: "Dart_deleteAllClipImageData", callBack: (msg) => _deleteAllClipImageData(msg), ), DartCallback( name: "Dart_meshActiveStatusChanged", callBack: (boolValue) => _meshActiveStatusChanged(boolValue), ), DartCallback( name: "Dart_aiClipStateCallBack", callBack: (boolValue) => _aiClipStateCallBack(boolValue), ), DartCallback( name: "Dart_getVesselClipPlanePoints", callBack: (msg) => callShellGetVesselClipPlanePoints(msg), ), }, webSpecificParams: const WebSpecificParams( printDebugInfo: true, ), mobileSpecificParams: const MobileSpecificParams( androidEnableHybridComposition: true, ), navigationDelegate: (navigation) { debugPrint(navigation.content.sourceType.toString()); return NavigationDecision.navigate; }, ); } else { return const Center(child: CircularProgressIndicator()); } }); } void _deleteClipImageData(String msg) { // debugPrint("webview 触发 Dart_deleteClipImageData :$msg"); callShellMethod('deleteClipImageData', [msg]); } void _deleteAllClipImageData(msg) { // debugPrint("webview 触发 Dart_deleteAllClipImageData :$msg"); callShellMethod('deleteAllClipImageData', []); } /// [Carotid] ✅触发激活状态更新,来判断是否显示 [切换2D测量] 按钮 void _meshActiveStatusChanged(bool res) { debugPrint("webview 触发 Dart_meshActiveStatusChanged :$res"); callShellMethod('meshActiveStatusChanged', [res]); measure3DViewController.enable2DMeasure = res; setState(() {}); } /// AI 切割成功的回调 void _aiClipStateCallBack(bool success) async { if (success) { measure3DViewController.needAutoDetection = true; // callShellMethod('aiClipStateCallBack', [res]); measure3DViewController.resetTone(); } } ///[Carotid] ✅初始化 mesh 长宽高可以由两张小图计算得到,设置标准宽度用于缩放切面 Future _setSurface() async { final faceImages = measure3DViewController.carotidResult.surfaceImageList; if (faceImages!.length == 6) { final first = await loadImageSize(faceImages[0]); final last = await loadImageSize(faceImages[5]); final width = (last.width - 1).toInt(); final height = (last.height - 1).toInt(); final depth = (first.height - 1).toInt(); measure3DViewController.stdSize = Size(width.toDouble(), depth.toDouble()); try { await webviewController.callJsMethod( 'changeSurface', [width, height, depth, ...faceImages]); } catch (e) { debugPrint("callmethod fail " + e.toString()); } } } /// [Carotid] ✅获取到图像并进入 2D 测量页 Future _changeTo2DMeasure() async { try { String res = await webviewController.callJsMethod('getCurrentClipInformation', []); callShellSwitchMeasureMode( res, measure3DViewController.curImageAdjustPara) .callMethod("then", [ (res) async { VidUsImage vidUsImage = VidUsImage.fromBytes(base64Decode(res)); /// [Carotid] ✅写入单帧缓存 final playerController = Get.find(); playerController.pause(); measure3DViewController.image4Measure = vidUsImage; measure3DViewController.changeModeTo2DMeasure(); application.clearRecords(); if (measure3DViewController.needAutoDetection) { Future.delayed(400.milliseconds, () { /// 内膜检测 final intimaDetection = carotidMeasureApplicationList.last; application.switchItem(intimaDetection); PromptBox.snackbar(i18nBook.measure.autoRunIntimaDetection.t, duration: const Duration(milliseconds: 1500), title: i18nBook.common.tip.t, textColor: Colors.white, backgroundColor: Colors.black.withOpacity(0.7)); Future.delayed(400.milliseconds, () { final firstItem = carotidMeasureApplicationList.first; application.switchItem(firstItem); }); }); measure3DViewController.needAutoDetection = false; } } ]); } catch (e) { debugPrint("callmethod fail " + e.toString()); } } /// [Carotid] ✅_mdlFileLoaded 由壳子触发,通知 webview 模型已加载,显示操作按钮 void _mdlFileLoaded(Object s, dynamic e) { try { webviewController.callJsMethod('mdlFileLoaded', ["Carotid", "True"]); } catch (e) { debugPrint("callmethod fail " + e.toString()); } } /// [Carotid] ✅_adjustedImage 由 Flutter 触发,通知 webview 更新所有的面数据 void _adjustedImage(Object s, ImageAdjustPara e) { try { webviewController.callJsMethod( 'adjustedImageByFlutter', [e.sharpness, e.brightness, e.contrast]); } catch (e) { debugPrint("callmethod fail " + e.toString()); } } /// webview 初始化完成后通知壳子切换至对应模型文件 Future _notifyShellToLoad() async { measure3DViewController.notifyShellSetCurModel(); } }