初始化
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import 'dart:async';
|
||||
|
||||
/// 定时弹窗的任务标识:只服务 [TimedPopupManager.schedule] / [TimedPopupManager.cancel] 的去重与取消。
|
||||
/// 立即触发的场景(首页吸底、返回挽留、会员页倒计时等)走 trigger,不占枚举值。
|
||||
enum TimedPopupType {
|
||||
freeWatchPay, // 新用户(newUnpay)免费次数观看满5分钟付费引导
|
||||
videoPayAhead, // 需付费视频起播3秒的前置提示(VIDEO_PREVIEW_END 开关控,一次进页面只弹一次)
|
||||
guidePushBanner, // 进 app 满3分钟的 VIP「特享内容」顶部推送横幅
|
||||
}
|
||||
|
||||
/// 引导弹窗统一调度管理器(全站唯一)
|
||||
///
|
||||
/// 所有付费引导弹窗都经这里触发,统一「多久/什么条件/弹什么」,并保证同一时刻只弹一个(互斥,防叠弹)。
|
||||
/// - [schedule] 定时触发:停留 N 时长后弹(如试看/停留计时)。
|
||||
/// - [trigger] 立即触发:事件驱动即弹(如进页面/点返回)。
|
||||
/// 具体触发条件与弹窗动作由调用方传入(canShow/onShow),本类只负责调度、去重、互斥、生命周期。
|
||||
class TimedPopupManager {
|
||||
static final TimedPopupManager _instance = TimedPopupManager._();
|
||||
factory TimedPopupManager() => _instance;
|
||||
TimedPopupManager._();
|
||||
|
||||
/// 进行中的计时任务(按类型去重,同类型再次 schedule 会重启计时)
|
||||
final Map<TimedPopupType, Timer> _timers = {};
|
||||
|
||||
/// 互斥:同一时刻只显示一个引导弹窗,避免多场景叠弹
|
||||
bool _showing = false;
|
||||
|
||||
/// 定时触发:停留 [delay] 后满足条件弹。同 [type] 重复调用会取消上一个、重新计时。
|
||||
///
|
||||
/// ⚠️ 计时无条件起,[canShow] 只在到点时判一次。别在这里加"起步先判一次不满足就不计时"的优化:
|
||||
/// 这些条件(分层、免费次数、登录态)本来就是等着这 N 分钟里变的,起步挡掉就再也起不来了
|
||||
/// ——切号后分层/次数要等接口回来才对,起步判死会让弹窗永久不弹。
|
||||
void schedule({
|
||||
required TimedPopupType type,
|
||||
required Duration delay,
|
||||
required bool Function() canShow,
|
||||
required Future<void> Function() onShow,
|
||||
}) {
|
||||
cancel(type);
|
||||
_timers[type] = Timer(delay, () {
|
||||
_timers.remove(type);
|
||||
trigger(canShow: canShow, onShow: onShow); // 到点走统一出口;被互斥挡下则本次作废
|
||||
});
|
||||
}
|
||||
|
||||
/// 立即触发:事件驱动(进页面/返回等)满足条件即弹,也是 [schedule] 到点后的统一出口。
|
||||
/// 返回的 Future 在弹窗关闭时完成,调用方可 await 以串起后续逻辑(如试看结束链路)。
|
||||
/// ⚠️ [onShow] 的 Future 必须最终完成,否则互斥标记不复位、全站引导弹窗会静默失效。
|
||||
Future<void> trigger({
|
||||
required bool Function() canShow,
|
||||
required Future<void> Function() onShow,
|
||||
}) async {
|
||||
if (_showing) return; // 已有引导弹窗在显示
|
||||
if (!canShow()) return; // 弹前二次校验(定时场景期间状态可能变)
|
||||
_showing = true;
|
||||
try {
|
||||
await onShow(); // 弹窗 show 的 Future 在关闭时 resolve
|
||||
} finally {
|
||||
_showing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 取消指定类型的计时任务(页面退出时调用)
|
||||
void cancel(TimedPopupType type) => _timers.remove(type)?.cancel();
|
||||
}
|
||||
Reference in New Issue
Block a user