AI_Drawing/lib/common/dialog_manager.dart
2024-06-03 15:30:15 +08:00

310 lines
12 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<DialogBean> _dialogList;
// ///当前正在显示的弹窗
// DialogBean showDialogBean;
///已显示过或正在显示的弹窗数据,还未被回调清除
///已经显示过的弹窗不能用一个DialogBean
///(因为showDialogBean在对话 pop 是会赋为空,一个正要显示,一个取消的回调过来又重新给设为空了。)
late List<DialogBean> _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<DialogBean>? 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<DialogBean>? beans}) {
try {
return (beans ?? _getAllDialogBean()).firstWhere(
(bean) => priority == bean.dialogPriority,
);
} catch (e) {
return null;
}
/*return (beans ?? _getAllDialogBean()).firstWhereOrNull(
(bean) => priority == bean.dialogPriority,
);*/
}
///取得所有弹窗,包括待显示及正在显示的弹窗
List<DialogBean> _getAllDialogBean() {
List<DialogBean> all = [];
DialogBean? showingDialogBean = getShowingDialog();
if (getShowingDialog() != null) {
all.add(showingDialogBean!);
}
all.addAll(_dialogList);
return all;
}
bool isEmpty(String? s) {
return s == null || s.isEmpty;
}
}