import 'dart:async'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; import 'package:puzzleweave/ads/applovin_ads_controller.dart'; import 'package:puzzleweave/l10n/app_localizations.dart'; import 'package:puzzleweave/persistence/persistence.dart'; import 'package:puzzleweave/skin/skin.dart'; import '../remote_config/remote_config.dart'; import 'ad_helper.dart'; final Logger _log = Logger('AdsState'); /// Add by guoziyi /// 自带广告的 StatefulWidget State /// 从该类继承的 State 自动带有广告功能 abstract class AdsState extends State { ///这个值用于数据统计,子类应该在initState初始化的时候正确设置skuId String skuId = ""; VoidCallback? onEnterBackground; // app 进入后台回调, 外部如关心, 可设置此回调 Function(Duration duration)? onEnterForeground; // app 回到前台回调, 外部如关心, 可设置此回调 Function(AdState state)? onRewardAdState; // reward广告状态回调,外部如关心,可设置此回调 Function(AdState state)? onInterstitialAdState; // reward广告状态回调,外部如关心,可设置此回调 late ApplovinAdsController _applovinAdsController; late ValueNotifier lifecycleNotifier; Completer? _intersCompleter; Completer? _rewardCompleter; bool earnedReward = false; //是否确定获得激励 //插屏广告是否准备好 late ValueNotifier intersReadyNotifier; bool get isIntersReady => intersReadyNotifier.value; //激励广告是否准备好 late ValueNotifier rewardReadyNotifier; bool get isRewardReady => rewardReadyNotifier.value; //插屏广告当前状态 AdState get intersState => _applovinAdsController.interstitialAdState.value; //激励广告当前状态 AdState get rewardState => _applovinAdsController.rewardedAdState.value; @override void initState() { super.initState(); _applovinAdsController = context.read(); _applovinAdsController.interstitialAdState.removeListener(_onInterstitialAdState); _applovinAdsController.interstitialAdState.addListener(_onInterstitialAdState); _applovinAdsController.rewardedAdState.removeListener(_onRewardAdState); _applovinAdsController.rewardedAdState.addListener(_onRewardAdState); intersReadyNotifier = ValueNotifier(intersState == AdState.ready); rewardReadyNotifier = ValueNotifier(rewardState == AdState.ready); lifecycleNotifier = context.read>(); lifecycleNotifier.removeListener(_handleAppLifecycle); lifecycleNotifier.addListener(_handleAppLifecycle); _applovinAdsController.loadInterstitialAd(); _applovinAdsController.loadRewardedAd(); } @override void dispose() { intersReadyNotifier.dispose(); rewardReadyNotifier.dispose(); lifecycleNotifier.removeListener(_handleAppLifecycle); if (_intersCompleter != null && !_intersCompleter!.isCompleted) { _intersCompleter!.complete(false); } _intersCompleter = null; if (_rewardCompleter != null && !_rewardCompleter!.isCompleted) { _rewardCompleter!.complete(false); } _rewardCompleter = null; _applovinAdsController.interstitialAdState.removeListener(_onInterstitialAdState); _applovinAdsController.rewardedAdState.removeListener(_onRewardAdState); super.dispose(); } /// 广告 sdk 是否初始化完成 Future adSDKReady() async { return _applovinAdsController.completer.future; } /// 检查是否应该展示banner广告 bool shouldShowBannerAd(int doneLevels) { if (doneLevels == 0) { _log.info("首关不展示banner广告"); return false; // 首关是引导关卡,一定不要显示广告 } bool levelShould = doneLevels >= RemoteConfig().intersFreeLevels; DateTime now = DateTime.now(); int bannerFreeDuration = RemoteConfig().bannerFreeDuration; DateTime firstRunTime = Persistence().firstRunTime; bool timeShould = now.difference(firstRunTime).inMinutes > bannerFreeDuration; return levelShould || timeShould; } /// banner 广告 Widget get adBanner { return _applovinAdsController.bannerAdWidget; } ///显示激励广告 ///返回奖励获取结果。true:成功获取到奖励;false;未获得奖励 Future showRewardAd(String src, String skuId) async { if (!(await _applovinAdsController.isRewardedAdReady())) { _applovinAdsController.loadRewardedAd(); if (mounted) { Fluttertoast.showToast( msg: AppLocalizations.of(context)!.adNotReady, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, backgroundColor: SkinHelper.slotBorderColor, textColor: Colors.white, fontSize: 16.0, ); } return false; } if (_rewardCompleter != null && !_rewardCompleter!.isCompleted) { _rewardCompleter!.complete(earnedReward); } _rewardCompleter = Completer(); this.skuId = skuId; earnedReward = false; _applovinAdsController.showRewardedlAd( adSrc: src, skuId: skuId, onUserEarnedReward: (ad, reward) { _log.info("................earn reward.........."); earnedReward = true; }, ); return _rewardCompleter!.future; } bool shouldShowInterstitialAd(String src, int doneLevels) { bool shouldShowAd = false; if (src == 'level_enter') { shouldShowAd = _shouldShowEnterIntersAd(doneLevels); } else { shouldShowAd = _shouldShowExitIntersAd(doneLevels); } return shouldShowAd; } ///显示插屏广告 ///返回广告播放结果。true:播放成功;false;播放失败 Future showInterstitialAd(String src, String skuId, int doneLevels) async { bool shouldShowAd = shouldShowInterstitialAd(src, doneLevels); if (!shouldShowAd) return false; // 插屏广告没有预备好或者当前不符合播放插屏广告条件,直接返回false if (!(await _applovinAdsController.isInterstitialAdReady())) { _applovinAdsController.loadInterstitialAd(); return false; } if (_intersCompleter != null && !_intersCompleter!.isCompleted) { _intersCompleter!.complete(false); } _intersCompleter = Completer(); this.skuId = skuId; _applovinAdsController.showInterstitialAd(adSrc: src, skuId: skuId); return _intersCompleter!.future; } // 检查是否应该展示进入插屏广告 bool _shouldShowEnterIntersAd(int doneLevels) { if (doneLevels == 0) return false; // 首关是引导关卡,一定不要显示广告 bool levelShould = doneLevels >= RemoteConfig().intersFreeLevels; DateTime now = DateTime.now(); int enterIntersDuration = RemoteConfig().enterIntersFreeDuration; //新手保护时长,单位:分钟 DateTime firstRunTime = Persistence().firstRunTime; bool timeShould = now.difference(firstRunTime).inMinutes > enterIntersDuration; //判断当前时间是否已超过新手保护时长 return levelShould || timeShould; // 时间限制和关卡限制两者只要满足其一都可以播放插屏 } // 检查是否应该展示退出插屏广告 bool _shouldShowExitIntersAd(int doneLevels) { bool levelShould = doneLevels >= RemoteConfig().intersFreeLevels; DateTime now = DateTime.now(); int quitIntersDuration = RemoteConfig().quitIntersFreeDuration; //新手保护时长,单位:分钟 DateTime firstRunTime = Persistence().firstRunTime; bool timeShould = now.difference(firstRunTime).inMinutes > quitIntersDuration; return levelShould || timeShould; // 时间限制和关卡限制两者只要满足其一都可以播放插屏 } // 插屏广告回调处理 _onInterstitialAdState() { if (intersState == AdState.dismissed) { _log.info("onInterstitialAdState AdState.dismissed"); if (_intersCompleter != null && !_intersCompleter!.isCompleted) { _intersCompleter!.complete(true); } _intersCompleter = null; } intersReadyNotifier.value = (intersState == AdState.ready); onInterstitialAdState?.call(intersState); } // 激励广告回调处理 _onRewardAdState() { if (_applovinAdsController.rewardedAdState.value == AdState.dismissed) { _log.info("onRewardAdState AdState.dismissed"); if (_rewardCompleter != null && !_rewardCompleter!.isCompleted) { _rewardCompleter!.complete(earnedReward); } _rewardCompleter = null; } rewardReadyNotifier.value = (rewardState == AdState.ready); onRewardAdState?.call(rewardState); } ///-----------[Lifecycle]------------- ///需要处理可重写状态处理函数 AppLifecycleState lifeState = AppLifecycleState.resumed; bool get isActive => lifeState == AppLifecycleState.resumed; void _handleAppLifecycle() { _log.info('AppLifecycleState changed: ${lifecycleNotifier.value}'); lifeState = lifecycleNotifier.value; if (lifeState == AppLifecycleState.inactive) { //前台可见,但是无法交互 onInactive(); } else if (lifeState == AppLifecycleState.paused) { //前台不可见 onPause(); } else if (lifeState == AppLifecycleState.resumed) { //前台可见,可交互 onResume(); } } void onInactive() {} DateTime? _leaveTime; void onPause() { if (intersState == AdState.showing || rewardState == AdState.showing) { return; } _leaveTime = DateTime.now(); // 记录切换到后台的时间 onEnterBackground?.call(); _log.info("App enter background, leaveTime: $_leaveTime"); } void onResume() { if (_leaveTime != null) { var duration = DateTime.now().difference(_leaveTime!); _log.info("App enter foreground, leaveTime: $_leaveTime, duration=$duration"); onEnterForeground?.call(duration); } _leaveTime = null; } }