showModalDailog<T> static method

Future<T?> showModalDailog<T>(
  1. BuildContext rootContext,
  2. Widget dialog, {
  3. bool barrierDismissible = true,
  4. bool useSafeArea = true,
  5. bool useRootNavigator = false,
  6. required String dialogKey,
  7. Duration transitionDuration = const Duration(milliseconds: 300),
  8. DialogAnimationType animationType = DialogAnimationType.scale,
  9. Color barrierColor = Colors.black54,
  10. bool isSlideDialog = false,
  11. SlideDialogDirection slideDirection = SlideDialogDirection.fromRight,
})

显示模态对话框 rootContext 上下文 dialog 对话框 barrierDismissible 是否可以点击背景关闭 useSafeArea 是否使用安全区域 useRootNavigator 是否使用根导航器 transitionDuration 动画时长 animationType 动画类型 isSlideDialog 是否是滑动对话框(竖屏从底部滑入,横屏从侧边滑入) slideDirection 滑动方向(仅在横屏且isSlideDialog=true时生效) @return 返回对话框的唯一key

Implementation

static Future<T?> showModalDailog<T>(
  BuildContext rootContext,
  Widget dialog, {
  bool barrierDismissible = true,
  bool useSafeArea = true,
  bool useRootNavigator = false,
  required String dialogKey,
  Duration transitionDuration = const Duration(milliseconds: 300),
  DialogAnimationType animationType = DialogAnimationType.scale,
  Color barrierColor = Colors.black54,
  bool isSlideDialog = false,
  SlideDialogDirection slideDirection = SlideDialogDirection.fromRight,
}) async {
  TCICLog.info(
    'showModalDailog dialog: ${dialog.toStringShort()}, key: $dialogKey, animation: $animationType',
    actionModule: ActionModule.tools.name,
    actionName: ActionName.showModalDailog.name,
  );

  // 使用 Completer 来处理异步返回值
  final completer = Completer<T?>();

  // 生成当前弹窗实例的唯一ID
  final instanceId =
      '${dialogKey}_${DateTime.now().millisecondsSinceEpoch}_${_currentMaxLevel}';

  // 使用 addPostFrameCallback 确保在正确的时机执行
  // runAfterInit(() async {
  try {
    // 只在第一个弹窗打开时隐藏floating
    if (_currentMaxLevel == 0) {
      hideAllFloating();
    }
    // 为当前弹窗分配层级
    _currentMaxLevel++;

    // 初始化列表(如果不存在)
    _dialogContexts.putIfAbsent(dialogKey, () => []);
    _dialogLevels.putIfAbsent(dialogKey, () => []);
    _dialogInstanceIds.putIfAbsent(dialogKey, () => []);

    // 添加当前实例ID到列表
    _dialogInstanceIds[dialogKey]!.add(instanceId);
    _dialogLevels[dialogKey]!.add(_currentMaxLevel);

    // 提前获取屏幕尺寸,避免在动画过程中访问可能失效的context
    final screenSize = MediaQuery.of(rootContext).size;
    final isPortrait =
        MediaQuery.of(rootContext).orientation == Orientation.portrait;
    dynamic res;
    if (isSlideDialog) {
      if (isPortrait) {
        // 竖屏模式:使用 showModalBottomSheet
        res = await showModalBottomSheet<T>(
          context: rootContext,
          backgroundColor: Colors.transparent,
          isScrollControlled: true,
          builder: (context) {
            // 将新的context添加到列表中
            _dialogContexts[dialogKey]!.add(context);
            return ClipRRect(
              borderRadius: const BorderRadius.only(
                topLeft: Radius.circular(12),
                topRight: Radius.circular(12),
              ),
              child: Container(
                width: screenSize.width,
                height: screenSize.height * 0.8,
                color: Theme.of(rootContext).colorScheme.secondary,
                child: Theme(data: Theme.of(rootContext), child: dialog),
              ),
            );
          },
        );
      } else {
        // 横屏模式:使用 showGeneralDialog 从侧边滑入
        // 根据 slideDirection 确定对齐方式和圆角
        final isFromRight = slideDirection == SlideDialogDirection.fromRight;
        final alignment =
            isFromRight ? Alignment.centerRight : Alignment.centerLeft;
        final borderRadius =
            isFromRight
                ? const BorderRadius.only(
                  topLeft: Radius.circular(12),
                  bottomLeft: Radius.circular(12),
                )
                : const BorderRadius.only(
                  topRight: Radius.circular(12),
                  bottomRight: Radius.circular(12),
                );

        res = await showGeneralDialog<T>(
          context: rootContext,
          barrierDismissible: false,
          barrierLabel: "Dismiss",
          barrierColor: Colors.black54,
          transitionDuration: Duration(milliseconds: 300),
          pageBuilder: (context, animation1, animation2) {
            // 将新的context添加到列表中
            _dialogContexts[dialogKey]!.add(context);
            return PointerInterceptor(
              child: GestureDetector(
                onTap: () => Navigator.of(context).pop(),
                behavior: HitTestBehavior.opaque,
                child: Container(
                  color: Colors.transparent,
                  child: Align(
                    alignment: alignment,
                    child: GestureDetector(
                      onTap: () {},
                      behavior: HitTestBehavior.opaque,
                      child: Theme(
                        data: Theme.of(rootContext),
                        child: ClipRRect(
                          borderRadius: borderRadius,
                          child: Material(
                            color:
                                Theme.of(rootContext).colorScheme.secondary,
                            child: SizedBox(
                              width: screenSize.width * 0.5,
                              height: screenSize.height,
                              child: dialog,
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            );
          },
          transitionBuilder: (context, animation1, animation2, child) {
            // 根据方向计算偏移量
            final curvedAnimation = Curves.easeOut.transform(
              animation1.value,
            );
            final offsetX =
                isFromRight
                    ? screenSize.width *
                        0.5 *
                        (1 - curvedAnimation) // 从右往左
                    : -screenSize.width * 0.5 * (1 - curvedAnimation); // 从左往右
            return Transform.translate(
              offset: Offset(offsetX, 0),
              child: child,
            );
          },
        );
      }
      // 对话框关闭后,从存储中移除对应的实例
      _cleanupDialogInstance(dialogKey, instanceId);
      // 只在所有弹窗都关闭时才显示floating
      if (_currentMaxLevel == 0) {
        showAllFloating();
      }

      completer.complete(res);
    } else {
      runAfterInit(() async {
        res = await showGeneralDialog<T?>(
          context: rootContext,
          barrierDismissible: barrierDismissible,
          barrierLabel: '',
          barrierColor: barrierColor,
          transitionDuration: transitionDuration,
          pageBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
          ) {
            // 将新的context添加到列表中
            _dialogContexts[dialogKey]!.add(context);
            return Theme(
              data: TCICController.instance.getThemeObs().theme,
              child: dialog,
            );
          },
          transitionBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
            Widget child,
          ) {
            return _buildAnimation(animation, child, animationType);
          },
          // useRootNavigator: useRootNavigator,
        );
        // 对话框关闭后,从存储中移除对应的实例
        _cleanupDialogInstance(dialogKey, instanceId);
        // 只在所有弹窗都关闭时才显示floating
        if (_currentMaxLevel == 0) {
          showAllFloating();
        }

        completer.complete(res);
      });
    }
  } catch (e) {
    completer.completeError(e);
  }
  // });

  return completer.future;
}