AtmoSphere/lib/tools/app_ads_managers.dart
2026-01-16 18:22:32 +08:00

483 lines
14 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 'dart:io' show Platform;
import 'package:flutter/services.dart';
import 'package:unity_levelplay_mediation/unity_levelplay_mediation.dart';
const String TAG = 'InterstitialAdManager';
const String APP_KEY_IOS = '24e816d5d';
enum InterstitialAdType {
first,
second,
third;
String get displayName {
switch (this) {
case InterstitialAdType.first:
return 'First Interstitial';
case InterstitialAdType.second:
return 'Second Interstitial';
case InterstitialAdType.third:
return 'Third Interstitial';
}
}
/// 获取广告的 adUnitId广告位 ID
String get adUnitId {
switch (this) {
case InterstitialAdType.first:
return 'ipgwvc8d6vg3bbf6';
case InterstitialAdType.second:
return '5b53pus0enyp87xw';
case InterstitialAdType.third:
return 'mnhvu9lj9vrdb4fz';
}
}
/// 获取广告的 placement 名称(返回广告 ID
String get placementName => adUnitId;
}
/// Interstitial 广告管理器(单例)
class InterstitialAdManager with LevelPlayInitListener {
// 单例实例
static final InterstitialAdManager _instance =
InterstitialAdManager._internal();
// 工厂构造函数返回单例
factory InterstitialAdManager() {
return _instance;
}
// 私有构造函数
InterstitialAdManager._internal();
// 三个 Interstitial 广告实例
final Map<InterstitialAdType, LevelPlayInterstitialAd> _interstitialAds = {};
// adId 到 adType 的映射,用于在监听器中识别广告
final Map<String, InterstitialAdType> _adIdToTypeMap = {};
// 初始化状态
bool _isInitialized = false;
bool _isInitializing = false;
// 当前正在展示的广告(避免同时展示多个)
InterstitialAdType? _currentShowingAd;
// 广告完成回调映射
final Map<InterstitialAdType, VoidCallback?> _adCompletedCallbacks = {};
// --- 开屏广告专用回调和状态 ---
Function(InterstitialAdType adType)? _onSplashAdReadyCallback;
Function()? _onSplashAdAllFailedCallback;
Set<InterstitialAdType> _splashAdsToLoad = {};
int _splashAdsFailedCount = 0;
bool _isHandlingSplashAd = false;
/// 获取单例实例
static InterstitialAdManager get instance => _instance;
/// 获取广告实例
LevelPlayInterstitialAd? getAd(InterstitialAdType adType) {
return _interstitialAds[adType];
}
/// 初始化 LevelPlay SDK 和广告实例
Future<void> initialize() async {
if (_isInitialized || _isInitializing) {
logMethodName(
'Initialize',
'Info',
'Already initialized or initializing',
);
return;
}
_isInitializing = true;
try {
// 检查平台,只支持 iOS
if (!Platform.isIOS) {
logMethodName(
'Platform Check',
'Error',
'This app only supports iOS platform',
);
_isInitializing = false;
return;
}
// 初始化三个 Interstitial 广告实例,每个使用不同的 adUnitId
for (var adType in InterstitialAdType.values) {
final ad = LevelPlayInterstitialAd(adUnitId: adType.adUnitId);
_interstitialAds[adType] = ad;
ad.setListener(_InterstitialAdListenerWrapper(adType, this));
}
// 启用调试模式
// await enableDebug();
// 初始化 LevelPlay SDK
final initRequest = LevelPlayInitRequest.builder(APP_KEY_IOS).build();
await LevelPlay.init(initRequest: initRequest, initListener: this);
} on PlatformException catch (e) {
_isInitializing = false;
logMethodName('Initialize', 'PlatformException', e);
} catch (e) {
_isInitializing = false;
logMethodName('Initialize', 'Error', e);
}
}
/// iOS14 IDFA 访问权限检查
Future<void> checkATT() async {
try {
final currentStatus =
await ATTrackingManager.getTrackingAuthorizationStatus();
logMethodName(
'getTrackingAuthorizationStatus',
'ATTStatus:',
currentStatus,
);
if (currentStatus == ATTStatus.NotDetermined) {
final returnedStatus =
await ATTrackingManager.requestTrackingAuthorization();
logMethodName(
'requestTrackingAuthorizationATTStatus',
'ATTStatus returned:',
returnedStatus,
);
}
} catch (e) {
logMethodName('checkATT', 'Error', e);
}
}
/// 启用调试模式
Future<void> enableDebug() async {
try {
await LevelPlay.setAdaptersDebug(true);
LevelPlay.validateIntegration();
} catch (e) {
logMethodName('enableDebug', 'Error', e);
}
}
/// 加载开屏广告的专用方法 - 并行加载三个广告,任意一个成功就立即回调并跳转
/// 如果所有广告都失败,则调用 onAllAdsFailed
void loadInitialSplashAd({
required Function(InterstitialAdType adType) onAdReady,
required Function() onAllAdsFailed,
}) {
if (!_isInitialized) {
logMethodName(
'LoadInitialSplashAd',
'Warning',
'Manager not initialized yet. Call initialize() first.',
);
onAllAdsFailed();
return;
}
_onSplashAdReadyCallback = onAdReady;
_onSplashAdAllFailedCallback = onAllAdsFailed;
_splashAdsFailedCount = 0;
_isHandlingSplashAd = true;
_splashAdsToLoad = {
InterstitialAdType.first,
InterstitialAdType.second,
InterstitialAdType.third,
};
logMethodName('LoadInitialSplashAd', 'Info', '开始并行加载初始开屏广告...');
for (final adType in _splashAdsToLoad) {
loadAd(adType);
}
}
/// 清除开屏广告回调的私有方法
void _clearSplashCallbacks() {
_onSplashAdReadyCallback = null;
_onSplashAdAllFailedCallback = null;
_splashAdsToLoad.clear();
_splashAdsFailedCount = 0;
_isHandlingSplashAd = false;
}
/// 加载指定类型的广告
Future<void> loadAd(InterstitialAdType adType) async {
if (!_isInitialized) {
logMethodName(
'LoadAd',
'Warning',
'Manager not initialized yet. Call initialize() first.',
);
return;
}
final ad = _interstitialAds[adType];
if (ad != null) {
try {
await ad.loadAd();
// 加载后更新 adId 映射
if (ad.adId.isNotEmpty) {
_adIdToTypeMap[ad.adId] = adType;
}
logMethodName('LoadAd', 'Success', 'Loading ${adType.displayName}');
} catch (e) {
logMethodName(
'LoadAd',
'Error',
'Failed to load ${adType.displayName}: $e',
);
}
} else {
logMethodName(
'LoadAd',
'Error',
'Ad instance not found for ${adType.displayName}',
);
}
}
/// 显示指定类型的广告
/// [onAdCompleted] 广告完成回调(广告关闭或展示失败时调用)
Future<void> showAd(
InterstitialAdType adType, {
VoidCallback? onAdCompleted,
}) async {
if (!_isInitialized) {
logMethodName(
'ShowAd',
'Warning',
'Manager not initialized yet. Call initialize() first.',
);
return;
}
// 如果已有广告在展示,直接忽略新的展示请求,避免串台
if (_currentShowingAd != null) {
logMethodName(
'ShowAd',
'Info',
'Another interstitial is showing (${_currentShowingAd!.displayName}), skip ${adType.displayName}',
);
return;
}
final ad = _interstitialAds[adType];
if (ad != null) {
try {
final isReady = await ad.isAdReady();
if (isReady) {
_currentShowingAd = adType;
// 保存完成回调
_adCompletedCallbacks[adType] = onAdCompleted;
await ad.showAd(placementName: adType.placementName);
logMethodName('ShowAd', 'Success', 'Showing ${adType.displayName}');
} else {
logMethodName(
'ShowAd',
'Warning',
'${adType.displayName} is not ready yet',
);
// 如果未就绪,尝试重新加载
await loadAd(adType);
// 广告未就绪时也调用完成回调
onAdCompleted?.call();
}
} catch (e) {
logMethodName(
'ShowAd',
'Error',
'Failed to show ${adType.displayName}: $e',
);
// 避免异常导致状态悬挂
if (_currentShowingAd == adType) {
_currentShowingAd = null;
}
// 异常时调用完成回调
onAdCompleted?.call();
}
} else {
logMethodName(
'ShowAd',
'Error',
'Ad instance not found for ${adType.displayName}, try to load it.',
);
// 理论上初始化已创建实例;若缺失则尝试加载
await loadAd(adType);
// 广告实例不存在时也调用完成回调
onAdCompleted?.call();
}
}
/// 检查指定类型的广告是否已准备好
Future<bool> isAdReady(InterstitialAdType adType) async {
if (!_isInitialized) {
return false;
}
final ad = _interstitialAds[adType];
if (ad != null) {
try {
return await ad.isAdReady();
} catch (e) {
logMethodName(
'isAdReady',
'Error',
'Failed to check ${adType.displayName}: $e',
);
return false;
}
}
return false;
}
/// 检查是否已初始化
bool get isInitialized => _isInitialized;
/// LevelPlay Init listener
@override
void onInitFailed(LevelPlayInitError error) {
////初始化失败
logMethodName('❌InitListener', 'onInitFailed', error);
_isInitialized = false;
_isInitializing = false;
}
@override
void onInitSuccess(LevelPlayConfiguration configuration) {
//初始化成功
logMethodName('✅InitListener', 'onInitSuccess', configuration);
_isInitialized = true;
_isInitializing = false;
}
/// 内部方法:处理广告事件(由监听器包装类调用)
void _handleAdEvent(
InterstitialAdType adType,
String eventName,
dynamic data,
) {
logMethodName('${adType.displayName}', eventName, data);
}
/// 工具函数
void logMethodName(String adFormat, String methodName, dynamic data) {
print('$TAG: $adFormat - $methodName $data');
}
}
/// Interstitial 广告监听器包装类
/// 用于为每个广告实例创建独立的监听器,以便准确识别事件来源
class _InterstitialAdListenerWrapper with LevelPlayInterstitialAdListener {
final InterstitialAdType adType;
final InterstitialAdManager manager;
_InterstitialAdListenerWrapper(this.adType, this.manager);
@override
void onAdClicked(LevelPlayAdInfo adInfo) {
manager._handleAdEvent(adType, 'onAdClicked', adInfo);
}
@override
void onAdClosed(LevelPlayAdInfo adInfo) {
manager._handleAdEvent(adType, 'onAdClosed', adInfo);
// 调用完成回调
final callback = manager._adCompletedCallbacks[adType];
callback?.call();
manager._adCompletedCallbacks.remove(adType);
// 广告关闭后,自动重新加载该广告以便下次使用
manager.logMethodName(
'onAdClosed',
'Info',
'广告已关闭,开始重新加载: ${adType.displayName}',
);
// 清理当前展示状态
if (manager._currentShowingAd == adType) {
manager._currentShowingAd = null;
}
manager.loadAd(adType);
}
@override
void onAdDisplayFailed(LevelPlayAdError error, LevelPlayAdInfo adInfo) {
manager._handleAdEvent(adType, 'onAdDisplayFailed', '$error | $adInfo');
// 调用完成回调
final callback = manager._adCompletedCallbacks[adType];
callback?.call();
manager._adCompletedCallbacks.remove(adType);
// 广告展示失败后,也重新加载该广告
manager.logMethodName(
'onAdDisplayFailed',
'Info',
'广告展示失败,开始重新加载: ${adType.displayName}',
);
// 展示失败时清理当前展示状态
if (manager._currentShowingAd == adType) {
manager._currentShowingAd = null;
}
manager.loadAd(adType);
}
@override
void onAdDisplayed(LevelPlayAdInfo adInfo) {
manager._handleAdEvent(adType, 'onAdDisplayed', adInfo);
}
@override
void onAdInfoChanged(LevelPlayAdInfo adInfo) {
manager._handleAdEvent(adType, 'onAdInfoChanged', adInfo);
}
@override
void onAdLoadFailed(LevelPlayAdError error) {
manager._handleAdEvent(adType, 'onAdLoadFailed', error);
// 处理开屏广告失败逻辑
if (manager._isHandlingSplashAd &&
manager._splashAdsToLoad.contains(adType) &&
manager._onSplashAdAllFailedCallback != null) {
manager.logMethodName(
'LoadInitialSplashAd',
'Failed',
'❌ 广告加载失败: ${adType.displayName}, 原因: $error',
);
manager._splashAdsFailedCount++;
if (manager._splashAdsFailedCount >= manager._splashAdsToLoad.length) {
manager._onSplashAdAllFailedCallback!();
manager._clearSplashCallbacks();
}
}
}
@override
void onAdLoaded(LevelPlayAdInfo adInfo) {
manager._handleAdEvent(adType, 'onAdLoaded', adInfo);
// 处理开屏广告逻辑:任意一个加载成功就立即回调(只触发一次)
if (manager._isHandlingSplashAd &&
manager._splashAdsToLoad.contains(adType) &&
manager._onSplashAdReadyCallback != null) {
manager.logMethodName(
'LoadInitialSplashAd',
'Success',
'✅ 广告加载成功: ${adType.displayName}',
);
manager._onSplashAdReadyCallback!(adType);
// 清除回调状态,确保后续广告加载成功不会再次触发回调
manager._clearSplashCallbacks();
}
}
}