import 'package:flutter/material.dart'; import 'dialog_bean.dart'; import 'navigator_manager.dart'; /// @desc 统一弹窗(包含 showDialog、showModalBottomSheet) class DialogManager { // 工厂模式 : 单例公开访问点 factory DialogManager() => _getInstance()!; // static DialogManager get instance => _getInstance(); // 静态私有成员,没有初始化 static DialogManager? _instance; ///弹窗数据(待显示) late List _dialogList; // ///当前正在显示的弹窗 // DialogBean showDialogBean; ///已显示过或正在显示的弹窗数据,还未被回调清除 ///已经显示过的弹窗,不能用一个DialogBean ///(因为showDialogBean在对话 pop 是会赋为空,一个正要显示,一个取消的回调过来又重新给设为空了。) late List _hasShowDialogList; ///是否有系统弹窗正在显示,例如系统权限申请弹窗 late bool isSystemDialogShowing; // 私有构造函数 DialogManager._internal() { // 初始化 _dialogList = []; _hasShowDialogList = []; isSystemDialogShowing = false; } // 静态、同步、私有访问点 static DialogManager? _getInstance() { if (_instance == null) { _instance = DialogManager._internal(); } return _instance; } ///高优先级,回收取消正在显示的弹窗 static const String popRecycle = "popRecycle"; ///其它地方触发取消正在显示的调窗,且不自动显示下一个 static const String popRecycleAndNotNext = "popRecycleAndNotNext"; ///取消弹窗,但不自动显示下一个 static const String popNotNext = "popNotNext"; ///增加弹窗 void add(DialogBean bean) { print("dialog add ${bean.dialogId}"); //对于 highClear 和 highClearAll则加入时要清掉其它低优先级弹窗,必须放在 show 前。假如在 add中,可能低优先级的在后台添加的 switch (bean.dialogPriority) { case DialogPriority.highClear: case DialogPriority.highClearAll: //移除其它低优先级弹窗数据,(不清除正显示的。在 show 里面再清除&& !hasShow(item.dialogId),也可以在后台处理) _dialogList.removeWhere((item) => item.priority < bean.priority); break; default: break; } //去掉重复弹窗(待显示的与顶层正在显示的弹窗) bool hasDialog = _getAllDialogBean().any((dialogBean) => bean.dialogId == dialogBean.dialogId); //没有重复的,且栈中没有 highClear 等优先级的弹窗,或者 clearBean小于等于bean.priority if (!hasDialog) { DialogBean? clearBean = _getHighClearDialogBean(); if (clearBean == null || bean.priority >= clearBean.priority) { _dialogList.add(bean); } } //根据优先级进行排序:降序 _dialogList.sort((a, b) => b.priority.compareTo(a.priority)); } ///显示弹窗,不能返回 Future,不然在同时 show两次时重叠 DialogManager()..add()..show;DialogManager()..add()..show; void show(BuildContext context) { print("dialog show $_dialogList"); //如果有系统弹窗正在显示,则不显示 if (isSystemDialogShowing) { return; } //如果待显示数组为空 if (_dialogList.isEmpty) { return; } //对于 highClean 和 highCleanAll则加入时要清掉其它低优先级弹窗,必须放在 show 前。假如在 add中,可能低优先级的在后台添加的 // if (bean != null) { // //移除其它低优先级弹窗数据 // _dialogList.removeWhere((item) => item.priority < bean.priority); // } //已经有正在显示的弹窗且是在栈顶层显示(showDialogBean != null && 不能加入此判断,在显示强升时碰到的,因为showDialogBean在对话 pop 是会赋为空。所以导致这里不准,一个正在显示,一个取消的回调过来又重新给设为空了。) if (NavigatorManager().isTopDialog() && _hasShowDialogList.isNotEmpty) { DialogBean? hasShowDialogBean = getShowingDialog(); print("当前有正在显示的弹窗$hasShowDialogBean"); DialogBean? bean; //已有显示的弹窗,且high 弹窗在当前页显示,且已显示的弹窗优先级低于high级弹窗 //highClear 和 highClearAll 不用管显示页面 所以不要这个判断,这里_getHighClearDialogBean取所有的(包括正在显示的) if ((bean = _getHighClearDialogBean(beans: _dialogList)) != null && _canShow(bean) && (hasShowDialogBean!.priority < bean!.priority)) { //如果当前加入的弹窗是 highClear,则清掉所有弹窗(包括当前正在显示的,以及未显示的) print("清除正在显示的弹窗${bean.dialogPriority}"); Navigator.pop(context); } else if (((bean = _getDialogBeanByPriority(DialogPriority.high, beans: _dialogList)) != null || (bean = _getDialogBeanByPriority(DialogPriority.normal, beans: _dialogList)) != null) && //已有显示的弹窗,且high 弹窗在当前页显示,且已显示的弹窗优先级低于high级弹窗 _canShow(bean) && hasShowDialogBean!.priority < bean!.priority) { //如果当前加入的弹窗是 high,则回收当前正在显示的弹窗。 print("回收正在显示的弹窗${bean.dialogPriority}"); Navigator.pop(context, popRecycle); //不能在这里把弹窗直接回收,因为在使用 pop 会调用whenComplete会清掉当前显示的弹窗。所以要到whenComplete 中去处理 } return; } //查找当前页面可显示的弹窗数据 DialogBean showDialogBean; try { showDialogBean = _dialogList.firstWhere((bean) => _canShow(bean)); _hasShowDialogList.add(showDialogBean); //新从数组中移动,因为回调中再移动可能会出现提交成功后显示一个提示框,导致一直显示提交框,不会消失 _dialogList.remove(showDialogBean); print("显示的dialog =$showDialogBean"); switch (showDialogBean.dialogType!) { case DialogType.dialog: showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return showDialogBean.createDialogWidget!(); }, ).then((obj) { _handlePopResult(context, obj); }); break; case DialogType.bottomSheet: showModalBottomSheet( backgroundColor: Color(0x00FFFFFF), context: context, isDismissible: false, enableDrag: false, isScrollControlled: true, builder: (BuildContext context) { return showDialogBean.createDialogWidget!(); }, ).then((obj) { _handlePopResult(context, obj); }); break; case DialogType.bottomScrollSheet: showModalBottomSheet( backgroundColor: Color(0x00FFFFFF), context: context, isScrollControlled: true, builder: (BuildContext context) { return showDialogBean.createDialogWidget!(); }, ).then((obj) { _handlePopResult(context, obj); }); break; } } catch (e) { //没有找到可显示的弹窗 } } void _handlePopResult(BuildContext context, dynamic obj) { //取消第一个 DialogBean dismissBean = _hasShowDialogList[0]; print("消失的dialog =$dismissBean,接收回调结果:$obj"); //取消第一个 _hasShowDialogList.remove(dismissBean); //如果是从 high 弹窗回收或外部手动 pop的话,则重新添加回数组 if (obj == popRecycle || obj == popRecycleAndNotNext) { _dialogList.add(dismissBean); } // widget 中可能会先 pop ,再 push,这里显示下一个时,通过顶层路由来判断,不能通过当前显示的页面来判断。 if (obj != popRecycleAndNotNext && obj != popNotNext) { show(context); } } ///弹窗是否正在显示 bool isShowing(String? dialogId) { DialogBean? dialogBean = getShowingDialog(); if (dialogBean != null && dialogId == dialogBean.dialogId) { return true; } return false; } ///取得在正显示的弹窗 DialogBean? getShowingDialog() { if (_hasShowDialogList.isNotEmpty && NavigatorManager().isTopDialog()) { return _hasShowDialogList[_hasShowDialogList.length - 1]; } return null; } ///显示系统弹窗,必须与 dismiss 成对使用 void showSystemDialog(BuildContext context) { //回收正在显示的弹窗 pop(context, result: popRecycleAndNotNext); //正在显示系统弹窗 isSystemDialogShowing = true; } ///隐藏系统弹窗,必须与 show 成对使用 void dismissSystemDialog(BuildContext context) { //正在显示系统弹窗 isSystemDialogShowing = false; //系统弹窗消失,续续显示其它弹窗 show(context); } ///弹窗栈中是否此弹窗 bool hasShow(String dialogId) { return _dialogList.any((bean) => dialogId == bean.dialogId); } ///清除已经显示的弹窗,系统权限弹窗时回收到其它弹窗 ///[result] 默认回收弹窗,不显示下一个 void pop(BuildContext context, {String? result}) { if (_hasShowDialogList.isNotEmpty && NavigatorManager().isTopDialog()) { Navigator.pop(context, result); } } ///清除已经显示的弹窗,系统权限弹窗时回收到其它弹窗 ///[result] 默认回收弹窗,不显示下一个 void popByDialogId(BuildContext context, String dialogId, {String? result}) { if (isShowing(dialogId)) { Navigator.pop(context, result); } } ///清除已经显示的弹窗或待显示的弹窗,例如登录超时打开登录时、或者退出登录 void popAndClear(BuildContext context) { //如果没有 highClear或 highClearAll. DialogBean? dialogBean = _getHighClearDialogBean(); if (dialogBean == null) { _dialogList.clear(); pop(context, result: popNotNext); } } ///是否可显示,未指定页面,或者指定的页面在顶层 bool _canShow(DialogBean? dialogBean) { if (dialogBean == null) { return false; } String? pageRouter = dialogBean.pageRouter; //指定的页面在顶层 return isEmpty(pageRouter) || NavigatorManager().isTopRouter(pageRouter!); } ///按取得 clear 其它弹窗优先级的弹窗 bean DialogBean? _getHighClearDialogBean({List? beans}) { DialogBean? bean = _getDialogBeanByPriority(DialogPriority.highClearAll, beans: beans); if (bean != null) { return bean; } bean = _getDialogBeanByPriority(DialogPriority.highClear, beans: beans); if (bean != null) { return bean; } return null; } ///按弹窗优先级获取弹窗(默认包括待显示的及正在显示的) DialogBean? _getDialogBeanByPriority(DialogPriority priority, {List? beans}) { try { return (beans ?? _getAllDialogBean()).firstWhere( (bean) => priority == bean.dialogPriority, ); } catch (e) { return null; } /*return (beans ?? _getAllDialogBean()).firstWhereOrNull( (bean) => priority == bean.dialogPriority, );*/ } ///取得所有弹窗,包括待显示及正在显示的弹窗 List _getAllDialogBean() { List all = []; DialogBean? showingDialogBean = getShowingDialog(); if (getShowingDialog() != null) { all.add(showingDialogBean!); } all.addAll(_dialogList); return all; } bool isEmpty(String? s) { return s == null || s.isEmpty; } }