Browse Source

移动端支持更多测量项

gavin.chen 2 years ago
parent
commit
d7e15a405c

+ 1 - 1
lib/view/mobile_view/mobile_measure_main_view.dart

@@ -408,7 +408,7 @@ class _MobileMeasureMainViewState extends State<MobileMeasureMainView> {
                     LayoutId(
                       id: _LayerLayoutIds.result,
                       child: const SizedBox(
-                          height: 200, width: 150, child: MeasureResultPanel()),
+                          height: 200, child: MeasureResultPanel()),
                     ),
                     LayoutId(
                       id: _LayerLayoutIds.canvasMagnifier,

+ 187 - 30
lib/view/mobile_view/mobile_right_panel/mobile_measure_tool.dart

@@ -1,9 +1,14 @@
 import 'package:fis_common/logger/logger.dart';
 import 'package:fis_i18n/i18n.dart';
+import 'package:fis_jsonrpc/services/remedical.m.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/workspace/application.dart';
+import 'package:fis_measure/process/workspace/measure_data_controller.dart';
+import 'package:fis_measure/process/workspace/measure_handler.dart';
+import 'package:fis_measure/view/mobile_view/mobile_right_panel/mobile_more_measure_item_dialog.dart';
 import 'package:fis_ui/index.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
@@ -21,12 +26,19 @@ class MobileMeasureSelector extends FStatefulWidget {
 class _MobileMeasureSelector extends FState<MobileMeasureSelector> {
   late final application = Get.find<IApplication>();
 
+  /// 数据
+  late final measureData = Get.find<MeasureDataController>();
+  late final playerController = Get.find<IPlayerController>();
+
   /// 当前选中的测量项目
   String activeName = "";
 
-  /// 写死的移动端测量项
-  List<MobileMeasureBtn> mobileMeasureBtnList = [
-    MobileMeasureBtn(
+  /// 是否打开更多测量项列表
+  bool showMore = false;
+
+  /// 写死的移动端基础测量项
+  final List<MobileBaseMeasureItemBtn> _mobileBasicMeasureItemsList = [
+    MobileBaseMeasureItemBtn(
       icon: Icons.linear_scale,
       displayName: i18nBook.measure.distance.t,
       itemMeta: ItemMeta(
@@ -39,7 +51,7 @@ class _MobileMeasureSelector extends FState<MobileMeasureSelector> {
         ],
       ),
     ),
-    MobileMeasureBtn(
+    MobileBaseMeasureItemBtn(
       icon: Icons.border_style,
       displayName: i18nBook.measure.circumference.t,
       itemMeta: ItemMeta(
@@ -52,7 +64,7 @@ class _MobileMeasureSelector extends FState<MobileMeasureSelector> {
         ],
       ),
     ),
-    MobileMeasureBtn(
+    MobileBaseMeasureItemBtn(
       icon: Icons.format_shapes,
       displayName: i18nBook.measure.area.t,
       itemMeta: ItemMeta(
@@ -67,12 +79,17 @@ class _MobileMeasureSelector extends FState<MobileMeasureSelector> {
     ),
   ];
 
+  /// 是否已初始化完成所有测量项数据
+  bool _isInitAllMeasureItems = false;
+
+  /// 可用的模式 (测量项包含在内)
+  List<MobileMoreMeasureItemModesModel> _availableModes = [];
+
   /// 切换测量项
   void changeItem(ItemMeta itemMeta) {
     activeName = itemMeta.name;
     try {
       application.switchItem(itemMeta);
-      // application.switchItemByName(itemMeta.name);
     } catch (e) {
       logger.e("changeItem failed", e);
     }
@@ -85,36 +102,33 @@ class _MobileMeasureSelector extends FState<MobileMeasureSelector> {
   FWidget build(BuildContext context) {
     return FSizedBox(
       width: 100,
-      height: 320,
-      child: FColumn(
-          mainAxisAlignment: MainAxisAlignment.center,
-          mainAxisSize: MainAxisSize.max,
-          children: [
-            const FSizedBox(height: 30),
-            ...mobileMeasureBtnList.map((e) {
-              return _buildMeasureBtn(e.itemMeta, e.itemMeta.name == activeName,
-                  e.displayName, e.icon);
-            }).toList(),
-          ]),
+      child: _buildBaseMeasureItemPanel(),
+    );
+  }
+
+  /// 构建基础测量项面板
+  FWidget _buildBaseMeasureItemPanel() {
+    return FColumn(
+      mainAxisAlignment: MainAxisAlignment.center,
+      mainAxisSize: MainAxisSize.max,
+      children: [
+        ..._mobileBasicMeasureItemsList.map((e) {
+          return _buildBaseMeasureBtn(
+              e.itemMeta, e.itemMeta.name == activeName, e.displayName, e.icon);
+        }).toList(),
+        _buildChangeMoreMeasureItem(),
+      ],
     );
   }
 
-  FWidget _buildMeasureBtn(
+  /// 构建基础测量项按钮
+  FWidget _buildBaseMeasureBtn(
       ItemMeta itemMeta, bool ifActive, String displayName, IconData icon) {
     return FInkWell(
       onTap: () {
         HapticFeedback.mediumImpact();
         if (ifActive) {
-          changeItem(
-            ItemMeta(
-              "None",
-              measureType: "None",
-              description: "None",
-              outputs: [
-                ItemOutputMeta("None", "None", VidUsUnit.cm2),
-              ],
-            ),
-          );
+          _cancelCurrSelect();
         } else {
           changeItem(itemMeta);
         }
@@ -151,12 +165,155 @@ class _MobileMeasureSelector extends FState<MobileMeasureSelector> {
       ),
     );
   }
+
+  /// 构建打开更多测量项按钮
+  FWidget _buildChangeMoreMeasureItem() {
+    return FInkWell(
+      onTap: () async {
+        _cancelCurrSelect();
+        HapticFeedback.mediumImpact();
+        await _openMoreMeasureItemDialog();
+      },
+      child: Container(
+        margin: const EdgeInsets.fromLTRB(0, 10, 10, 10),
+        padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
+        decoration: BoxDecoration(
+          border: Border.all(color: Colors.transparent),
+          borderRadius: BorderRadiusGeometry.lerp(
+              BorderRadius.circular(10), BorderRadius.circular(10), 10),
+        ),
+        child: FColumn(
+          children: const [
+            FIcon(
+              Icons.format_list_bulleted,
+              color: Colors.grey,
+            ),
+            FText(
+              /// TODO: i18n
+              '更多',
+              style: TextStyle(
+                color: Colors.grey,
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  /// 打开更多测量项选择器
+  Future<void> _openMoreMeasureItemDialog() async {
+    List<MobileMoreMeasureItemModesModel> modes = [];
+    if (_isInitAllMeasureItems) {
+      modes = _availableModes;
+    } else {
+      modes = await _initMeasureItemsList();
+      _availableModes = modes;
+    }
+    final ItemMeta? result =
+        await Get.dialog<ItemMeta>(MobileMoreMeasureItemDialog(
+      measureModeList: modes,
+    ));
+    if (result != null) {
+      changeItem(result);
+      setState(() {
+        showMore = true;
+      });
+    }
+  }
+
+  /// 取消当前选中的测量项
+  void _cancelCurrSelect() {
+    changeItem(
+      ItemMeta(
+        "None",
+        measureType: "None",
+        description: "None",
+        outputs: [
+          ItemOutputMeta("None", "None", VidUsUnit.cm2),
+        ],
+      ),
+    );
+  }
+
+  /// 获取当前帧信息来初始化当前测量项
+  Future<List<MobileMoreMeasureItemModesModel>> _initMeasureItemsList() async {
+    final vidImage = playerController.currentFrame;
+    if (vidImage == null) return [];
+    List<String> getModes = [];
+    List<String> displayModeNames = [];
+    for (var element in vidImage.visuals[0].modes) {
+      getModes.add(element.type.toString().split('.')[1]);
+      displayModeNames.add(element.displayName);
+    }
+    measureData.applicationModes = vidImage.visuals[0].modes;
+    measureData.currentMode = vidImage.visuals[0].modes.first.type.name;
+    var measureModeSelection = MeasureModeSelection(
+      application.applicationName,
+      application.categoryName,
+      application.isThirdPart ? ['TPPTissue'] : getModes,
+    );
+
+    /// 请求数据
+    final MeasureApplicationDTO? measureApplicationDTO =
+        await measureData.getMeasureApplication.call(measureModeSelection);
+    if (measureApplicationDTO == null ||
+        measureApplicationDTO.availableModes == null) return [];
+
+    /// 遍历 measureApplicationDTO.availableModes 拼装出新的 supportedModes
+    List<MobileMoreMeasureItemModesModel> supportedModes = [];
+    for (int i = 0; i < measureApplicationDTO.availableModes!.length; i++) {
+      MeasureModeDTO mode = measureApplicationDTO.availableModes![i];
+      List<ItemMetaDTO> supportedItems = _currSupportedItemFilter(mode);
+      String modeName = displayModeNames.length > i ? displayModeNames[i] : '';
+      MobileMoreMeasureItemModesModel modeModel =
+          MobileMoreMeasureItemModesModel(
+              modeName: modeName, availableItems: supportedItems);
+      supportedModes.add(modeModel);
+    }
+    if (supportedModes.isNotEmpty) {
+      _isInitAllMeasureItems = true;
+    }
+    return supportedModes;
+  }
+
+  /// 过滤出当前支持的所有测量项
+  List<ItemMetaDTO> _currSupportedItemFilter(MeasureModeDTO mode) {
+    List<ItemMetaDTO> _supportedItems = [];
+    if (mode.availableGroups == null || mode.availableGroups!.isEmpty) {
+      return _supportedItems;
+    }
+    for (MeasureGroupDTO group in mode.availableGroups!) {
+      if (group.availableFolders == null || group.availableFolders!.isEmpty) {
+        continue;
+      }
+      for (MeasureFolderDTO folder in group.availableFolders!) {
+        if (folder.availableItems == null || folder.availableItems!.isEmpty) {
+          continue;
+        }
+        for (ItemMetaDTO items in folder.availableItems!) {
+          if (items.measureTypeName == "Distance") {
+            _supportedItems.add(items);
+          }
+        }
+      }
+    }
+    print("${mode.modeName} 模式下过滤出 ${_supportedItems.length} 项");
+    return _supportedItems;
+  }
 }
 
-class MobileMeasureBtn {
+class MobileBaseMeasureItemBtn {
   ItemMeta itemMeta;
   String displayName;
   IconData icon;
-  MobileMeasureBtn(
+  MobileBaseMeasureItemBtn(
       {required this.itemMeta, required this.displayName, required this.icon});
 }
+
+class MobileMoreMeasureItemModesModel {
+  String modeName;
+  List<ItemMetaDTO> availableItems;
+  MobileMoreMeasureItemModesModel(
+      {required this.modeName, required this.availableItems});
+}

+ 334 - 0
lib/view/mobile_view/mobile_right_panel/mobile_more_measure_item_dialog.dart

@@ -0,0 +1,334 @@
+import 'package:fis_common/logger/logger.dart';
+import 'package:fis_jsonrpc/services/remedical.m.dart';
+import 'package:fis_measure/interfaces/process/items/item_metas.dart';
+import 'package:fis_measure/process/items/item_meta_convert.dart';
+import 'package:fis_measure/view/mobile_view/mobile_right_panel/mobile_measure_tool.dart';
+import 'package:fis_ui/index.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:get/get.dart';
+
+class MobileMoreMeasureItemDialog extends FStatefulWidget {
+  const MobileMoreMeasureItemDialog({super.key, required this.measureModeList});
+  final List<MobileMoreMeasureItemModesModel> measureModeList;
+
+  @override
+  FState<MobileMoreMeasureItemDialog> createState() =>
+      _MobileMoreMeasureItemDialogState();
+}
+
+class _MobileMoreMeasureItemDialogState
+    extends FState<MobileMoreMeasureItemDialog> {
+  final scrollController = ScrollController();
+  final searchBarController = TextEditingController();
+
+  /// 当前模式的下标
+  int currentModeIndex = 0;
+
+  /// 当前模式下可用的测量项
+  List<ItemMetaDTO> currentModeMeasureItemList = [];
+
+  /// 当前可用的模式列表
+  List<String> currentModeNameList = [];
+
+  /// 搜索词
+  String searchWord = '';
+
+  /// 搜索结果
+  List<ItemMetaDTO> searchResult = [];
+
+  @override
+  void initState() {
+    super.initState();
+    // 遍历 measureModeList 获取到当前可用的模式列表,写入 currentModeNameList
+    for (final measureMode in widget.measureModeList) {
+      currentModeNameList.add(measureMode.modeName);
+    }
+    if (widget.measureModeList.isEmpty) {
+      return;
+    }
+    currentModeMeasureItemList =
+        widget.measureModeList[currentModeIndex].availableItems;
+    searchResult = currentModeMeasureItemList.toList();
+  }
+
+  @override
+  FWidget build(BuildContext context) {
+    return _buildMoreMeasureItemDialog();
+  }
+
+  /// 构建更多测量项弹窗容器
+  FWidget _buildMoreMeasureItemDialog() {
+    return FDialog(
+      // title: _buildDialogTitle(),
+      // titleColor: const Color.fromARGB(255, 36, 36, 36),
+      backgroundColor: const Color.fromARGB(255, 36, 36, 36),
+      // isDefault: false,
+      // onCancel: () {
+      //   Get.back();
+      // },
+      shape: const RoundedRectangleBorder(
+        borderRadius: BorderRadius.all(
+          Radius.circular(7),
+        ),
+      ),
+      clipBehavior: Clip.hardEdge,
+      child: FColumn(
+        mainAxisSize: MainAxisSize.min,
+        children: [
+          _buildDialogTitle(),
+          FContainer(
+            color: const Color.fromARGB(255, 36, 36, 36),
+            width: Get.width,
+            height: Get.height * 0.6,
+            child: FContainer(
+              child: _buildMoreMeasureItemList(),
+            ),
+          ),
+          const FSizedBox(
+            height: 10,
+          )
+        ],
+      ),
+      // bottomButton: FContainer(
+      //   height: 20,
+      //   decoration: const BoxDecoration(
+      //     color: Color.fromARGB(255, 36, 36, 36),
+      //     borderRadius: BorderRadius.only(
+      //       bottomLeft: Radius.circular(7),
+      //       bottomRight: Radius.circular(7),
+      //     ),
+      //   ),
+      // ),
+    );
+  }
+
+  FWidget _buildDialogTitle() {
+    return FPadding(
+      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+      child: FRow(
+        children: [
+          const FText(
+            /// TODO: i18n
+            "更多测量项",
+            style: TextStyle(
+              color: Color.fromARGB(255, 220, 220, 220),
+              fontSize: 16,
+            ),
+          ),
+          const FSizedBox(
+            width: 20,
+          ),
+          _buildModeSelector(currentModeNameList),
+          FExpanded(
+            child: _buildSearchBar(),
+          ),
+          FInkWell(
+            onTap: () => Get.back(),
+            child: const FIcon(
+              Icons.close,
+              color: Colors.white,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  /// 构建更多测量项列表
+  FWidget _buildMoreMeasureItemList() {
+    if (currentModeMeasureItemList.isEmpty) {
+      return FContainer(
+        color: const Color.fromARGB(255, 36, 36, 36),
+        child: const FCenter(
+          child: FText(
+            /// TODO: i18n
+            "此模式下暂无支持的测量项",
+            style: TextStyle(
+              color: Colors.grey,
+              fontSize: 16,
+            ),
+          ),
+        ),
+      );
+    }
+    if (searchResult.isEmpty) {
+      return FContainer(
+        color: const Color.fromARGB(255, 36, 36, 36),
+        child: const FCenter(
+          child: FText(
+            /// TODO: i18n
+            "搜不到相关测量项",
+            style: TextStyle(
+              color: Colors.grey,
+              fontSize: 16,
+            ),
+          ),
+        ),
+      );
+    }
+    return FContainer(
+      color: const Color.fromARGB(255, 36, 36, 36),
+      child: FScrollbar(
+        controller: scrollController,
+        isAlwaysShown: true,
+        child: FGridView.count(
+          padding: const EdgeInsets.all(10),
+          controller: scrollController,
+          crossAxisCount: 6,
+          childAspectRatio: 3,
+          mainAxisSpacing: 5,
+          crossAxisSpacing: 5,
+          children: [
+            ...searchResult.map((e) {
+              return _buildMeasureListItem(
+                e,
+              );
+            }).toList()
+          ],
+        ),
+      ),
+    );
+  }
+
+  /// 构建更多测量项列表中的测量项按钮
+  FWidget _buildMeasureListItem(
+    ItemMetaDTO itemMeta,
+  ) {
+    return FInkWell(
+      onTap: () {
+        HapticFeedback.mediumImpact();
+        ItemMeta selectedItemMeta;
+        try {
+          selectedItemMeta = ItemMetaConverter(itemMeta).output();
+          Get.back<ItemMeta>(result: selectedItemMeta);
+        } catch (e) {
+          logger.e(
+            "Item meta -[${itemMeta.name}] convert error; JSON-Text: ${itemMeta.toJson()}",
+            e,
+          );
+        }
+      },
+      child: Container(
+        decoration: BoxDecoration(
+          border: Border.all(color: const Color.fromARGB(255, 71, 71, 71)),
+          borderRadius: BorderRadius.circular(4),
+        ),
+        child: FCenter(
+          child: FText(
+            itemMeta.name ?? '',
+            style: const TextStyle(
+              fontSize: 12,
+              color: Colors.grey,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  /// 构建模式选择按钮
+  FWidget _buildModeSelector(List<String> currentModeNameList) {
+    return FRow(
+      mainAxisAlignment: MainAxisAlignment.center,
+      children: currentModeNameList
+          .asMap()
+          .entries
+          .map<FWidget>(
+            (entry) => FInkWell(
+              onTap: () => _changeCurrModeByIndex(entry.key),
+              child: FContainer(
+                constraints: const BoxConstraints(
+                  minWidth: 40,
+                ),
+                margin: const EdgeInsets.symmetric(horizontal: 5),
+                padding: const EdgeInsets.all(5),
+                decoration: BoxDecoration(
+                  border: entry.key == currentModeIndex
+                      ? Border.all(color: Colors.white)
+                      : Border.all(
+                          color: const Color.fromRGBO(124, 124, 124, 1)),
+                  borderRadius: BorderRadius.circular(5),
+                ),
+                child: FCenter(
+                  child: FText(
+                    entry.value,
+                    style: TextStyle(
+                      fontSize: 16,
+                      color: entry.key == currentModeIndex
+                          ? Colors.white
+                          : Colors.grey,
+                    ),
+                  ),
+                ),
+              ),
+            ),
+          )
+          .toList(),
+    );
+  }
+
+  FWidget _buildSearchBar() {
+    return FContainer(
+      height: 40,
+      margin: const EdgeInsets.symmetric(horizontal: 10),
+      decoration: BoxDecoration(
+        color: const Color.fromARGB(255, 36, 36, 36),
+        borderRadius: BorderRadius.circular(5),
+      ),
+      child: FRow(
+        children: [
+          const FSizedBox(
+            width: 30,
+          ),
+          const FIcon(
+            Icons.search,
+            color: Colors.grey,
+          ),
+          const FSizedBox(
+            width: 10,
+          ),
+          FExpanded(
+            child: FTextField(
+              controller: searchBarController,
+              style: const TextStyle(
+                color: Colors.white,
+              ),
+              decoration: const InputDecoration(
+                /// TODO: i18n
+                hintText: "搜索",
+                hintStyle: TextStyle(
+                  color: Colors.grey,
+                ),
+                border: InputBorder.none,
+              ),
+              onChanged: (value) {
+                _searchMeasureItem(value);
+              },
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  void _searchMeasureItem(String value) {
+    // 转换为小写以忽略大小写差异
+    value = value.toLowerCase();
+    searchResult.clear(); // 清除任何现有的搜索结果
+    for (ItemMetaDTO itemMeta in currentModeMeasureItemList) {
+      if (itemMeta.name!.toLowerCase().contains(value)) {
+        searchResult.add(itemMeta);
+      }
+    }
+    setState(() {});
+  }
+
+  void _changeCurrModeByIndex(int index) {
+    setState(() {
+      currentModeIndex = index;
+      currentModeMeasureItemList =
+          widget.measureModeList[currentModeIndex].availableItems;
+    });
+  }
+}

+ 3 - 0
lib/view/mobile_view/widgets/magnifier.dart

@@ -75,6 +75,9 @@ class _CanvasMagnifierState extends State<CanvasMagnifier> {
   }
 
   void _getPositions() {
+    if (_magnifierKey.currentContext == null) {
+      return;
+    }
     final RenderBox renderBoxRed =
         _magnifierKey.currentContext!.findRenderObject() as RenderBox;
     magnifierCenter = renderBoxRed.localToGlobal(