初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
@@ -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();
}