257 lines
8.3 KiB
Dart
257 lines
8.3 KiB
Dart
import 'dart:async';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:secmtp_sdk/at_init.dart';
|
||
import 'package:secmtp_sdk/at_interstitial.dart';
|
||
import 'package:secmtp_sdk/at_interstitial_response.dart';
|
||
import 'package:secmtp_sdk/at_listener.dart';
|
||
|
||
|
||
// --- 1. 使用枚举定义广告位,更安全、清晰 ---
|
||
enum AdPlacement { interstitial1, interstitial2, interstitial3 }
|
||
|
||
/// 广告管理器
|
||
class AppAdsTools {
|
||
// --- 单例实现 ---
|
||
static final AppAdsTools _instance = AppAdsTools._internal();
|
||
|
||
AppAdsTools._internal();
|
||
|
||
static AppAdsTools get instance => _instance;
|
||
|
||
// --- 广告 ID 配置 ---
|
||
static const String _appId = 'h69718907a25ad';
|
||
static const String _appKey = 'a5a35545d00d0e9c5471c354dd24edd03';
|
||
|
||
static final Map<AdPlacement, String> _adUnitIds = {
|
||
AdPlacement.interstitial1: "n6971892e19211",
|
||
AdPlacement.interstitial2: "n6971892f2da1f",
|
||
AdPlacement.interstitial3: "n697189366ab4f",
|
||
};
|
||
|
||
|
||
// --- 内部状态变量 ---
|
||
bool _isSdkInitialized = false;
|
||
Completer<void>? _initCompleter;
|
||
final Set<String> _loadingAds = <String>{};
|
||
bool _isAdShowing = false;
|
||
final Map<String, VoidCallback> _adClosedCallbacks = {};
|
||
|
||
// --- 开屏广告专用回调和状态 ---
|
||
Function(AdPlacement adPlacement)? _onSplashAdReadyCallback;
|
||
Function()? _onSplashAdAllFailedCallback;
|
||
Set<AdPlacement> _splashAdsToLoad = {};
|
||
int _splashAdsFailedCount = 0;
|
||
bool _isHandlingSplashAd = false;
|
||
|
||
// 广告冷却逻辑
|
||
final bool _useGlobalCooldown = true;
|
||
static const String _globalCooldownKey = 'any_interstitial_ad_was_shown';
|
||
final Map<String, DateTime> _lastShownTimestamps = {};
|
||
final int _adCooldownSeconds = 15;
|
||
|
||
/// 初始化TopOn SDK和广告监听
|
||
Future<void> init() {
|
||
if (_initCompleter != null) {
|
||
return _initCompleter!.future;
|
||
}
|
||
_initCompleter = Completer<void>();
|
||
|
||
debugPrint("【TopOnAdManager】SDK 开始初始化...");
|
||
ATInitManger.setLogEnabled(logEnabled: false);
|
||
ATInitManger.initAnyThinkSDK(appidStr: _appId, appidkeyStr: _appKey).then((
|
||
_,
|
||
) {
|
||
debugPrint("【TopOnAdManager】SDK 初始化成功。");
|
||
_isSdkInitialized = true;
|
||
_setupEventListeners();
|
||
_initCompleter!.complete();
|
||
});
|
||
|
||
return _initCompleter!.future;
|
||
}
|
||
|
||
/// 统一配置插屏广告监听器
|
||
void _setupEventListeners() {
|
||
ATListenerManager.interstitialEventHandler.listen((event) {
|
||
final placement = _getPlacementForAdUnitId(event.placementID);
|
||
if (placement == null) return;
|
||
|
||
switch (event.interstatus) {
|
||
case InterstitialStatus.interstitialAdDidFinishLoading:
|
||
_loadingAds.remove(event.placementID);
|
||
debugPrint("【TopOnAdManager】✅ 广告加载成功: ${placement.name}");
|
||
|
||
if (_isHandlingSplashAd &&
|
||
_splashAdsToLoad.contains(placement) &&
|
||
_onSplashAdReadyCallback != null) {
|
||
_onSplashAdReadyCallback!(placement);
|
||
_clearSplashCallbacks();
|
||
}
|
||
break;
|
||
|
||
case InterstitialStatus.interstitialAdFailToLoadAD:
|
||
_loadingAds.remove(event.placementID);
|
||
debugPrint(
|
||
"【TopOnAdManager】❌ 广告加载失败: ${placement.name}, 原因: ${event.requestMessage}",
|
||
);
|
||
|
||
if (_isHandlingSplashAd &&
|
||
_splashAdsToLoad.contains(placement) &&
|
||
_onSplashAdAllFailedCallback != null) {
|
||
_splashAdsFailedCount++;
|
||
if (_splashAdsFailedCount >= _splashAdsToLoad.length) {
|
||
_onSplashAdAllFailedCallback!();
|
||
_clearSplashCallbacks();
|
||
}
|
||
}
|
||
break;
|
||
|
||
case InterstitialStatus.interstitialAdDidClose:
|
||
case InterstitialStatus.interstitialFailedToShow:
|
||
debugPrint("【TopOnAdManager】广告已关闭或展示失败: ${placement.name}");
|
||
_isAdShowing = false;
|
||
_adClosedCallbacks[event.placementID]?.call();
|
||
_adClosedCallbacks.remove(event.placementID);
|
||
preloadAd(placement);
|
||
break;
|
||
|
||
case InterstitialStatus.interstitialDidShowSucceed:
|
||
_isAdShowing = true;
|
||
debugPrint("【TopOnAdManager】广告开始显示: ${placement.name}");
|
||
final keyToUpdate = _useGlobalCooldown
|
||
? _globalCooldownKey
|
||
: event.placementID;
|
||
_lastShownTimestamps[keyToUpdate] = DateTime.now();
|
||
debugPrint("【TopOnAdManager】冷却计时器已更新 (Key: $keyToUpdate)");
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
});
|
||
}
|
||
|
||
/// 加载开屏广告的专用方法
|
||
void loadInitialSplashAd({
|
||
required Function(AdPlacement adPlacement) onAdReady,
|
||
required Function() onAllAdsFailed,
|
||
}) {
|
||
if (!_isSdkInitialized) {
|
||
debugPrint("【TopOnAdManager】SDK 未初始化,无法加载开屏广告。");
|
||
onAllAdsFailed();
|
||
return;
|
||
}
|
||
_onSplashAdReadyCallback = onAdReady;
|
||
_onSplashAdAllFailedCallback = onAllAdsFailed;
|
||
_splashAdsFailedCount = 0;
|
||
_isHandlingSplashAd = true;
|
||
|
||
_splashAdsToLoad = {
|
||
AdPlacement.interstitial1,
|
||
AdPlacement.interstitial2,
|
||
AdPlacement.interstitial3,
|
||
};
|
||
|
||
debugPrint("【TopOnAdManager】开始并行加载初始开屏广告...");
|
||
for (final placement in _splashAdsToLoad) {
|
||
_loadInterstitialAd(placement);
|
||
}
|
||
}
|
||
|
||
/// 清除开屏广告回调的私有方法
|
||
void _clearSplashCallbacks() {
|
||
_onSplashAdReadyCallback = null;
|
||
_onSplashAdAllFailedCallback = null;
|
||
_splashAdsToLoad.clear();
|
||
_splashAdsFailedCount = 0;
|
||
_isHandlingSplashAd = false;
|
||
}
|
||
|
||
/// 公共方法:展示插屏广告
|
||
Future<bool> showAd(AdPlacement placement, {VoidCallback? onAdClosed}) async {
|
||
if (_isAdShowing) {
|
||
debugPrint("【TopOnAdManager】已有广告正在显示,无法展示新广告: ${placement.name}");
|
||
return false;
|
||
}
|
||
|
||
if (!_isSdkInitialized) {
|
||
debugPrint("【TopOnAdManager】SDK 未初始化,无法展示广告。");
|
||
return false;
|
||
}
|
||
final adUnitId = _adUnitIds[placement]!;
|
||
|
||
final keyToCheck = _useGlobalCooldown ? _globalCooldownKey : adUnitId;
|
||
final lastShown = _lastShownTimestamps[keyToCheck];
|
||
if (lastShown != null &&
|
||
DateTime.now().difference(lastShown) <
|
||
Duration(seconds: _adCooldownSeconds)) {
|
||
debugPrint("【TopOnAdManager】❌ 广告冷却中 (${_adCooldownSeconds}s)。放弃展示。");
|
||
return false;
|
||
}
|
||
|
||
bool isReady = await ATInterstitialManager.hasInterstitialAdReady(
|
||
placementID: adUnitId,
|
||
);
|
||
debugPrint("【TopOnAdManager】检查广告就绪状态 ${placement.name}: $isReady");
|
||
|
||
if (isReady) {
|
||
if (onAdClosed != null) {
|
||
_adClosedCallbacks[adUnitId] = onAdClosed;
|
||
}
|
||
ATInterstitialManager.showSceneInterstitialAd(
|
||
placementID: adUnitId,
|
||
sceneID: _getSceneIdForPlacement(placement),
|
||
);
|
||
return true;
|
||
} else {
|
||
debugPrint("【TopOnAdManager】广告 ${placement.name} 尚未准备好展示。");
|
||
preloadAd(placement); // 第一次调用show时,如果没准备好,会自动触发加载
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 预加载广告
|
||
void preloadAd(AdPlacement placement) {
|
||
_loadInterstitialAd(placement);
|
||
}
|
||
|
||
/// 内部方法:加载单个插屏广告
|
||
void _loadInterstitialAd(AdPlacement placement) {
|
||
final adUnitId = _adUnitIds[placement]!;
|
||
if (!_isSdkInitialized) {
|
||
debugPrint("【TopOnAdManager】SDK 未初始化,无法加载广告: ${placement.name}");
|
||
return;
|
||
}
|
||
if (_loadingAds.contains(adUnitId)) {
|
||
debugPrint("【TopOnAdManager】广告 ${placement.name} 正在加载中,跳过本次请求");
|
||
return;
|
||
}
|
||
_loadingAds.add(adUnitId);
|
||
debugPrint("【TopOnAdManager】请求加载插屏广告: ${placement.name}");
|
||
ATInterstitialManager.loadInterstitialAd(
|
||
placementID: adUnitId,
|
||
extraMap: const {},
|
||
);
|
||
}
|
||
|
||
// --- 辅助方法 ---
|
||
AdPlacement? _getPlacementForAdUnitId(String adUnitId) {
|
||
for (var entry in _adUnitIds.entries) {
|
||
if (entry.value == adUnitId) return entry.key;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
String _getSceneIdForPlacement(AdPlacement placement) {
|
||
switch (placement) {
|
||
case AdPlacement.interstitial1:
|
||
return 'interstitial1';
|
||
case AdPlacement.interstitial2:
|
||
return 'interstitial2';
|
||
case AdPlacement.interstitial3:
|
||
return 'interstitial3';
|
||
|
||
}
|
||
}
|
||
}
|