2023-12-21 09:14:40 +00:00
|
|
|
import 'dart:async';
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
import 'dart:ui';
|
|
|
|
|
|
|
|
|
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
|
|
|
|
import 'package:guru_app/account/model/user.dart';
|
|
|
|
|
import 'package:guru_app/ads/applovin/banner/applovin_banner_ads.dart';
|
|
|
|
|
import 'package:guru_app/ads/core/ads.dart';
|
|
|
|
|
import 'package:guru_app/ads/core/ads_config.dart';
|
|
|
|
|
import 'package:guru_app/ads/core/ads_impression.dart';
|
|
|
|
|
import 'package:guru_app/ads/core/strategy/interstitial/max_strategy_interstitial_ads.dart';
|
|
|
|
|
import 'package:guru_app/financial/asset/assets_model.dart';
|
|
|
|
|
import 'package:guru_app/financial/asset/assets_store.dart';
|
|
|
|
|
import 'package:guru_app/financial/iap/iap_manager.dart';
|
|
|
|
|
import 'package:guru_app/financial/iap/iap_model.dart';
|
|
|
|
|
import 'package:guru_app/firebase/remoteconfig/remote_config_manager.dart';
|
|
|
|
|
import 'package:guru_app/guru_app.dart';
|
|
|
|
|
import 'package:guru_utils/lifecycle/lifecycle_manager.dart';
|
|
|
|
|
import 'package:guru_app/property/app_property.dart';
|
|
|
|
|
import 'package:guru_app/property/property_keys.dart';
|
|
|
|
|
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';
|
2024-04-11 05:18:00 +00:00
|
|
|
import 'package:guru_utils/network/network_utils.dart';
|
2023-12-21 09:14:40 +00:00
|
|
|
import 'package:guru_utils/tuple/tuple.dart';
|
|
|
|
|
import 'package:guru_utils/ads/ads.dart';
|
|
|
|
|
|
|
|
|
|
import 'applovin/interstitial/applovin_interstitial_ads.dart';
|
|
|
|
|
import 'applovin/rewarded/applovin_rewarded_ads.dart';
|
|
|
|
|
import 'utils/ads_exception.dart';
|
|
|
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
|
|
|
|
|
|
|
|
|
part 'ads_global_property.dart';
|
|
|
|
|
|
|
|
|
|
/// Created by Haoyi on 2022/3/2
|
|
|
|
|
|
|
|
|
|
class AdsManager extends AdsManagerDelegate {
|
|
|
|
|
static final AdsManager _instance = AdsManager._();
|
|
|
|
|
|
|
|
|
|
static AdsManager get instance => _instance;
|
|
|
|
|
|
|
|
|
|
AdsManager._();
|
|
|
|
|
|
|
|
|
|
final Map<AdUnitId, Ads> interstitialAds = {};
|
|
|
|
|
|
|
|
|
|
final Map<AdUnitId, ApplovinRewardedAds> rewardsAds = {};
|
|
|
|
|
|
2024-04-11 05:18:00 +00:00
|
|
|
final AdImpressionController adImpressionController =
|
|
|
|
|
AdImpressionController();
|
2023-12-21 09:14:40 +00:00
|
|
|
|
|
|
|
|
final BehaviorSubject<AdsConfig> _adsConfigSubject =
|
|
|
|
|
BehaviorSubject.seeded(AdsConfig.defaultAdsConfig);
|
|
|
|
|
|
|
|
|
|
final BehaviorSubject<AdsProfile> _adsProfileSubject =
|
|
|
|
|
BehaviorSubject.seeded(GuruApp.instance.adsProfile);
|
|
|
|
|
|
2024-04-11 05:18:00 +00:00
|
|
|
final BehaviorSubject<bool> _initializedSubject =
|
|
|
|
|
BehaviorSubject.seeded(false);
|
2023-12-21 09:14:40 +00:00
|
|
|
|
2024-04-11 05:18:00 +00:00
|
|
|
final BehaviorSubject<bool> noBannerAndInterstitialAdsSubject =
|
|
|
|
|
BehaviorSubject.seeded(false);
|
2023-12-21 09:14:40 +00:00
|
|
|
|
|
|
|
|
final Map<String, dynamic> adsGlobalProperties = <String, dynamic>{};
|
|
|
|
|
|
|
|
|
|
static const Set<String> _reservedKeywords = {
|
|
|
|
|
"app_version",
|
|
|
|
|
"lt",
|
|
|
|
|
"paid",
|
|
|
|
|
"blv",
|
|
|
|
|
"os_version",
|
|
|
|
|
"connection"
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-11 05:18:00 +00:00
|
|
|
static const List<int> ltSamples = [
|
|
|
|
|
0,
|
|
|
|
|
1,
|
|
|
|
|
2,
|
|
|
|
|
3,
|
|
|
|
|
4,
|
|
|
|
|
5,
|
|
|
|
|
6,
|
|
|
|
|
14,
|
|
|
|
|
30,
|
|
|
|
|
60,
|
|
|
|
|
90,
|
|
|
|
|
120,
|
|
|
|
|
180
|
|
|
|
|
];
|
2023-12-21 09:14:40 +00:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Stream<bool> get observableInitialized => _initializedSubject.stream;
|
|
|
|
|
|
2024-04-11 05:18:00 +00:00
|
|
|
ConnectivityResult get connectivityStatus => NetworkUtils.currentConnectivityStatus;
|
|
|
|
|
|
|
|
|
|
Stream<ConnectivityResult> get observableConnectivityStatus =>
|
|
|
|
|
NetworkUtils.observableConnectivityStatus;
|
2023-12-21 09:14:40 +00:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Stream<bool> get observableNoAds => noBannerAndInterstitialAdsSubject.stream;
|
|
|
|
|
|
|
|
|
|
final CompositeSubscription subscriptions = CompositeSubscription();
|
|
|
|
|
|
|
|
|
|
AdsProfile get adsProfile => _adsProfileSubject.value;
|
|
|
|
|
|
|
|
|
|
AdsConfig get adsConfig => _adsConfigSubject.value;
|
|
|
|
|
|
|
|
|
|
bool get hasAmazonBannerAds => adsConfig.bannerConfig.amazonEnable;
|
|
|
|
|
|
2024-04-11 05:18:00 +00:00
|
|
|
bool get hasAmazonInterstitialAds =>
|
|
|
|
|
adsConfig.interstitialConfig.amazonEnable;
|
2023-12-21 09:14:40 +00:00
|
|
|
|
|
|
|
|
bool get hasAmazonAds => hasAmazonBannerAds || hasAmazonInterstitialAds;
|
|
|
|
|
|
2024-04-11 05:18:00 +00:00
|
|
|
final BehaviorSubject<Map<String, String>> keywordsSubject =
|
|
|
|
|
BehaviorSubject.seeded({});
|
2023-12-21 09:14:40 +00:00
|
|
|
|
|
|
|
|
Stream<Map<String, String>> get observableKeywords => keywordsSubject.stream;
|
|
|
|
|
|
|
|
|
|
Map<String, String> get adsKeywords => keywordsSubject.value;
|
|
|
|
|
|
|
|
|
|
String? consentTestDeviceId;
|
|
|
|
|
|
|
|
|
|
int? consentDebugGeography;
|
|
|
|
|
|
|
|
|
|
static final RegExp _nonAlphaNumeric = RegExp('[^a-zA-Z0-9_]');
|
|
|
|
|
static final RegExp _alpha = RegExp('[a-zA-Z]');
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
bool get isPurchasedNoAd => noBannerAndInterstitialAdsSubject.value;
|
|
|
|
|
|
|
|
|
|
void setProperty(String key, String value) {
|
|
|
|
|
adsGlobalProperties[key] = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setNoAds(bool noAds) {
|
|
|
|
|
noBannerAndInterstitialAdsSubject.addIfChanged(noAds);
|
|
|
|
|
GuruSettings.instance.isNoAds.set(noAds);
|
2024-04-11 05:18:00 +00:00
|
|
|
GuruAnalytics.instance
|
|
|
|
|
.setUserProperty("user_type", noAds ? "noads" : "default");
|
2023-12-21 09:14:40 +00:00
|
|
|
setProperty("user_type", noAds ? "noads" : "default");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ensureInitialize() {}
|
|
|
|
|
|
|
|
|
|
void listenIap() {
|
2024-04-11 05:18:00 +00:00
|
|
|
final obs = Rx.combineLatest2<bool, AssetsStore<Asset>,
|
|
|
|
|
Tuple2<bool, AssetsStore<Asset>>>(
|
2023-12-21 09:14:40 +00:00
|
|
|
IapManager.instance.observableAvailable,
|
|
|
|
|
IapManager.instance.observableAssetStore,
|
|
|
|
|
(a, b) => Tuple2(a, b));
|
|
|
|
|
subscriptions.add(obs.listen((tuple) {
|
|
|
|
|
final available = tuple.item1;
|
|
|
|
|
final purchasedStore = tuple.item2;
|
|
|
|
|
if (available && purchasedStore.isActive) {
|
2024-04-11 05:18:00 +00:00
|
|
|
final tempIsNoAds = purchasedStore
|
|
|
|
|
.existsAssets(GuruApp.instance.productProfile.noAdsCapIds);
|
2023-12-21 09:14:40 +00:00
|
|
|
final isNoAds = isPurchasedNoAd;
|
2024-04-11 05:18:00 +00:00
|
|
|
Log.i(
|
|
|
|
|
"purchased store changed active! tempIsNoAds:$tempIsNoAds isNoAds:$isNoAds",
|
2023-12-21 09:14:40 +00:00
|
|
|
syncFirebase: true);
|
|
|
|
|
if (isNoAds != tempIsNoAds) {
|
|
|
|
|
if (!tempIsNoAds) {
|
|
|
|
|
GuruAnalytics.instance.logException(NoAdsException(
|
|
|
|
|
"The payment system is abnormal, it shouldn't appear that the purchased item become unpurchased"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setNoAds(tempIsNoAds);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
}
|
2024-04-11 05:18:00 +00:00
|
|
|
static bool initializedSdk = false;
|
2023-12-21 09:14:40 +00:00
|
|
|
Future initialize({SaasUser? saasUser}) async {
|
|
|
|
|
_adsProfileSubject.addEx(GuruApp.instance.adsProfile);
|
|
|
|
|
await initEnv();
|
2024-04-11 05:18:00 +00:00
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
} 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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-12-21 09:14:40 +00:00
|
|
|
|
|
|
|
|
subscriptions.add(RemoteConfigManager.instance.observeConfig().listen((_) {
|
|
|
|
|
refreshAdsConfig();
|
|
|
|
|
}, onError: (error, stacktrace) {
|
2024-04-11 05:18:00 +00:00
|
|
|
Log.i("init config error!",
|
|
|
|
|
tag: "Ads", error: error, stackTrace: stacktrace);
|
2023-12-21 09:14:40 +00:00
|
|
|
}));
|
|
|
|
|
listenIap();
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-11 05:18:00 +00:00
|
|
|
// 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;
|
|
|
|
|
// }
|
|
|
|
|
// });
|
|
|
|
|
// }
|
2023-12-21 09:14:40 +00:00
|
|
|
|
|
|
|
|
void initAdsProfile() {
|
|
|
|
|
final _hasAmazonBannerAds = hasAmazonBannerAds;
|
|
|
|
|
final _hasAmazonInterstitialAds = hasAmazonInterstitialAds;
|
|
|
|
|
final _hasAmazonAds = _hasAmazonBannerAds || _hasAmazonInterstitialAds;
|
|
|
|
|
final defaultAdsProfile = GuruApp.instance.adsProfile;
|
|
|
|
|
final strategyInterstitialIds = adsConfig.strategyAdsConfig.interstitialIds;
|
|
|
|
|
final newAdsProfile = adsProfile.copyWith(
|
|
|
|
|
amazonAppId: _hasAmazonAds ? defaultAdsProfile.amazonAppId : null,
|
2024-04-11 05:18:00 +00:00
|
|
|
amazonBannerSlotId:
|
|
|
|
|
_hasAmazonBannerAds ? defaultAdsProfile.amazonBannerSlotId : null,
|
|
|
|
|
amazonInterstitialSlotId: _hasAmazonInterstitialAds
|
|
|
|
|
? defaultAdsProfile.amazonInterstitialSlotId
|
|
|
|
|
: null,
|
2023-12-21 09:14:40 +00:00
|
|
|
strategyInterstitialIds: strategyInterstitialIds);
|
|
|
|
|
_adsProfileSubject.addEx(newAdsProfile);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future initEnv() async {
|
2024-04-11 05:18:00 +00:00
|
|
|
final adsPropertyBundle =
|
|
|
|
|
await AppProperty.getInstance().loadValuesByTag(PropertyTags.ads);
|
2023-12-21 09:14:40 +00:00
|
|
|
final isNoAds = adsPropertyBundle.getBool(PropertyKeys.isNoAds) ?? false;
|
|
|
|
|
|
2024-04-11 05:18:00 +00:00
|
|
|
consentTestDeviceId =
|
|
|
|
|
adsPropertyBundle.getString(PropertyKeys.admobConsentTestDeviceId);
|
|
|
|
|
consentDebugGeography =
|
|
|
|
|
adsPropertyBundle.getInt(PropertyKeys.admobConsentDebugGeography);
|
2023-12-21 09:14:40 +00:00
|
|
|
noBannerAndInterstitialAdsSubject.addIfChanged(isNoAds);
|
2024-04-11 05:18:00 +00:00
|
|
|
GuruAnalytics.instance
|
|
|
|
|
.setUserProperty("user_type", isNoAds ? "noads" : "default");
|
2023-12-21 09:14:40 +00:00
|
|
|
setProperty("user_type", isNoAds ? "noads" : "default");
|
|
|
|
|
|
|
|
|
|
final result = await Connectivity().checkConnectivity().catchError((error) {
|
|
|
|
|
Log.w("checkConnectivity error! $error");
|
|
|
|
|
});
|
2024-04-11 05:18:00 +00:00
|
|
|
// connectivityStatusSubject.addEx(result);
|
2023-12-21 09:14:40 +00:00
|
|
|
setProperty("connectivityStatus", result.toString());
|
|
|
|
|
|
|
|
|
|
refreshAdsConfig();
|
|
|
|
|
initAdsProfile();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future initSdk(
|
|
|
|
|
{SaasUser? saasUser,
|
|
|
|
|
required VoidCallback onInitialized,
|
|
|
|
|
Duration retryPeriod = const Duration(seconds: 15)}) async {
|
|
|
|
|
final _adsProfile = adsProfile;
|
|
|
|
|
bool initializeResult = false;
|
|
|
|
|
if (GuruApp.instance.appSpec.deployment.adsCompliantInitialization &&
|
|
|
|
|
adsConfig.commonAdsConfig.compliantInitialization &&
|
|
|
|
|
Platform.isAndroid) {
|
|
|
|
|
initializeResult = await GuruApplovinFlutter.instance
|
|
|
|
|
.gatherConsentAndInitialize(
|
|
|
|
|
userId: saasUser?.uid,
|
|
|
|
|
amazonAppId: _adsProfile.amazonAppId?.id,
|
|
|
|
|
pubmaticStoreUrl: adsProfile.pubmaticAppStoreUrl,
|
|
|
|
|
testDeviceId: consentTestDeviceId,
|
|
|
|
|
debugGeography: consentDebugGeography)
|
|
|
|
|
.catchError((err) => false) ??
|
|
|
|
|
false;
|
|
|
|
|
} else {
|
|
|
|
|
initializeResult = await GuruApplovinFlutter.instance
|
|
|
|
|
.initialize(
|
|
|
|
|
userId: saasUser?.uid,
|
|
|
|
|
amazonAppId: _adsProfile.amazonAppId?.id,
|
|
|
|
|
pubmaticStoreUrl: adsProfile.pubmaticAppStoreUrl)
|
|
|
|
|
.catchError((err) => false) ??
|
|
|
|
|
false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_initializedSubject.addEx(initializeResult);
|
|
|
|
|
Log.d("MAX sdk initialize result: $initializeResult");
|
|
|
|
|
if (initializeResult) {
|
|
|
|
|
try {
|
|
|
|
|
await initKeywords();
|
|
|
|
|
} catch (error, stacktrace) {
|
|
|
|
|
Log.e("initKeywords error! $error $stacktrace", tag: "Ads");
|
|
|
|
|
}
|
|
|
|
|
onInitialized.call();
|
|
|
|
|
} else {
|
|
|
|
|
Future.delayed(retryPeriod, () {
|
|
|
|
|
initSdk(onInitialized: onInitialized, retryPeriod: retryPeriod);
|
|
|
|
|
});
|
|
|
|
|
Log.w("Ads Initialize error! retry", tag: "Ads", syncFirebase: true);
|
|
|
|
|
}
|
|
|
|
|
return initializeResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void checkAndPreload(
|
2024-04-11 05:18:00 +00:00
|
|
|
{AdsValidator? rewardedValidator,
|
|
|
|
|
AdsValidator? interstitialValidator}) async {
|
2023-12-21 09:14:40 +00:00
|
|
|
final canPreloadReward =
|
|
|
|
|
await adsConfig.rewardedConfig.canPreload(validator: rewardedValidator);
|
|
|
|
|
if (canPreloadReward) {
|
|
|
|
|
Log.d("preload reward canPreload!");
|
|
|
|
|
final reward = await getRewardsAds();
|
|
|
|
|
if (reward.loadCount <= 0) {
|
|
|
|
|
reward.preload();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-11 05:18:00 +00:00
|
|
|
final canPreloadInterstitial = await adsConfig.interstitialConfig
|
|
|
|
|
.canPreload(validator: interstitialValidator);
|
2023-12-21 09:14:40 +00:00
|
|
|
if (!isPurchasedNoAd && canPreloadInterstitial) {
|
|
|
|
|
Log.d("preload interstitial canPreload!");
|
|
|
|
|
final interstitial = await getInterstitialAds();
|
|
|
|
|
if (interstitial is AdsAudit && interstitial.loadCount <= 0) {
|
|
|
|
|
interstitial.preload();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void retry() async {
|
|
|
|
|
final canPreloadReward = await adsConfig.rewardedConfig.canPreload();
|
|
|
|
|
if (canPreloadReward) {
|
|
|
|
|
final reward = await getRewardsAds();
|
|
|
|
|
reward.retry();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final canPreload = await adsConfig.interstitialConfig.canPreload();
|
|
|
|
|
if (canPreload) {
|
|
|
|
|
Log.d("preload interstitial canPreload!");
|
|
|
|
|
final interstitial = await getInterstitialAds();
|
|
|
|
|
interstitial.retry();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int _nearestLt(int low, int high, int lt) {
|
|
|
|
|
if (low > high) {
|
|
|
|
|
return -low;
|
|
|
|
|
}
|
|
|
|
|
while (low <= high) {
|
|
|
|
|
final int mid = (low + high) >> 1;
|
|
|
|
|
if (lt == ltSamples[mid]) {
|
|
|
|
|
return mid;
|
|
|
|
|
} else if (lt < ltSamples[mid]) {
|
|
|
|
|
return _nearestLt(low, mid - 1, lt);
|
|
|
|
|
} else {
|
|
|
|
|
return _nearestLt(mid + 1, high, lt);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -low;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<int> getKeywordLt() async {
|
|
|
|
|
final latestLtDate = await AppProperty.getInstance().getLatestLtDate();
|
|
|
|
|
final dateNum = DateTimeUtils.yyyyMMddUtcNum;
|
|
|
|
|
|
|
|
|
|
int lt = await AppProperty.getInstance().getLtDays();
|
|
|
|
|
if (dateNum != latestLtDate) {
|
|
|
|
|
if (dateNum > latestLtDate) {
|
|
|
|
|
lt = lt + 1;
|
|
|
|
|
await AppProperty.getInstance().setLtDays(lt);
|
|
|
|
|
}
|
|
|
|
|
await AppProperty.getInstance().setLatestLtDate(dateNum);
|
|
|
|
|
}
|
2024-04-11 05:18:00 +00:00
|
|
|
final idx = _nearestLt(0, ltSamples.lastIndex, lt)
|
|
|
|
|
.abs()
|
|
|
|
|
.clamp(0, ltSamples.lastIndex);
|
2023-12-21 09:14:40 +00:00
|
|
|
Log.d(
|
|
|
|
|
"getKeywordLt: installTime:$latestLtDate now:$dateNum lt:$lt keywordLt:${ltSamples[idx]}");
|
|
|
|
|
|
|
|
|
|
return ltSamples[idx];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> getOSVersion() async {
|
|
|
|
|
try {
|
|
|
|
|
final deviceInfo = DeviceInfoPlugin();
|
|
|
|
|
if (Platform.isAndroid) {
|
|
|
|
|
final info = await deviceInfo.androidInfo;
|
|
|
|
|
return info.version.release;
|
|
|
|
|
} else if (Platform.isIOS) {
|
|
|
|
|
final info = await deviceInfo.iosInfo;
|
|
|
|
|
return info.systemVersion;
|
|
|
|
|
}
|
|
|
|
|
} catch (error, stacktrace) {
|
|
|
|
|
Log.w("getOSVersion error! $error");
|
|
|
|
|
}
|
|
|
|
|
return "unknown";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> getConnection() async {
|
|
|
|
|
try {
|
|
|
|
|
final connectivity = await Connectivity().checkConnectivity();
|
|
|
|
|
return connectivity.toString();
|
|
|
|
|
} catch (error, stacktrace) {
|
|
|
|
|
Log.w("getConnection error! $error");
|
|
|
|
|
}
|
|
|
|
|
return "unknown";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future initKeywords() async {
|
|
|
|
|
final paidUser = await AppProperty.getInstance().isPaidUser();
|
|
|
|
|
final version = Settings.get().version.get();
|
|
|
|
|
final lt = await getKeywordLt();
|
|
|
|
|
final osVersion = await getOSVersion();
|
|
|
|
|
final connection = await getConnection();
|
|
|
|
|
final keywords = <String, String>{
|
|
|
|
|
"app_version": version,
|
|
|
|
|
"paid": paidUser ? "true" : "false",
|
|
|
|
|
"lt": lt.toString(),
|
|
|
|
|
"os_version": osVersion,
|
|
|
|
|
"connection": connection
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
keywordsSubject.stream.listen((keywords) {
|
|
|
|
|
if (keywords.isNotEmpty) {
|
|
|
|
|
Log.i("invoke setKeywords: $keywords", tag: "Ads");
|
|
|
|
|
GuruApplovinFlutter.instance.setKeywords(keywords);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
keywordsSubject.addEx(keywords);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future restoreKeywords(Map<String, String> keywords) async {
|
|
|
|
|
if (GuruSettings.instance.debugMode.get()) {
|
|
|
|
|
final newKeywords = Map.of(keywords);
|
|
|
|
|
keywordsSubject.addEx(newKeywords);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setKeyword(String key, String value, {bool debugForce = false}) {
|
|
|
|
|
if (!GuruSettings.instance.debugMode.get() || !debugForce) {
|
|
|
|
|
if (_reservedKeywords.contains(key)) {
|
2024-04-11 05:18:00 +00:00
|
|
|
Log.w("setKeyword error! the key($key) is reserved and cannot be used!",
|
|
|
|
|
tag: "Ads");
|
2023-12-21 09:14:40 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (key.isEmpty ||
|
|
|
|
|
key.length > 36 ||
|
|
|
|
|
key.indexOf(_alpha) != 0 ||
|
|
|
|
|
key.contains(_nonAlphaNumeric)) {
|
2024-04-11 05:18:00 +00:00
|
|
|
Log.w(
|
|
|
|
|
"setKeyword error! the key($key) must contain 1 to 36 alphanumeric characters.",
|
2023-12-21 09:14:40 +00:00
|
|
|
tag: "Ads");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
final newKeywords = Map.of(keywordsSubject.value);
|
|
|
|
|
newKeywords[key] = value;
|
|
|
|
|
keywordsSubject.addEx(newKeywords);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void removeKeyword(String key, {bool debugForce = false}) {
|
|
|
|
|
if (!GuruSettings.instance.debugMode.get() || !debugForce) {
|
|
|
|
|
if (_reservedKeywords.contains(key)) {
|
2024-04-11 05:18:00 +00:00
|
|
|
Log.w(
|
|
|
|
|
"removeKeyword error! the key($key) is reserved and cannot be used!",
|
|
|
|
|
tag: "Ads");
|
2023-12-21 09:14:40 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (key.isEmpty ||
|
|
|
|
|
key.length > 36 ||
|
|
|
|
|
key.indexOf(_alpha) != 0 ||
|
|
|
|
|
key.contains(_nonAlphaNumeric)) {
|
2024-04-11 05:18:00 +00:00
|
|
|
Log.w(
|
|
|
|
|
"removeKeyword error! the key($key) must contain 1 to 36 alphanumeric characters.",
|
2023-12-21 09:14:40 +00:00
|
|
|
tag: "Ads");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
final newKeywords = Map.of(keywordsSubject.value);
|
|
|
|
|
newKeywords.remove(key);
|
|
|
|
|
keywordsSubject.addEx(newKeywords);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<int> checkConsentDialogStatus() async {
|
|
|
|
|
return await GuruApplovinFlutter.instance.checkConsentDialogStatus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<bool> afterAcceptPrivacy(bool consentResult) async {
|
|
|
|
|
return await GuruApplovinFlutter.instance.afterAcceptPrivacy(consentResult);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool testParseAdsDefaultConfig() {
|
2024-04-11 05:18:00 +00:00
|
|
|
final iadsConfigString =
|
|
|
|
|
RemoteConfigReservedConstants.getDefaultConfigString(
|
|
|
|
|
RemoteConfigReservedConstants.iadsConfig) ??
|
|
|
|
|
"";
|
|
|
|
|
final radsConfigString =
|
|
|
|
|
RemoteConfigReservedConstants.getDefaultConfigString(
|
|
|
|
|
RemoteConfigReservedConstants.radsConfig) ??
|
|
|
|
|
"";
|
|
|
|
|
final badsConfigString =
|
|
|
|
|
RemoteConfigReservedConstants.getDefaultConfigString(
|
|
|
|
|
RemoteConfigReservedConstants.badsConfig) ??
|
|
|
|
|
"";
|
|
|
|
|
final iosAttConfigString =
|
|
|
|
|
RemoteConfigReservedConstants.getDefaultConfigString(
|
|
|
|
|
RemoteConfigReservedConstants.iosAttConfig) ??
|
|
|
|
|
"";
|
2023-12-21 09:14:40 +00:00
|
|
|
try {
|
2024-04-11 05:18:00 +00:00
|
|
|
final adInterstitial =
|
|
|
|
|
AdInterstitialConfig.fromJson(json.decode(iadsConfigString));
|
2023-12-21 09:14:40 +00:00
|
|
|
final adBanner = AdBannerConfig.fromJson(json.decode(badsConfigString));
|
2024-04-11 05:18:00 +00:00
|
|
|
final iosAttConfig =
|
|
|
|
|
IOSAttConfig.fromJson(json.decode(iosAttConfigString));
|
2023-12-21 09:14:40 +00:00
|
|
|
Log.d("==== ADS AdsConfig ====");
|
|
|
|
|
Log.d(" ---> [INTERSTITIAL]: $iadsConfigString");
|
|
|
|
|
Log.d(" ---> [BANNER]: $badsConfigString");
|
|
|
|
|
Log.d(" ---> [IOSATT]: $iosAttConfigString");
|
|
|
|
|
Log.d("=======================");
|
|
|
|
|
|
|
|
|
|
_adsConfigSubject.addEx(AdsConfig.build(
|
2024-04-11 05:18:00 +00:00
|
|
|
interstitialConfig: adInterstitial,
|
|
|
|
|
bannerConfig: adBanner,
|
|
|
|
|
iosAttConfig: iosAttConfig));
|
2023-12-21 09:14:40 +00:00
|
|
|
return true;
|
|
|
|
|
} catch (error, stacktrace) {
|
|
|
|
|
Log.e("refreshAdsConfig error $error $stacktrace");
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool refreshAdsConfig() {
|
|
|
|
|
try {
|
|
|
|
|
final commonAdsConfig = RemoteConfigManager.instance.getCommonAdsConfig();
|
|
|
|
|
final adInterstitial = RemoteConfigManager.instance.getIadsConfig();
|
|
|
|
|
final adReward = RemoteConfigManager.instance.getRadsConfig();
|
|
|
|
|
final adBanner = RemoteConfigManager.instance.getBadsConfig();
|
2024-04-11 05:18:00 +00:00
|
|
|
final strategyAdsConfig =
|
|
|
|
|
RemoteConfigManager.instance.getStrategyAdsConfig();
|
2023-12-21 09:14:40 +00:00
|
|
|
final iosAttConfig = RemoteConfigManager.instance.getIOSAttConfig();
|
|
|
|
|
Log.d("==== ADS AdsConfig ====", tag: PropertyTags.ads);
|
2024-04-11 05:18:00 +00:00
|
|
|
Log.d(" ---> [COMMON]: ${commonAdsConfig.toJson()}",
|
|
|
|
|
tag: PropertyTags.ads);
|
|
|
|
|
Log.d(" ---> [INTERSTITIAL]: ${adInterstitial.toJson()}",
|
|
|
|
|
tag: PropertyTags.ads);
|
2023-12-21 09:14:40 +00:00
|
|
|
Log.d(" ---> [REWARD]: ${adReward.toJson()}", tag: PropertyTags.ads);
|
|
|
|
|
Log.d(" ---> [BANNER]: ${adBanner.toJson()}", tag: PropertyTags.ads);
|
2024-04-11 05:18:00 +00:00
|
|
|
Log.d(" ---> [STRATEGY]: ${strategyAdsConfig.toJson()}",
|
|
|
|
|
tag: PropertyTags.ads);
|
2023-12-21 09:14:40 +00:00
|
|
|
Log.d(" ---> [IOSATT]: ${iosAttConfig.toJson()}", tag: PropertyTags.ads);
|
|
|
|
|
Log.d("=======================", tag: PropertyTags.ads);
|
|
|
|
|
_adsConfigSubject.addEx(AdsConfig.build(
|
|
|
|
|
commonAdsConfig: commonAdsConfig,
|
|
|
|
|
interstitialConfig: adInterstitial,
|
|
|
|
|
rewardedConfig: adReward,
|
|
|
|
|
bannerConfig: adBanner,
|
|
|
|
|
strategyAdsConfig: strategyAdsConfig,
|
|
|
|
|
iosAttConfig: iosAttConfig));
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error, stacktrace) {
|
|
|
|
|
Log.e("refreshAdsConfig error $error $stacktrace");
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<Ads> getInterstitialAds() async {
|
|
|
|
|
final _adsProfile = adsProfile;
|
|
|
|
|
final strategyInterstitialIds = adsProfile.strategyInterstitialIds ?? [];
|
|
|
|
|
Ads? ad;
|
|
|
|
|
if (strategyInterstitialIds.isNotEmpty) {
|
|
|
|
|
if (strategyInterstitialIds.length > 1) {
|
|
|
|
|
ad = interstitialAds[strategyInterstitialIds.first.adUnitId] ??=
|
|
|
|
|
MaxStrategyInterstitialAds.create(strategyInterstitialIds)..init();
|
|
|
|
|
} else {
|
|
|
|
|
ad = interstitialAds[strategyInterstitialIds.first.adUnitId] ??=
|
2024-04-11 05:18:00 +00:00
|
|
|
ApplovinInterstitialAds.create(
|
|
|
|
|
strategyInterstitialIds.first.adUnitId,
|
2023-12-21 09:14:40 +00:00
|
|
|
strategyInterstitialIds.first.amazonAdSlotId)
|
|
|
|
|
..init();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2024-04-11 05:18:00 +00:00
|
|
|
ad = interstitialAds[_adsProfile.interstitialId] ??=
|
|
|
|
|
ApplovinInterstitialAds.create(
|
|
|
|
|
_adsProfile.interstitialId, _adsProfile.amazonInterstitialSlotId)
|
|
|
|
|
..init();
|
2023-12-21 09:14:40 +00:00
|
|
|
}
|
|
|
|
|
return ad;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<ApplovinRewardedAds> getRewardsAds() async {
|
|
|
|
|
final _adsProfile = adsProfile;
|
|
|
|
|
ApplovinRewardedAds? ad = rewardsAds[_adsProfile.rewardsId];
|
|
|
|
|
if (ad == null) {
|
|
|
|
|
ad = ApplovinRewardedAds.create(_adsProfile.rewardsId,
|
|
|
|
|
adAmazonSlotId: _adsProfile.amazonRewardedSlotId)
|
|
|
|
|
..init();
|
|
|
|
|
rewardsAds[_adsProfile.rewardsId] = ad;
|
|
|
|
|
}
|
|
|
|
|
return ad;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-07 03:46:50 +00:00
|
|
|
Future<int> requestGdpr({int? debugGeography, String? testDeviceId}) async {
|
2024-04-11 05:18:00 +00:00
|
|
|
Log.d(
|
|
|
|
|
"requestGdpr! debugGeography:$debugGeography testDeviceId:$testDeviceId",
|
|
|
|
|
tag: "Ads");
|
2023-12-21 09:14:40 +00:00
|
|
|
// adb logcat -s UserMessagingPlatform
|
|
|
|
|
// Use new ConsentDebugSettings.Builder().addTestDeviceHashedId("xxxx") to set this as a debug device.
|
2024-04-11 05:18:00 +00:00
|
|
|
final result = await GuruApplovinFlutter.instance.requestGdpr(
|
|
|
|
|
debugGeography: debugGeography, testDeviceId: testDeviceId);
|
2024-03-07 03:46:50 +00:00
|
|
|
final consentResult = await GuruAnalytics.instance.refreshConsents();
|
|
|
|
|
Log.d("requestGdpr result:$result consentResult:$consentResult");
|
|
|
|
|
return result;
|
2023-12-21 09:14:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<bool> resetGdpr() {
|
|
|
|
|
return GuruApplovinFlutter.instance.resetGdpr();
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-07 03:46:50 +00:00
|
|
|
Future<bool> updateOrientation(int orientation) async {
|
2024-04-11 05:18:00 +00:00
|
|
|
final result =
|
|
|
|
|
await GuruApplovinFlutter.instance.updateOrientation(orientation);
|
2024-03-07 03:46:50 +00:00
|
|
|
return result == true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-21 09:14:40 +00:00
|
|
|
@override
|
2024-04-11 05:18:00 +00:00
|
|
|
Future<ApplovinBannerAds> createBannerAds(
|
|
|
|
|
{String? scene, AdsLifecycleObserver? observer}) async {
|
2023-12-21 09:14:40 +00:00
|
|
|
final _adsProfile = adsProfile;
|
2024-04-11 05:18:00 +00:00
|
|
|
return ApplovinBannerAds.create(
|
|
|
|
|
_adsProfile.bannerId, _adsProfile.amazonBannerSlotId,
|
2023-12-21 09:14:40 +00:00
|
|
|
scene: scene, observer: observer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AdCause canShowInterstitial(String scene) {
|
|
|
|
|
if (isPurchasedNoAd) {
|
|
|
|
|
return AdCause.noAds;
|
|
|
|
|
}
|
2024-03-07 03:46:50 +00:00
|
|
|
final hiddenAt = AdsManager.instance.latestFullscreenAdsHiddenTimestamps;
|
2023-12-21 09:14:40 +00:00
|
|
|
final now = DateTimeUtils.currentTimeInMillis();
|
2024-04-11 05:18:00 +00:00
|
|
|
final impGapInMillis = AdsManager.instance.adsConfig.interstitialConfig
|
|
|
|
|
.getSceneImpGapInSeconds(scene) *
|
|
|
|
|
1000;
|
2023-12-21 09:14:40 +00:00
|
|
|
Log.d(
|
|
|
|
|
"canShowInterstitial($scene): now:$now latestFullscreenAdsHiddenTimestamps:$latestFullscreenAdsHiddenTimestamps hiddenAt:$hiddenAt impGapInMillis:$impGapInMillis",
|
|
|
|
|
tag: "Ads");
|
2024-03-07 03:46:50 +00:00
|
|
|
if ((now - hiddenAt) < impGapInMillis) {
|
2023-12-21 09:14:40 +00:00
|
|
|
Log.d("show ads too frequency", syncFirebase: true);
|
|
|
|
|
return AdCause.tooFrequent;
|
|
|
|
|
}
|
|
|
|
|
return AdCause.success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
2024-04-11 05:18:00 +00:00
|
|
|
Future<AdCause> validateInterstitial(String? scene,
|
|
|
|
|
{AdsValidator? validator}) {
|
2023-12-21 09:14:40 +00:00
|
|
|
final interstitialConfig = adsConfig.interstitialConfig;
|
|
|
|
|
return interstitialConfig.check(scene ?? "", validator: validator);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<AdCause> validateRewards(String? scene, {AdsValidator? validator}) {
|
|
|
|
|
final rewardedConfig = adsConfig.rewardedConfig;
|
|
|
|
|
return rewardedConfig.check(scene ?? "", validator: validator);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<AdCause> validateBanner(String? scene, {AdsValidator? validator}) {
|
|
|
|
|
final rewardedConfig = adsConfig.bannerConfig;
|
|
|
|
|
return rewardedConfig.check(scene ?? "", validator: validator);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
dynamic getConfig(String type) {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case "bannerAutoDisposeInterval":
|
|
|
|
|
return adsConfig.bannerConfig.autoDisposeIntervalInMinutes;
|
|
|
|
|
case "allowInterstitialAsAlternativeReward":
|
2024-04-11 05:18:00 +00:00
|
|
|
return GuruApp
|
|
|
|
|
.instance.appSpec.deployment.allowInterstitialAsAlternativeReward;
|
2023-12-21 09:14:40 +00:00
|
|
|
case "showInternalAdsWhenBannerUnavailable":
|
2024-04-11 05:18:00 +00:00
|
|
|
return GuruApp
|
|
|
|
|
.instance.appSpec.deployment.showInternalAdsWhenBannerUnavailable;
|
2023-12-21 09:14:40 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|