diff --git a/guru_app/lib/ads/ads_manager.dart b/guru_app/lib/ads/ads_manager.dart index 84322e6..124d749 100644 --- a/guru_app/lib/ads/ads_manager.dart +++ b/guru_app/lib/ads/ads_manager.dart @@ -22,6 +22,7 @@ import 'package:guru_app/property/settings/guru_settings.dart'; import 'package:guru_applovin_flutter/guru_applovin_flutter.dart'; import 'package:guru_utils/datetime/datetime_utils.dart'; import 'package:guru_utils/extensions/extensions.dart'; +import 'package:guru_utils/network/network_utils.dart'; import 'package:guru_utils/tuple/tuple.dart'; import 'package:guru_utils/ads/ads.dart'; @@ -45,7 +46,8 @@ class AdsManager extends AdsManagerDelegate { final Map rewardsAds = {}; - final AdImpressionController adImpressionController = AdImpressionController(); + final AdImpressionController adImpressionController = + AdImpressionController(); final BehaviorSubject _adsConfigSubject = BehaviorSubject.seeded(AdsConfig.defaultAdsConfig); @@ -53,12 +55,11 @@ class AdsManager extends AdsManagerDelegate { final BehaviorSubject _adsProfileSubject = BehaviorSubject.seeded(GuruApp.instance.adsProfile); - final BehaviorSubject _initializedSubject = BehaviorSubject.seeded(false); + final BehaviorSubject _initializedSubject = + BehaviorSubject.seeded(false); - final BehaviorSubject connectivityStatusSubject = - BehaviorSubject.seeded(ConnectivityResult.none); - - final BehaviorSubject noBannerAndInterstitialAdsSubject = BehaviorSubject.seeded(false); + final BehaviorSubject noBannerAndInterstitialAdsSubject = + BehaviorSubject.seeded(false); final Map adsGlobalProperties = {}; @@ -71,12 +72,29 @@ class AdsManager extends AdsManagerDelegate { "connection" }; - static const List ltSamples = [0, 1, 2, 3, 4, 5, 6, 14, 30, 60, 90, 120, 180]; + static const List ltSamples = [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 14, + 30, + 60, + 90, + 120, + 180 + ]; @override Stream get observableInitialized => _initializedSubject.stream; - Stream get observableConnectivityStatus => connectivityStatusSubject.stream; + ConnectivityResult get connectivityStatus => NetworkUtils.currentConnectivityStatus; + + Stream get observableConnectivityStatus => + NetworkUtils.observableConnectivityStatus; @override Stream get observableNoAds => noBannerAndInterstitialAdsSubject.stream; @@ -89,13 +107,13 @@ class AdsManager extends AdsManagerDelegate { bool get hasAmazonBannerAds => adsConfig.bannerConfig.amazonEnable; - bool get hasAmazonInterstitialAds => adsConfig.interstitialConfig.amazonEnable; + bool get hasAmazonInterstitialAds => + adsConfig.interstitialConfig.amazonEnable; bool get hasAmazonAds => hasAmazonBannerAds || hasAmazonInterstitialAds; - ConnectivityResult get connectivityStatus => connectivityStatusSubject.value; - - final BehaviorSubject> keywordsSubject = BehaviorSubject.seeded({}); + final BehaviorSubject> keywordsSubject = + BehaviorSubject.seeded({}); Stream> get observableKeywords => keywordsSubject.stream; @@ -118,14 +136,16 @@ class AdsManager extends AdsManagerDelegate { void setNoAds(bool noAds) { noBannerAndInterstitialAdsSubject.addIfChanged(noAds); GuruSettings.instance.isNoAds.set(noAds); - GuruAnalytics.instance.setUserProperty("user_type", noAds ? "noads" : "default"); + GuruAnalytics.instance + .setUserProperty("user_type", noAds ? "noads" : "default"); setProperty("user_type", noAds ? "noads" : "default"); } void ensureInitialize() {} void listenIap() { - final obs = Rx.combineLatest2, Tuple2>>( + final obs = Rx.combineLatest2, + Tuple2>>( IapManager.instance.observableAvailable, IapManager.instance.observableAssetStore, (a, b) => Tuple2(a, b)); @@ -133,10 +153,11 @@ class AdsManager extends AdsManagerDelegate { final available = tuple.item1; final purchasedStore = tuple.item2; if (available && purchasedStore.isActive) { - final tempIsNoAds = - purchasedStore.existsAssets(GuruApp.instance.productProfile.noAdsCapIds); + final tempIsNoAds = purchasedStore + .existsAssets(GuruApp.instance.productProfile.noAdsCapIds); final isNoAds = isPurchasedNoAd; - Log.i("purchased store changed active! tempIsNoAds:$tempIsNoAds isNoAds:$isNoAds", + Log.i( + "purchased store changed active! tempIsNoAds:$tempIsNoAds isNoAds:$isNoAds", syncFirebase: true); if (isNoAds != tempIsNoAds) { if (!tempIsNoAds) { @@ -149,57 +170,88 @@ class AdsManager extends AdsManagerDelegate { } })); } - + static bool initializedSdk = false; Future initialize({SaasUser? saasUser}) async { _adsProfileSubject.addEx(GuruApp.instance.adsProfile); await initEnv(); - await initSdk( - saasUser: saasUser, - onInitialized: () { - // loadAds(); - adImpressionController.init(); - initLifecycleConnectivity(); - checkAndPreload(); - // GuruSettings.instance.totalLevelUp - // .observe() - // .throttleTime(const Duration(seconds: 1)) - // .listen((count) { - // checkAndPreload(); - // }); + final connected = await NetworkUtils.isNetworkConnected(); + Log.d("adsManager initialize connected:$connected", tag: "Ads"); + if (connected) { + await initSdk( + saasUser: saasUser, + onInitialized: () { + // loadAds(); + adImpressionController.init(); + checkAndPreload(); + // GuruSettings.instance.totalLevelUp + // .observe() + // .throttleTime(const Duration(seconds: 1)) + // .listen((count) { + // checkAndPreload(); + // }); - Log.i("ADS Initialized", tag: "Ads", syncFirebase: true); - }); + Log.i("ADS Initialized", tag: "Ads", syncFirebase: true); + }); + } else { + + NetworkUtils.observableConnectivityTrack.listen((track) async { + if (track.newResult != ConnectivityResult.none) { + if (!initializedSdk) { + initializedSdk = true; + await initSdk(onInitialized: () { + adImpressionController.init(); + checkAndPreload(); + Log.i("ADS Initialized", tag: "Ads", syncFirebase: true); + }); + } + } + if (track.newResult != track.oldResult) { + Log.i("connectivity result changed! retry ads!", tag: "Ads"); + setKeyword("connection", track.newResult.toString()); + if (LifecycleManager.instance.isAppForeground()) { + if (track.newResult == ConnectivityResult.none && + track.oldResult != ConnectivityResult.none) { + Log.i("connectivity changed! retry ads!", tag: "Ads"); + retry(); + } + } + } + }); + } subscriptions.add(RemoteConfigManager.instance.observeConfig().listen((_) { refreshAdsConfig(); }, onError: (error, stacktrace) { - Log.i("init config error!", tag: "Ads", error: error, stackTrace: stacktrace); + Log.i("init config error!", + tag: "Ads", error: error, stackTrace: stacktrace); })); listenIap(); } - void initLifecycleConnectivity() { - StreamSubscription? streamSubscription; - LifecycleManager.instance.observableAppLifecycle.listen((foreground) { - if (foreground) { - streamSubscription = - Connectivity().onConnectivityChanged.listen((ConnectivityResult result) { - Log.i("Connectivity: $result", tag: "Connectivity"); - if (connectivityStatus == ConnectivityResult.none && result != ConnectivityResult.none) { - Log.i("connectivity changed! retry ads!", tag: "Ads"); - retry(); - } - final changed = connectivityStatusSubject.addIfChanged(result); - if (changed) { - setKeyword("connection", result.toString()); - } - }); - } else { - streamSubscription?.cancel(); - streamSubscription = null; - } - }); - } + // void initLifecycleConnectivity() { + // StreamSubscription? streamSubscription; + // LifecycleManager.instance.observableAppLifecycle.listen((foreground) { + // if (foreground) { + // streamSubscription = Connectivity() + // .onConnectivityChanged + // .listen((ConnectivityResult result) { + // Log.i("Connectivity: $result", tag: "Connectivity"); + // if (connectivityStatus == ConnectivityResult.none && + // result != ConnectivityResult.none) { + // Log.i("connectivity changed! retry ads!", tag: "Ads"); + // retry(); + // } + // final changed = connectivityStatusSubject.addIfChanged(result); + // if (changed) { + // setKeyword("connection", result.toString()); + // } + // }); + // } else { + // streamSubscription?.cancel(); + // streamSubscription = null; + // } + // }); + // } void initAdsProfile() { final _hasAmazonBannerAds = hasAmazonBannerAds; @@ -209,27 +261,33 @@ class AdsManager extends AdsManagerDelegate { final strategyInterstitialIds = adsConfig.strategyAdsConfig.interstitialIds; final newAdsProfile = adsProfile.copyWith( amazonAppId: _hasAmazonAds ? defaultAdsProfile.amazonAppId : null, - amazonBannerSlotId: _hasAmazonBannerAds ? defaultAdsProfile.amazonBannerSlotId : null, - amazonInterstitialSlotId: - _hasAmazonInterstitialAds ? defaultAdsProfile.amazonInterstitialSlotId : null, + amazonBannerSlotId: + _hasAmazonBannerAds ? defaultAdsProfile.amazonBannerSlotId : null, + amazonInterstitialSlotId: _hasAmazonInterstitialAds + ? defaultAdsProfile.amazonInterstitialSlotId + : null, strategyInterstitialIds: strategyInterstitialIds); _adsProfileSubject.addEx(newAdsProfile); } Future initEnv() async { - final adsPropertyBundle = await AppProperty.getInstance().loadValuesByTag(PropertyTags.ads); + final adsPropertyBundle = + await AppProperty.getInstance().loadValuesByTag(PropertyTags.ads); final isNoAds = adsPropertyBundle.getBool(PropertyKeys.isNoAds) ?? false; - consentTestDeviceId = adsPropertyBundle.getString(PropertyKeys.admobConsentTestDeviceId); - consentDebugGeography = adsPropertyBundle.getInt(PropertyKeys.admobConsentDebugGeography); + consentTestDeviceId = + adsPropertyBundle.getString(PropertyKeys.admobConsentTestDeviceId); + consentDebugGeography = + adsPropertyBundle.getInt(PropertyKeys.admobConsentDebugGeography); noBannerAndInterstitialAdsSubject.addIfChanged(isNoAds); - GuruAnalytics.instance.setUserProperty("user_type", isNoAds ? "noads" : "default"); + GuruAnalytics.instance + .setUserProperty("user_type", isNoAds ? "noads" : "default"); setProperty("user_type", isNoAds ? "noads" : "default"); final result = await Connectivity().checkConnectivity().catchError((error) { Log.w("checkConnectivity error! $error"); }); - connectivityStatusSubject.addEx(result); + // connectivityStatusSubject.addEx(result); setProperty("connectivityStatus", result.toString()); refreshAdsConfig(); @@ -283,7 +341,8 @@ class AdsManager extends AdsManagerDelegate { } void checkAndPreload( - {AdsValidator? rewardedValidator, AdsValidator? interstitialValidator}) async { + {AdsValidator? rewardedValidator, + AdsValidator? interstitialValidator}) async { final canPreloadReward = await adsConfig.rewardedConfig.canPreload(validator: rewardedValidator); if (canPreloadReward) { @@ -294,8 +353,8 @@ class AdsManager extends AdsManagerDelegate { } } - final canPreloadInterstitial = - await adsConfig.interstitialConfig.canPreload(validator: interstitialValidator); + final canPreloadInterstitial = await adsConfig.interstitialConfig + .canPreload(validator: interstitialValidator); if (!isPurchasedNoAd && canPreloadInterstitial) { Log.d("preload interstitial canPreload!"); final interstitial = await getInterstitialAds(); @@ -349,7 +408,9 @@ class AdsManager extends AdsManagerDelegate { } await AppProperty.getInstance().setLatestLtDate(dateNum); } - final idx = _nearestLt(0, ltSamples.lastIndex, lt).abs().clamp(0, ltSamples.lastIndex); + final idx = _nearestLt(0, ltSamples.lastIndex, lt) + .abs() + .clamp(0, ltSamples.lastIndex); Log.d( "getKeywordLt: installTime:$latestLtDate now:$dateNum lt:$lt keywordLt:${ltSamples[idx]}"); @@ -415,7 +476,8 @@ class AdsManager extends AdsManagerDelegate { void setKeyword(String key, String value, {bool debugForce = false}) { if (!GuruSettings.instance.debugMode.get() || !debugForce) { if (_reservedKeywords.contains(key)) { - Log.w("setKeyword error! the key($key) is reserved and cannot be used!", tag: "Ads"); + Log.w("setKeyword error! the key($key) is reserved and cannot be used!", + tag: "Ads"); return; } @@ -423,7 +485,8 @@ class AdsManager extends AdsManagerDelegate { key.length > 36 || key.indexOf(_alpha) != 0 || key.contains(_nonAlphaNumeric)) { - Log.w("setKeyword error! the key($key) must contain 1 to 36 alphanumeric characters.", + Log.w( + "setKeyword error! the key($key) must contain 1 to 36 alphanumeric characters.", tag: "Ads"); return; } @@ -436,7 +499,9 @@ class AdsManager extends AdsManagerDelegate { void removeKeyword(String key, {bool debugForce = false}) { if (!GuruSettings.instance.debugMode.get() || !debugForce) { if (_reservedKeywords.contains(key)) { - Log.w("removeKeyword error! the key($key) is reserved and cannot be used!", tag: "Ads"); + Log.w( + "removeKeyword error! the key($key) is reserved and cannot be used!", + tag: "Ads"); return; } @@ -444,7 +509,8 @@ class AdsManager extends AdsManagerDelegate { key.length > 36 || key.indexOf(_alpha) != 0 || key.contains(_nonAlphaNumeric)) { - Log.w("removeKeyword error! the key($key) must contain 1 to 36 alphanumeric characters.", + Log.w( + "removeKeyword error! the key($key) must contain 1 to 36 alphanumeric characters.", tag: "Ads"); return; } @@ -463,22 +529,28 @@ class AdsManager extends AdsManagerDelegate { } bool testParseAdsDefaultConfig() { - final iadsConfigString = RemoteConfigReservedConstants.getDefaultConfigString( - RemoteConfigReservedConstants.iadsConfig) ?? - ""; - final radsConfigString = RemoteConfigReservedConstants.getDefaultConfigString( - RemoteConfigReservedConstants.radsConfig) ?? - ""; - final badsConfigString = RemoteConfigReservedConstants.getDefaultConfigString( - RemoteConfigReservedConstants.badsConfig) ?? - ""; - final iosAttConfigString = RemoteConfigReservedConstants.getDefaultConfigString( - RemoteConfigReservedConstants.iosAttConfig) ?? - ""; + final iadsConfigString = + RemoteConfigReservedConstants.getDefaultConfigString( + RemoteConfigReservedConstants.iadsConfig) ?? + ""; + final radsConfigString = + RemoteConfigReservedConstants.getDefaultConfigString( + RemoteConfigReservedConstants.radsConfig) ?? + ""; + final badsConfigString = + RemoteConfigReservedConstants.getDefaultConfigString( + RemoteConfigReservedConstants.badsConfig) ?? + ""; + final iosAttConfigString = + RemoteConfigReservedConstants.getDefaultConfigString( + RemoteConfigReservedConstants.iosAttConfig) ?? + ""; try { - final adInterstitial = AdInterstitialConfig.fromJson(json.decode(iadsConfigString)); + final adInterstitial = + AdInterstitialConfig.fromJson(json.decode(iadsConfigString)); final adBanner = AdBannerConfig.fromJson(json.decode(badsConfigString)); - final iosAttConfig = IOSAttConfig.fromJson(json.decode(iosAttConfigString)); + final iosAttConfig = + IOSAttConfig.fromJson(json.decode(iosAttConfigString)); Log.d("==== ADS AdsConfig ===="); Log.d(" ---> [INTERSTITIAL]: $iadsConfigString"); Log.d(" ---> [BANNER]: $badsConfigString"); @@ -486,7 +558,9 @@ class AdsManager extends AdsManagerDelegate { Log.d("======================="); _adsConfigSubject.addEx(AdsConfig.build( - interstitialConfig: adInterstitial, bannerConfig: adBanner, iosAttConfig: iosAttConfig)); + interstitialConfig: adInterstitial, + bannerConfig: adBanner, + iosAttConfig: iosAttConfig)); return true; } catch (error, stacktrace) { Log.e("refreshAdsConfig error $error $stacktrace"); @@ -500,14 +574,18 @@ class AdsManager extends AdsManagerDelegate { final adInterstitial = RemoteConfigManager.instance.getIadsConfig(); final adReward = RemoteConfigManager.instance.getRadsConfig(); final adBanner = RemoteConfigManager.instance.getBadsConfig(); - final strategyAdsConfig = RemoteConfigManager.instance.getStrategyAdsConfig(); + final strategyAdsConfig = + RemoteConfigManager.instance.getStrategyAdsConfig(); final iosAttConfig = RemoteConfigManager.instance.getIOSAttConfig(); Log.d("==== ADS AdsConfig ====", tag: PropertyTags.ads); - Log.d(" ---> [COMMON]: ${commonAdsConfig.toJson()}", tag: PropertyTags.ads); - Log.d(" ---> [INTERSTITIAL]: ${adInterstitial.toJson()}", tag: PropertyTags.ads); + Log.d(" ---> [COMMON]: ${commonAdsConfig.toJson()}", + tag: PropertyTags.ads); + Log.d(" ---> [INTERSTITIAL]: ${adInterstitial.toJson()}", + tag: PropertyTags.ads); Log.d(" ---> [REWARD]: ${adReward.toJson()}", tag: PropertyTags.ads); Log.d(" ---> [BANNER]: ${adBanner.toJson()}", tag: PropertyTags.ads); - Log.d(" ---> [STRATEGY]: ${strategyAdsConfig.toJson()}", tag: PropertyTags.ads); + Log.d(" ---> [STRATEGY]: ${strategyAdsConfig.toJson()}", + tag: PropertyTags.ads); Log.d(" ---> [IOSATT]: ${iosAttConfig.toJson()}", tag: PropertyTags.ads); Log.d("=======================", tag: PropertyTags.ads); _adsConfigSubject.addEx(AdsConfig.build( @@ -535,14 +613,16 @@ class AdsManager extends AdsManagerDelegate { MaxStrategyInterstitialAds.create(strategyInterstitialIds)..init(); } else { ad = interstitialAds[strategyInterstitialIds.first.adUnitId] ??= - ApplovinInterstitialAds.create(strategyInterstitialIds.first.adUnitId, + ApplovinInterstitialAds.create( + strategyInterstitialIds.first.adUnitId, strategyInterstitialIds.first.amazonAdSlotId) ..init(); } } else { - ad = interstitialAds[_adsProfile.interstitialId] ??= ApplovinInterstitialAds.create( - _adsProfile.interstitialId, _adsProfile.amazonInterstitialSlotId) - ..init(); + ad = interstitialAds[_adsProfile.interstitialId] ??= + ApplovinInterstitialAds.create( + _adsProfile.interstitialId, _adsProfile.amazonInterstitialSlotId) + ..init(); } return ad; } @@ -561,11 +641,13 @@ class AdsManager extends AdsManagerDelegate { } Future requestGdpr({int? debugGeography, String? testDeviceId}) async { - Log.d("requestGdpr! debugGeography:$debugGeography testDeviceId:$testDeviceId", tag: "Ads"); + Log.d( + "requestGdpr! debugGeography:$debugGeography testDeviceId:$testDeviceId", + tag: "Ads"); // adb logcat -s UserMessagingPlatform // Use new ConsentDebugSettings.Builder().addTestDeviceHashedId("xxxx") to set this as a debug device. - final result = await GuruApplovinFlutter.instance - .requestGdpr(debugGeography: debugGeography, testDeviceId: testDeviceId); + final result = await GuruApplovinFlutter.instance.requestGdpr( + debugGeography: debugGeography, testDeviceId: testDeviceId); final consentResult = await GuruAnalytics.instance.refreshConsents(); Log.d("requestGdpr result:$result consentResult:$consentResult"); return result; @@ -576,14 +658,17 @@ class AdsManager extends AdsManagerDelegate { } Future updateOrientation(int orientation) async { - final result = await GuruApplovinFlutter.instance.updateOrientation(orientation); + final result = + await GuruApplovinFlutter.instance.updateOrientation(orientation); return result == true; } @override - Future createBannerAds({String? scene, AdsLifecycleObserver? observer}) async { + Future createBannerAds( + {String? scene, AdsLifecycleObserver? observer}) async { final _adsProfile = adsProfile; - return ApplovinBannerAds.create(_adsProfile.bannerId, _adsProfile.amazonBannerSlotId, + return ApplovinBannerAds.create( + _adsProfile.bannerId, _adsProfile.amazonBannerSlotId, scene: scene, observer: observer); } @@ -593,8 +678,9 @@ class AdsManager extends AdsManagerDelegate { } final hiddenAt = AdsManager.instance.latestFullscreenAdsHiddenTimestamps; final now = DateTimeUtils.currentTimeInMillis(); - final impGapInMillis = - AdsManager.instance.adsConfig.interstitialConfig.getSceneImpGapInSeconds(scene) * 1000; + final impGapInMillis = AdsManager.instance.adsConfig.interstitialConfig + .getSceneImpGapInSeconds(scene) * + 1000; Log.d( "canShowInterstitial($scene): now:$now latestFullscreenAdsHiddenTimestamps:$latestFullscreenAdsHiddenTimestamps hiddenAt:$hiddenAt impGapInMillis:$impGapInMillis", tag: "Ads"); @@ -606,7 +692,8 @@ class AdsManager extends AdsManagerDelegate { } @override - Future validateInterstitial(String? scene, {AdsValidator? validator}) { + Future validateInterstitial(String? scene, + {AdsValidator? validator}) { final interstitialConfig = adsConfig.interstitialConfig; return interstitialConfig.check(scene ?? "", validator: validator); } @@ -629,9 +716,11 @@ class AdsManager extends AdsManagerDelegate { case "bannerAutoDisposeInterval": return adsConfig.bannerConfig.autoDisposeIntervalInMinutes; case "allowInterstitialAsAlternativeReward": - return GuruApp.instance.appSpec.deployment.allowInterstitialAsAlternativeReward; + return GuruApp + .instance.appSpec.deployment.allowInterstitialAsAlternativeReward; case "showInternalAdsWhenBannerUnavailable": - return GuruApp.instance.appSpec.deployment.showInternalAdsWhenBannerUnavailable; + return GuruApp + .instance.appSpec.deployment.showInternalAdsWhenBannerUnavailable; } } } diff --git a/guru_app/packages/guru_utils/lib/network/network_utils.dart b/guru_app/packages/guru_utils/lib/network/network_utils.dart index c4fb19b..df639d0 100644 --- a/guru_app/packages/guru_utils/lib/network/network_utils.dart +++ b/guru_app/packages/guru_utils/lib/network/network_utils.dart @@ -44,6 +44,12 @@ class NetworkUtils { static Stream get observableConnectivityTrack => _connectivityTrackSubject.stream; + static ConnectivityResult get currentConnectivityStatus => + _connectivityTrackSubject.value.newResult; + + static Stream get observableConnectivityStatus => + observableConnectivityTrack.map((event) => event.newResult); + static void setMockConnectivityStatus(ConnectivityResult? status) { _mockConnectivityStatus = status; }