measure_3d_view.dart 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import 'dart:async';
  2. import 'package:fis_measure/interfaces/process/player/play_controller.dart';
  3. import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart';
  4. import 'package:fis_ui/index.dart';
  5. import 'package:flutter/material.dart' hide Image;
  6. import 'package:flutter/services.dart';
  7. import 'package:get/get.dart';
  8. import 'package:vid/us/vid_us_image.dart';
  9. import 'package:webviewx/webviewx.dart';
  10. import 'dart:convert';
  11. import 'package:fis_measure/utils/js_utils.dart'
  12. if (dart.library.io) 'package:fis_measure/utils/js_utils4native.dart'
  13. if (dart.library.html) 'package:fis_measure/utils/js_utils.dart';
  14. /// [Carotid] 🚧webview 下 initPage 方法需要 i18n 由 Flutter 触发
  15. /// [Carotid] 🚧启用 3D 后的亮度对比度需要通知 Shell 计算
  16. /// [Carotid] ✅需要接入 webview 控制器
  17. /// [Carotid] ✅需要判断是否为壳子,浏览器环境无 3D 操作
  18. ///
  19. class Measure3DView extends StatefulWidget implements FWidget {
  20. const Measure3DView({
  21. Key? key,
  22. }) : super(key: key);
  23. @override
  24. _Measure3DViewState createState() => _Measure3DViewState();
  25. }
  26. class _Measure3DViewState extends State<Measure3DView> {
  27. late WebViewXController webviewController;
  28. final measure3DViewController = Get.find<Measure3DViewController>();
  29. Size get screenSize => MediaQuery.of(context).size;
  30. String documentFromAsset = "<h2> Hello webviewX ! <h2>";
  31. Future<String> _preloadHTML() async {
  32. documentFromAsset = await _loadHTMLFromAssets(
  33. 'assets/webview/3DMeasurePage.html',
  34. );
  35. return Future.value("");
  36. }
  37. Future<String> _loadHTMLFromAssets(String path) async {
  38. try {
  39. return await rootBundle.loadString(path);
  40. } catch (e) {
  41. return Future.error(e);
  42. }
  43. }
  44. @override
  45. Widget build(BuildContext context) {
  46. return Column(
  47. children: <Widget>[
  48. Expanded(
  49. child: LayoutBuilder(
  50. builder: (BuildContext context, BoxConstraints constraints) {
  51. return _buildWebViewX(constraints);
  52. }),
  53. ),
  54. SizedBox(
  55. height: 30,
  56. child: Container(
  57. child: measure3DViewController.enable2DMeasure
  58. ? ElevatedButton(
  59. onPressed: () {
  60. _changeTo2DMeasure();
  61. },
  62. ///TODO: [Gavin] i18n
  63. child: const Text('使用选中面进入 2D 测量'),
  64. )
  65. : Container(),
  66. ),
  67. ),
  68. ],
  69. );
  70. }
  71. @override
  72. void initState() {
  73. super.initState();
  74. measure3DViewController.onShellLoadedMdlFile.addListener(_mdlFileLoaded);
  75. measure3DViewController.adjustPlaneImage.addListener(_adjustedImage);
  76. }
  77. @override
  78. void dispose() {
  79. measure3DViewController.onShellLoadedMdlFile.removeListener(_mdlFileLoaded);
  80. measure3DViewController.adjustPlaneImage.removeListener(_adjustedImage);
  81. webviewController.dispose();
  82. super.dispose();
  83. }
  84. Widget _buildWebViewX(BoxConstraints constraints) {
  85. return FutureBuilder<String>(
  86. future: _preloadHTML(),
  87. builder: (context, AsyncSnapshot<String> snapshot) {
  88. if (snapshot.hasData) {
  89. return WebViewX(
  90. key: const ValueKey('webviewx'),
  91. initialContent: documentFromAsset,
  92. initialSourceType: SourceType.html,
  93. height: constraints.maxHeight,
  94. width: constraints.maxWidth,
  95. onWebViewCreated: (controller) => webviewController = controller,
  96. onPageStarted: (src) =>
  97. debugPrint('A new page has started loading'),
  98. onPageFinished: (src) {
  99. debugPrint('The page has finished loading');
  100. _setSurface().then((value) => _notifyShellToLoad());
  101. },
  102. // jsContent: _loadJSFromAssets('assets/js/webviewx.js'),
  103. dartCallBacks: {
  104. DartCallback(
  105. name: 'Dart_getClipPlaneData',
  106. callBack: (msg) => callShellGetClipPlaneData(msg)),
  107. DartCallback(
  108. name: 'Dart_deleteClipImageData',
  109. callBack: (msg) => _deleteClipImageData(msg),
  110. ),
  111. DartCallback(
  112. name: "Dart_deleteAllClipImageData",
  113. callBack: (msg) => _deleteAllClipImageData(msg),
  114. ),
  115. DartCallback(
  116. name: "Dart_meshActiveStatusChanged",
  117. callBack: (boolValue) => _meshActiveStatusChanged(boolValue),
  118. ),
  119. DartCallback(
  120. name: "Dart_aiClipStateCallBack",
  121. callBack: (boolValue) => _aiClipStateCallBack(boolValue),
  122. ),
  123. DartCallback(
  124. name: "Dart_getVesselClipPlanePoints",
  125. callBack: (msg) => callShellGetVesselClipPlanePoints(msg),
  126. ),
  127. },
  128. webSpecificParams: const WebSpecificParams(
  129. printDebugInfo: true,
  130. ),
  131. mobileSpecificParams: const MobileSpecificParams(
  132. androidEnableHybridComposition: true,
  133. ),
  134. navigationDelegate: (navigation) {
  135. debugPrint(navigation.content.sourceType.toString());
  136. return NavigationDecision.navigate;
  137. },
  138. );
  139. } else {
  140. return const Center(child: CircularProgressIndicator());
  141. }
  142. });
  143. }
  144. void _deleteClipImageData(String msg) {
  145. // debugPrint("webview 触发 Dart_deleteClipImageData :$msg");
  146. callShellMethod('deleteClipImageData', [msg]);
  147. }
  148. void _deleteAllClipImageData(msg) {
  149. // debugPrint("webview 触发 Dart_deleteAllClipImageData :$msg");
  150. callShellMethod('deleteAllClipImageData', []);
  151. }
  152. /// [Carotid] ✅触发激活状态更新,来判断是否显示 "切换2D测量" 按钮
  153. void _meshActiveStatusChanged(bool res) {
  154. debugPrint("webview 触发 Dart_meshActiveStatusChanged :$res");
  155. callShellMethod('meshActiveStatusChanged', [res]);
  156. measure3DViewController.enable2DMeasure = res;
  157. setState(() {});
  158. }
  159. /// AI 切割成功的回调
  160. void _aiClipStateCallBack(bool res) async {
  161. debugPrint("webview 触发 Dart_aiClipStateCallBack :$res");
  162. // callShellMethod('aiClipStateCallBack', [res]);
  163. measure3DViewController.resetTone();
  164. }
  165. ///[Carotid] ✅初始化 mesh 长宽高可以由两张小图计算得到
  166. Future<void> _setSurface() async {
  167. final faceImages = measure3DViewController.carotidResult.surfaceImageList;
  168. if (faceImages!.length == 6) {
  169. final first = await loadImageSize(faceImages[0]);
  170. final last = await loadImageSize(faceImages[5]);
  171. final width = (last.width - 1).toInt();
  172. final height = (last.height - 1).toInt();
  173. final depth = (first.height - 1).toInt();
  174. try {
  175. await webviewController.callJsMethod(
  176. 'changeSurface', [width, height, depth, ...faceImages]);
  177. } catch (e) {
  178. debugPrint("callmethod fail " + e.toString());
  179. }
  180. }
  181. }
  182. /// [Carotid] ✅获取到图像并进入 2D 测量页
  183. Future<void> _changeTo2DMeasure() async {
  184. try {
  185. String res =
  186. await webviewController.callJsMethod('getCurrentClipInformation', []);
  187. callShellSwitchMeasureMode(
  188. res, measure3DViewController.curImageAdjustPara)
  189. .callMethod("then", [
  190. (res) async {
  191. VidUsImage vidUsImage = VidUsImage.fromBytes(base64Decode(res));
  192. /// [Carotid] ✅写入单帧缓存
  193. final playerController = Get.find<IPlayerController>();
  194. playerController.pause();
  195. measure3DViewController.image4Measure = vidUsImage;
  196. measure3DViewController.changeModeTo2DMeasure();
  197. }
  198. ]);
  199. } catch (e) {
  200. debugPrint("callmethod fail " + e.toString());
  201. }
  202. }
  203. /// [Carotid] ✅_mdlFileLoaded 由壳子触发,通知 webview 模型已加载,显示操作按钮
  204. void _mdlFileLoaded(Object s, dynamic e) {
  205. try {
  206. webviewController.callJsMethod('mdlFileLoaded', ["Carotid", "True"]);
  207. } catch (e) {
  208. debugPrint("callmethod fail " + e.toString());
  209. }
  210. }
  211. /// [Carotid] ✅_adjustedImage 由 Flutter 触发,通知 webview 更新所有的面数据
  212. void _adjustedImage(Object s, ImageAdjustPara e) {
  213. try {
  214. webviewController.callJsMethod(
  215. 'adjustedImageByFlutter', [e.sharpness, e.brightness, e.contrast]);
  216. } catch (e) {
  217. debugPrint("callmethod fail " + e.toString());
  218. }
  219. }
  220. /// webview 初始化完成后通知壳子切换至对应模型文件
  221. Future<void> _notifyShellToLoad() async {
  222. measure3DViewController.notifyShellSetCurModel();
  223. }
  224. }