ads_state.dart 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'package:fluttertoast/fluttertoast.dart';
  4. import 'package:logging/logging.dart';
  5. import 'package:provider/provider.dart';
  6. import 'package:puzzleweave/ads/applovin_ads_controller.dart';
  7. import 'package:puzzleweave/l10n/app_localizations.dart';
  8. import 'package:puzzleweave/persistence/persistence.dart';
  9. import 'package:puzzleweave/skin/skin.dart';
  10. import '../remote_config/remote_config.dart';
  11. import 'ad_helper.dart';
  12. final Logger _log = Logger('AdsState');
  13. /// Add by guoziyi
  14. /// 自带广告的 StatefulWidget State
  15. /// 从该类继承的 State 自动带有广告功能
  16. abstract class AdsState<T extends StatefulWidget> extends State<T> {
  17. ///这个值用于数据统计,子类应该在initState初始化的时候正确设置skuId
  18. String skuId = "";
  19. VoidCallback? onEnterBackground; // app 进入后台回调, 外部如关心, 可设置此回调
  20. Function(Duration duration)? onEnterForeground; // app 回到前台回调, 外部如关心, 可设置此回调
  21. Function(AdState state)? onRewardAdState; // reward广告状态回调,外部如关心,可设置此回调
  22. Function(AdState state)? onInterstitialAdState; // reward广告状态回调,外部如关心,可设置此回调
  23. late ApplovinAdsController _applovinAdsController;
  24. late ValueNotifier<AppLifecycleState> lifecycleNotifier;
  25. Completer<bool>? _intersCompleter;
  26. Completer<bool>? _rewardCompleter;
  27. bool earnedReward = false; //是否确定获得激励
  28. //插屏广告是否准备好
  29. late ValueNotifier<bool> intersReadyNotifier;
  30. bool get isIntersReady => intersReadyNotifier.value;
  31. //激励广告是否准备好
  32. late ValueNotifier<bool> rewardReadyNotifier;
  33. bool get isRewardReady => rewardReadyNotifier.value;
  34. //插屏广告当前状态
  35. AdState get intersState => _applovinAdsController.interstitialAdState.value;
  36. //激励广告当前状态
  37. AdState get rewardState => _applovinAdsController.rewardedAdState.value;
  38. @override
  39. void initState() {
  40. super.initState();
  41. _applovinAdsController = context.read<ApplovinAdsController>();
  42. _applovinAdsController.interstitialAdState.removeListener(_onInterstitialAdState);
  43. _applovinAdsController.interstitialAdState.addListener(_onInterstitialAdState);
  44. _applovinAdsController.rewardedAdState.removeListener(_onRewardAdState);
  45. _applovinAdsController.rewardedAdState.addListener(_onRewardAdState);
  46. intersReadyNotifier = ValueNotifier(intersState == AdState.ready);
  47. rewardReadyNotifier = ValueNotifier(rewardState == AdState.ready);
  48. lifecycleNotifier = context.read<ValueNotifier<AppLifecycleState>>();
  49. lifecycleNotifier.removeListener(_handleAppLifecycle);
  50. lifecycleNotifier.addListener(_handleAppLifecycle);
  51. _applovinAdsController.loadInterstitialAd();
  52. _applovinAdsController.loadRewardedAd();
  53. }
  54. @override
  55. void dispose() {
  56. intersReadyNotifier.dispose();
  57. rewardReadyNotifier.dispose();
  58. lifecycleNotifier.removeListener(_handleAppLifecycle);
  59. if (_intersCompleter != null && !_intersCompleter!.isCompleted) {
  60. _intersCompleter!.complete(false);
  61. }
  62. _intersCompleter = null;
  63. if (_rewardCompleter != null && !_rewardCompleter!.isCompleted) {
  64. _rewardCompleter!.complete(false);
  65. }
  66. _rewardCompleter = null;
  67. _applovinAdsController.interstitialAdState.removeListener(_onInterstitialAdState);
  68. _applovinAdsController.rewardedAdState.removeListener(_onRewardAdState);
  69. super.dispose();
  70. }
  71. /// 广告 sdk 是否初始化完成
  72. Future<bool> adSDKReady() async {
  73. return _applovinAdsController.completer.future;
  74. }
  75. /// 检查是否应该展示banner广告
  76. bool shouldShowBannerAd(int doneLevels) {
  77. if (doneLevels == 0) {
  78. _log.info("首关不展示banner广告");
  79. return false; // 首关是引导关卡,一定不要显示广告
  80. }
  81. bool levelShould = doneLevels >= RemoteConfig().intersFreeLevels;
  82. DateTime now = DateTime.now();
  83. int bannerFreeDuration = RemoteConfig().bannerFreeDuration;
  84. DateTime firstRunTime = Persistence().firstRunTime;
  85. bool timeShould = now.difference(firstRunTime).inMinutes > bannerFreeDuration;
  86. return levelShould || timeShould;
  87. }
  88. /// banner 广告
  89. Widget get adBanner {
  90. return _applovinAdsController.bannerAdWidget;
  91. }
  92. ///显示激励广告
  93. ///返回奖励获取结果。true:成功获取到奖励;false;未获得奖励
  94. Future<bool> showRewardAd(String src, String skuId) async {
  95. if (!(await _applovinAdsController.isRewardedAdReady())) {
  96. _applovinAdsController.loadRewardedAd();
  97. if (mounted) {
  98. Fluttertoast.showToast(
  99. msg: AppLocalizations.of(context)!.adNotReady,
  100. toastLength: Toast.LENGTH_SHORT,
  101. gravity: ToastGravity.CENTER,
  102. timeInSecForIosWeb: 1,
  103. backgroundColor: SkinHelper.slotBorderColor,
  104. textColor: Colors.white,
  105. fontSize: 16.0,
  106. );
  107. }
  108. return false;
  109. }
  110. if (_rewardCompleter != null && !_rewardCompleter!.isCompleted) {
  111. _rewardCompleter!.complete(earnedReward);
  112. }
  113. _rewardCompleter = Completer<bool>();
  114. this.skuId = skuId;
  115. earnedReward = false;
  116. _applovinAdsController.showRewardedlAd(
  117. adSrc: src,
  118. skuId: skuId,
  119. onUserEarnedReward: (ad, reward) {
  120. _log.info("................earn reward..........");
  121. earnedReward = true;
  122. },
  123. );
  124. return _rewardCompleter!.future;
  125. }
  126. bool shouldShowInterstitialAd(String src, int doneLevels) {
  127. bool shouldShowAd = false;
  128. if (src == 'level_enter') {
  129. shouldShowAd = _shouldShowEnterIntersAd(doneLevels);
  130. } else {
  131. shouldShowAd = _shouldShowExitIntersAd(doneLevels);
  132. }
  133. return shouldShowAd;
  134. }
  135. ///显示插屏广告
  136. ///返回广告播放结果。true:播放成功;false;播放失败
  137. Future<bool> showInterstitialAd(String src, String skuId, int doneLevels) async {
  138. bool shouldShowAd = shouldShowInterstitialAd(src, doneLevels);
  139. if (!shouldShowAd) return false;
  140. // 插屏广告没有预备好或者当前不符合播放插屏广告条件,直接返回false
  141. if (!(await _applovinAdsController.isInterstitialAdReady())) {
  142. _applovinAdsController.loadInterstitialAd();
  143. return false;
  144. }
  145. if (_intersCompleter != null && !_intersCompleter!.isCompleted) {
  146. _intersCompleter!.complete(false);
  147. }
  148. _intersCompleter = Completer<bool>();
  149. this.skuId = skuId;
  150. _applovinAdsController.showInterstitialAd(adSrc: src, skuId: skuId);
  151. return _intersCompleter!.future;
  152. }
  153. // 检查是否应该展示进入插屏广告
  154. bool _shouldShowEnterIntersAd(int doneLevels) {
  155. if (doneLevels == 0) return false; // 首关是引导关卡,一定不要显示广告
  156. bool levelShould = doneLevels >= RemoteConfig().intersFreeLevels;
  157. DateTime now = DateTime.now();
  158. int enterIntersDuration = RemoteConfig().enterIntersFreeDuration; //新手保护时长,单位:分钟
  159. DateTime firstRunTime = Persistence().firstRunTime;
  160. bool timeShould = now.difference(firstRunTime).inMinutes > enterIntersDuration; //判断当前时间是否已超过新手保护时长
  161. return levelShould || timeShould; // 时间限制和关卡限制两者只要满足其一都可以播放插屏
  162. }
  163. // 检查是否应该展示退出插屏广告
  164. bool _shouldShowExitIntersAd(int doneLevels) {
  165. bool levelShould = doneLevels >= RemoteConfig().intersFreeLevels;
  166. DateTime now = DateTime.now();
  167. int quitIntersDuration = RemoteConfig().quitIntersFreeDuration; //新手保护时长,单位:分钟
  168. DateTime firstRunTime = Persistence().firstRunTime;
  169. bool timeShould = now.difference(firstRunTime).inMinutes > quitIntersDuration;
  170. return levelShould || timeShould; // 时间限制和关卡限制两者只要满足其一都可以播放插屏
  171. }
  172. // 插屏广告回调处理
  173. _onInterstitialAdState() {
  174. if (intersState == AdState.dismissed) {
  175. _log.info("onInterstitialAdState AdState.dismissed");
  176. if (_intersCompleter != null && !_intersCompleter!.isCompleted) {
  177. _intersCompleter!.complete(true);
  178. }
  179. _intersCompleter = null;
  180. }
  181. intersReadyNotifier.value = (intersState == AdState.ready);
  182. onInterstitialAdState?.call(intersState);
  183. }
  184. // 激励广告回调处理
  185. _onRewardAdState() {
  186. if (_applovinAdsController.rewardedAdState.value == AdState.dismissed) {
  187. _log.info("onRewardAdState AdState.dismissed");
  188. if (_rewardCompleter != null && !_rewardCompleter!.isCompleted) {
  189. _rewardCompleter!.complete(earnedReward);
  190. }
  191. _rewardCompleter = null;
  192. }
  193. rewardReadyNotifier.value = (rewardState == AdState.ready);
  194. onRewardAdState?.call(rewardState);
  195. }
  196. ///-----------[Lifecycle]-------------
  197. ///需要处理可重写状态处理函数
  198. AppLifecycleState lifeState = AppLifecycleState.resumed;
  199. bool get isActive => lifeState == AppLifecycleState.resumed;
  200. void _handleAppLifecycle() {
  201. _log.info('AppLifecycleState changed: ${lifecycleNotifier.value}');
  202. lifeState = lifecycleNotifier.value;
  203. if (lifeState == AppLifecycleState.inactive) {
  204. //前台可见,但是无法交互
  205. onInactive();
  206. } else if (lifeState == AppLifecycleState.paused) {
  207. //前台不可见
  208. onPause();
  209. } else if (lifeState == AppLifecycleState.resumed) {
  210. //前台可见,可交互
  211. onResume();
  212. }
  213. }
  214. void onInactive() {}
  215. DateTime? _leaveTime;
  216. void onPause() {
  217. if (intersState == AdState.showing || rewardState == AdState.showing) {
  218. return;
  219. }
  220. _leaveTime = DateTime.now(); // 记录切换到后台的时间
  221. onEnterBackground?.call();
  222. _log.info("App enter background, leaveTime: $_leaveTime");
  223. }
  224. void onResume() {
  225. if (_leaveTime != null) {
  226. var duration = DateTime.now().difference(_leaveTime!);
  227. _log.info("App enter foreground, leaveTime: $_leaveTime, duration=$duration");
  228. onEnterForeground?.call(duration);
  229. }
  230. _leaveTime = null;
  231. }
  232. }