811 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Dart
		
	
	
			
		
		
	
	
			811 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Dart
		
	
	
| /// Created by Haoyi on 2022/8/24
 | ||
| 
 | ||
| import 'dart:collection';
 | ||
| import 'dart:core';
 | ||
| import 'dart:io';
 | ||
| 
 | ||
| import 'package:adjust_sdk/adjust_third_party_sharing.dart';
 | ||
| import 'package:firebase_crashlytics/firebase_crashlytics.dart';
 | ||
| import 'package:firebase_remote_config/firebase_remote_config.dart';
 | ||
| import 'package:flutter/foundation.dart';
 | ||
| import 'package:guru_analytics_flutter/event_logger.dart';
 | ||
| import 'package:guru_analytics_flutter/event_logger_common.dart';
 | ||
| import 'package:guru_analytics_flutter/events_constants.dart';
 | ||
| import 'package:guru_analytics_flutter/guru/guru_event_logger.dart';
 | ||
| import 'package:guru_analytics_flutter/guru/guru_statistic.dart';
 | ||
| import 'package:guru_app/account/account_data_store.dart';
 | ||
| import 'package:guru_app/ads/ads_manager.dart';
 | ||
| import 'package:guru_app/ads/core/ads_config.dart';
 | ||
| import 'package:guru_app/aigc/bi/ai_bi.dart';
 | ||
| import 'package:guru_app/analytics/abtest/abtest_model.dart';
 | ||
| import 'package:guru_app/analytics/data/analytics_model.dart';
 | ||
| import 'package:guru_app/analytics/strategy/guru_analytics_strategy.dart';
 | ||
| import 'package:device_info_plus/device_info_plus.dart';
 | ||
| import 'package:guru_app/firebase/remoteconfig/remote_config_manager.dart';
 | ||
| import 'package:guru_app/guru_app.dart';
 | ||
| import 'package:guru_app/property/app_property.dart';
 | ||
| import 'package:guru_app/property/property_keys.dart';
 | ||
| import 'package:guru_app/property/runtime_property.dart';
 | ||
| import 'package:guru_app/property/settings/guru_settings.dart';
 | ||
| import 'package:guru_platform_data/guru_platform_data.dart';
 | ||
| import 'package:guru_utils/collection/collectionutils.dart';
 | ||
| import 'package:guru_utils/datetime/datetime_utils.dart';
 | ||
| import 'package:guru_utils/device/device_info.dart';
 | ||
| import 'package:guru_utils/device/device_utils.dart';
 | ||
| import 'package:guru_utils/analytics/analytics.dart';
 | ||
| import 'package:guru_utils/network/network_utils.dart';
 | ||
| import 'package:intl/intl.dart';
 | ||
| import 'package:guru_utils/extensions/extensions.dart';
 | ||
| import 'package:adjust_sdk/adjust_ad_revenue.dart';
 | ||
| import 'package:adjust_sdk/adjust_config.dart';
 | ||
| export 'package:adjust_sdk/adjust.dart';
 | ||
| 
 | ||
| part 'modules/ads_analytics.dart';
 | ||
| 
 | ||
| part 'modules/adjust_aware.dart';
 | ||
| 
 | ||
| class GuruAnalytics extends Analytics with AdjustAware {
 | ||
|   bool get release => !_mock && (_enabledAnalytics || kReleaseMode);
 | ||
| 
 | ||
|   String appInstanceId = "";
 | ||
| 
 | ||
|   static bool _mock = false;
 | ||
| 
 | ||
|   static bool _enabledAnalytics = true;
 | ||
| 
 | ||
|   static GuruAnalytics instance = GuruAnalytics._();
 | ||
| 
 | ||
|   /// Name of virtual currency type.
 | ||
|   static bool initialized = false;
 | ||
| 
 | ||
|   static final Map<String, String> facebookEventMapping = {};
 | ||
| 
 | ||
|   static String currentScreen = "";
 | ||
| 
 | ||
|   static final RegExp _consentPurposeRegExp = RegExp(r"^[01]+$");
 | ||
| 
 | ||
|   static String? mockCountryCode;
 | ||
| 
 | ||
|   static const errorEventCodes = {
 | ||
|     14, // 上报事件失败
 | ||
|     22, // 网络状态不可用
 | ||
|     101, // 调用api出错
 | ||
|     102, // api返回结果错误
 | ||
|     103, // 设置cacheControl出错
 | ||
|     104, // 删除过期事件出错
 | ||
|     105, // 从数据库取事件以及更改事件状态为正在上报出错
 | ||
|     106, // dns 错误
 | ||
|   };
 | ||
| 
 | ||
|   int latestFetchStatisticTs = 0;
 | ||
| 
 | ||
|   final BehaviorSubject<GuruStatistic> guruEventStatistic =
 | ||
|       BehaviorSubject.seeded(GuruStatistic.invalid);
 | ||
| 
 | ||
|   final BehaviorSubject<Map<String, String>> abTestExperimentVariant = BehaviorSubject.seeded({});
 | ||
| 
 | ||
|   Stream<GuruStatistic> get observableGuruEventStatistic => guruEventStatistic.stream;
 | ||
| 
 | ||
|   Stream<Map<String, String>> get observableABTestExperimentVariant =>
 | ||
|       abTestExperimentVariant.stream;
 | ||
| 
 | ||
|   final BehaviorSubject<UserIdentification> userIdentificationSubject =
 | ||
|       BehaviorSubject.seeded(UserIdentification());
 | ||
| 
 | ||
|   UserIdentification get userIdentification => userIdentificationSubject.value;
 | ||
| 
 | ||
|   AppEventCapabilities get currentAppEventCapabilities => EventLogger.getCapabilities();
 | ||
| 
 | ||
|   static void setMock() {
 | ||
|     _mock = true;
 | ||
|   }
 | ||
| 
 | ||
|   static void disableAnalytics() {
 | ||
|     _enabledAnalytics = false;
 | ||
|   }
 | ||
| 
 | ||
|   static void enableAnalytics() {
 | ||
|     _enabledAnalytics = true;
 | ||
|   }
 | ||
| 
 | ||
|   GuruAnalytics._();
 | ||
| 
 | ||
|   String? getProperty(String key) {
 | ||
|     return Analytics.userProperties[key];
 | ||
|   }
 | ||
| 
 | ||
|   Future prepare() async {
 | ||
|     if (GuruApp.instance.appSpec.localABTestExperiments.isNotEmpty) {
 | ||
|       await initLocalExperiments();
 | ||
|     }
 | ||
|     RemoteConfigManager.instance.observeConfig().listen((config) {
 | ||
|       Log.i(
 | ||
|           "GuruAnalytics observeConfig changed: ${config.lastFetchStatus} ${config.lastFetchTime}");
 | ||
|       if (config.lastFetchStatus == RemoteConfigFetchStatus.success) {
 | ||
|         refreshABProperties();
 | ||
|       }
 | ||
|     });
 | ||
|   }
 | ||
| 
 | ||
|   void init() async {
 | ||
|     Log.d(
 | ||
|         "AnalyticsUtil init### Platform.localeName :${Platform.localeName} ${Intl.getCurrentLocale()}");
 | ||
|     if (!_mock && !initialized) {
 | ||
|       final analyticsConfig = RemoteConfigManager.instance.getAnalyticsConfig();
 | ||
|       EventLogger.setCapabilities(analyticsConfig.toAppEventCapabilities());
 | ||
|       EventLogger.registerTransmitter(EventTransmitter({}, defaultHook: (name, parameters) {
 | ||
|         recordEvents(name, parameters);
 | ||
|         final fbEvent = facebookEventMapping[name];
 | ||
|         if (fbEvent == null) {
 | ||
|           return;
 | ||
|         }
 | ||
|         Log.d("transmit EVENT [$name] => [$fbEvent]");
 | ||
|         EventLogger.facebookLogEvent(name: fbEvent);
 | ||
|       }));
 | ||
|       EventLogger.setGuruPriorityGetter((name, parameters) =>
 | ||
|           GuruApp.instance.conversionEvents.contains(name)
 | ||
|               ? EventPriority.EMERGENCE
 | ||
|               : EventPriority.DEFAULT);
 | ||
|       String xDeviceInfo = '';
 | ||
|       try {
 | ||
|         final deviceId = await AppProperty.getInstance().getDeviceId();
 | ||
|         final deviceInfo = await DeviceUtils.buildDeviceInfo(deviceId: deviceId);
 | ||
|         xDeviceInfo = deviceInfo.toXDeviceInfo();
 | ||
|       } catch (error, stacktrace) {
 | ||
|         Log.e("init deviceInfo error: $error, $stacktrace");
 | ||
|       }
 | ||
| 
 | ||
|       await GuruAnalyticsStrategy.instance.load();
 | ||
|       EventLogger.initialize(
 | ||
|         appId: GuruApp.instance.appSpec.details.saasAppId,
 | ||
|         deviceInfo: xDeviceInfo,
 | ||
|         delayedInSeconds: analyticsConfig.delayedInSeconds,
 | ||
|         eventExpiredInDays: analyticsConfig.expiredInDays,
 | ||
|         callback: processAnalyticsCallback,
 | ||
|         debug: true,
 | ||
|       );
 | ||
|       _initEnvProperties();
 | ||
|       _logLocale();
 | ||
|       _logDeviceType();
 | ||
|       _logFirstOpen();
 | ||
|       Future.delayed(const Duration(seconds: 1), () {
 | ||
|         initAdjust();
 | ||
|         initFbEventMapping();
 | ||
|         refreshConsents();
 | ||
|         Log.d("register transmitter");
 | ||
|       });
 | ||
|       initialized = true;
 | ||
|       // if (Platform.isAndroid) {
 | ||
|       //   _logPeerApps();
 | ||
|       // }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   Future switchSession(String oldToken, String newToken) async {
 | ||
|     _initEnvProperties();
 | ||
|     _logLocale();
 | ||
|     _logDeviceType();
 | ||
|   }
 | ||
| 
 | ||
|   Future initLocalExperiments() async {
 | ||
|     final runningExperiments = await AppProperty.getInstance().loadRunningExperiments();
 | ||
|     final experiments = GuruApp.instance.appSpec.localABTestExperiments;
 | ||
|     final validRunningExperimentKeys =
 | ||
|         runningExperiments.keys.toSet().intersection(experiments.keys.toSet());
 | ||
|     for (var experiment in experiments.values) {
 | ||
|       // 如果在已经开始的实验中,但是不在当前的实验列表中,需要删除
 | ||
|       final needRemove = runningExperiments.containsKey(experiment.name) &&
 | ||
|           !validRunningExperimentKeys.contains(experiment.name);
 | ||
|       if (needRemove) {
 | ||
|         await removeExperiment(experiment.name);
 | ||
|       } else {
 | ||
|         await _applyExperiment(experiment);
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   Future<String> refreshConsents({AnalyticsConfig? analyticsConfig}) async {
 | ||
|     final config = analyticsConfig ?? RemoteConfigManager.instance.getAnalyticsConfig();
 | ||
|     final purposeConsents = await GuruPlatformData.getPurposeConsents();
 | ||
|     Log.i("refreshConsents: '$purposeConsents'");
 | ||
|     if (purposeConsents.isEmpty) {
 | ||
|       return "";
 | ||
|     }
 | ||
| 
 | ||
|     /// 如果他不是完全使用 1,0 组成的字符串
 | ||
|     if (!_consentPurposeRegExp.hasMatch(purposeConsents)) {
 | ||
|       Log.i("invalid consents $purposeConsents");
 | ||
|       return "";
 | ||
|     }
 | ||
| 
 | ||
|     /// 获取当前的 countryCode, 判断是否在 dma country的范围内
 | ||
|     if (config.dmaCountry.isNotEmpty) {
 | ||
|       final countryCode = getCountryCode();
 | ||
|       if (!config.dmaCountry.contains(countryCode)) {
 | ||
|         Log.i("invalid country $countryCode");
 | ||
|         return "";
 | ||
|       }
 | ||
|     }
 | ||
| 
 | ||
|     final length = min(purposeConsents.length, 32);
 | ||
|     int flags = 0;
 | ||
|     for (var i = 0; i < length; i++) {
 | ||
|       flags |= (((purposeConsents[i] == "1") ? 1 : 0) << i);
 | ||
|     }
 | ||
| 
 | ||
|     final consentsData = {
 | ||
|       ConsentFieldName.adStorage: config.googleDmaGranted(ConsentType.adStorage, flags),
 | ||
|       ConsentFieldName.analyticsStorage:
 | ||
|           config.googleDmaGranted(ConsentType.analyticsStorage, flags),
 | ||
|       ConsentFieldName.adPersonalization:
 | ||
|           config.googleDmaGranted(ConsentType.adPersonalization, flags),
 | ||
|       ConsentFieldName.adUserData: config.googleDmaGranted(ConsentType.adUserData, flags),
 | ||
|     };
 | ||
| 
 | ||
|     String _flag(String key) {
 | ||
|       return consentsData[key] == true ? "1" : "0";
 | ||
|     }
 | ||
| 
 | ||
|     Log.d("setConsents consentsData: $consentsData");
 | ||
| 
 | ||
|     try {
 | ||
|       final result = await EventLogger.guru.setConsents(consentsData);
 | ||
|       Log.d("setConsents result: $result");
 | ||
|     } catch (error, stacktrace) {
 | ||
|       Log.e("setConsents error! $error, $stacktrace");
 | ||
|     }
 | ||
| 
 | ||
|     if (enabledAdjust) {
 | ||
|       AdjustThirdPartySharing adjustThirdPartySharing = AdjustThirdPartySharing(null);
 | ||
|       adjustThirdPartySharing.addGranularOption("google_dma", "eea", "1");
 | ||
|       adjustThirdPartySharing.addGranularOption(
 | ||
|           "google_dma", "ad_personalization", _flag(ConsentFieldName.adPersonalization));
 | ||
|       adjustThirdPartySharing.addGranularOption(
 | ||
|           "google_dma", "ad_user_data", _flag(ConsentFieldName.adUserData));
 | ||
|       Adjust.trackThirdPartySharing(adjustThirdPartySharing);
 | ||
|       Log.d("setAdjust complete!");
 | ||
|     }
 | ||
| 
 | ||
|     final result =
 | ||
|         "${_flag(ConsentFieldName.adStorage)}${_flag(ConsentFieldName.analyticsStorage)}${_flag(ConsentFieldName.adPersonalization)}${_flag(ConsentFieldName.adUserData)}";
 | ||
|     final changed = await AppProperty.getInstance().refreshGoogleDma(result);
 | ||
|     if (changed || GuruSettings.instance.debugMode.get()) {
 | ||
|       logEventEx("dma_gg", parameters: {"purpose": purposeConsents, "result": result});
 | ||
|     }
 | ||
|     return result;
 | ||
|   }
 | ||
| 
 | ||
|   void processAnalyticsCallback(int code, String? errorInfo) {
 | ||
|     if (!errorEventCodes.contains(code)) {
 | ||
|       return;
 | ||
|     }
 | ||
|     final parameters = {
 | ||
|       "item_category": "error_event",
 | ||
|       "item_name": code.toString(),
 | ||
|       "country": AccountDataStore.instance.countryCode,
 | ||
|       "network": AdsManager.instance.connectivityStatus.toString(),
 | ||
|     };
 | ||
| 
 | ||
|     if (errorInfo != null) {
 | ||
|       parameters["err"] = errorInfo.length > 32 ? errorInfo.substring(0, 32) : errorInfo;
 | ||
|     }
 | ||
|     logFirebaseEvent("dev_audit", parameters);
 | ||
|     // Guru Analytics Event(GAE)
 | ||
|     Log.d("[GAE]($code)=>$errorInfo $parameters", tag: "Analytics");
 | ||
|   }
 | ||
| 
 | ||
|   void updateUserIdentification(
 | ||
|       {String? firebaseAppInstanceId, String? idfa, String? adId, String? gpsAdId}) {
 | ||
|     final latestUserIdentification = userIdentificationSubject.value;
 | ||
|     bool changed = false;
 | ||
|     String? changedFirebaseInstanceId = latestUserIdentification.firebaseAppInstanceId;
 | ||
|     String? changedIdfa = latestUserIdentification.idfa;
 | ||
|     String? changedAdId = latestUserIdentification.adId;
 | ||
|     String? changedGpsAdId = latestUserIdentification.gpsAdId;
 | ||
| 
 | ||
|     if (firebaseAppInstanceId != null &&
 | ||
|         latestUserIdentification.firebaseAppInstanceId != firebaseAppInstanceId) {
 | ||
|       changedFirebaseInstanceId = firebaseAppInstanceId;
 | ||
|       changed = true;
 | ||
|     }
 | ||
|     if (idfa != null && latestUserIdentification.idfa != idfa) {
 | ||
|       changedIdfa = idfa;
 | ||
|       changed = true;
 | ||
|     }
 | ||
|     if (adId != null && latestUserIdentification.adId != adId) {
 | ||
|       changedAdId = adId;
 | ||
|       changed = true;
 | ||
|     }
 | ||
|     if (gpsAdId != null && latestUserIdentification.gpsAdId != gpsAdId) {
 | ||
|       changedGpsAdId = gpsAdId;
 | ||
|       changed = true;
 | ||
|     }
 | ||
|     if (changed) {
 | ||
|       final newUserIdentification = UserIdentification(
 | ||
|           firebaseAppInstanceId: changedFirebaseInstanceId ?? '',
 | ||
|           idfa: changedIdfa,
 | ||
|           adId: changedAdId,
 | ||
|           gpsAdId: changedGpsAdId);
 | ||
|       userIdentificationSubject.add(newUserIdentification);
 | ||
|       Log.d("updateUserIdentification: $newUserIdentification");
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   void parseFbEventMapping() {
 | ||
|     final fbEventMappingString =
 | ||
|         RemoteConfigManager.instance.getString(RemoteConfigReservedConstants.fbEventMapping);
 | ||
|     Log.d("parseFbEventMapping first: $fbEventMappingString");
 | ||
|     if (fbEventMappingString == null) {
 | ||
|       return;
 | ||
|     }
 | ||
|     Map<String, String> result = {};
 | ||
|     final eventEntries = fbEventMappingString.split(";");
 | ||
|     for (String eventEntryString in eventEntries) {
 | ||
|       final eventEntry = eventEntryString.split(":");
 | ||
|       if (eventEntry.length == 2) {
 | ||
|         result[eventEntry.first] = eventEntry.last;
 | ||
|       }
 | ||
|     }
 | ||
|     facebookEventMapping.clear();
 | ||
|     facebookEventMapping.addAll(result);
 | ||
|     Log.d("parseFbEventMapping: $result");
 | ||
|   }
 | ||
| 
 | ||
|   void initFbEventMapping() {
 | ||
|     RemoteConfigManager.instance.observeConfig().listen((config) {
 | ||
|       parseFbEventMapping();
 | ||
|     });
 | ||
|     parseFbEventMapping();
 | ||
|   }
 | ||
| 
 | ||
|   @override
 | ||
|   Future<String> getAppInstanceId() async {
 | ||
|     if (appInstanceId.isNotEmpty != true) {
 | ||
|       appInstanceId = await EventLogger.getAppInstanceId();
 | ||
|       RuntimeProperty.instance.setString(PropertyKeys.appInstanceId, appInstanceId);
 | ||
|     }
 | ||
|     return appInstanceId;
 | ||
|   }
 | ||
| 
 | ||
|   void _initEnvProperties() async {
 | ||
|     final bundle = await AppProperty.getInstance().loadValuesByTag(PropertyTags.analytics);
 | ||
| 
 | ||
|     final userId = bundle.getString(PropertyKeys.analyticsUserId);
 | ||
|     if (userId != null) {
 | ||
|       setUserId(userId);
 | ||
|     }
 | ||
| 
 | ||
|     final adjustId = bundle.getString(PropertyKeys.analyticsAdjustId);
 | ||
|     if (adjustId != null) {
 | ||
|       setAdjustId(adjustId);
 | ||
|     }
 | ||
| 
 | ||
|     final adId = bundle.getString(PropertyKeys.analyticsAdId);
 | ||
|     if (adId != null) {
 | ||
|       setAdId(adId);
 | ||
|     }
 | ||
|     refreshEventStatistic();
 | ||
| 
 | ||
|     String? firebaseId = await getAppInstanceId();
 | ||
|     if (firebaseId.isEmpty) {
 | ||
|       firebaseId = bundle.getString(PropertyKeys.analyticsFirebaseId);
 | ||
|     }
 | ||
|     if (firebaseId?.isNotEmpty == true) {
 | ||
|       setFirebaseId(firebaseId!);
 | ||
|     }
 | ||
|     refreshABProperties();
 | ||
|   }
 | ||
| 
 | ||
|   void refreshABProperties() {
 | ||
|     final abProperties = RemoteConfigManager.instance.getABProperties();
 | ||
| 
 | ||
|     final PropertyBundle propertyBundle = PropertyBundle();
 | ||
|     if (abProperties.isNotEmpty) {
 | ||
|       for (var entry in abProperties.entries) {
 | ||
|         setGuruUserProperty(entry.key, entry.value);
 | ||
|         propertyBundle.setString(PropertyKeys.buildABTestProperty(entry.key), entry.value);
 | ||
|         Log.d("setGuruUserProperty: ${entry.key} = ${entry.value}");
 | ||
|       }
 | ||
|     }
 | ||
|     AppProperty.getInstance().setProperties(propertyBundle);
 | ||
|   }
 | ||
| 
 | ||
|   void _logFirstOpen() async {
 | ||
|     int firstInstallTime =
 | ||
|         RuntimeProperty.instance.getInt(PropertyKeys.firstInstallTime, defValue: -1);
 | ||
|     if (firstInstallTime == -1) {
 | ||
|       firstInstallTime = await AppProperty.getInstance()
 | ||
|           .getOrCreateInt(PropertyKeys.firstInstallTime, DateTimeUtils.currentTimeInMillis());
 | ||
|     }
 | ||
|     setUserProperty("first_open_time", firstInstallTime.toString());
 | ||
|   }
 | ||
| 
 | ||
|   String getCountryCode() {
 | ||
|     if (mockCountryCode != null) {
 | ||
|       return mockCountryCode!;
 | ||
|     }
 | ||
|     final currentLocale = Platform.localeName.split('_');
 | ||
|     if (currentLocale.length > 1) {
 | ||
|       return currentLocale.last.toLowerCase();
 | ||
|     }
 | ||
|     return "";
 | ||
|   }
 | ||
| 
 | ||
|   void _logLocale() {
 | ||
|     if (Platform.localeName.isNotEmpty == true) {
 | ||
|       String lanCode = "";
 | ||
|       String countryCode = "";
 | ||
|       final currentLocale = Platform.localeName.split('_');
 | ||
|       if (currentLocale.isNotEmpty) {
 | ||
|         setUserProperty("lang_code", currentLocale[0].toLowerCase());
 | ||
|         lanCode = currentLocale[0].toLowerCase();
 | ||
|       }
 | ||
| 
 | ||
|       if (currentLocale.length > 1) {
 | ||
|         setUserProperty("country_code", currentLocale.last.toLowerCase());
 | ||
|         countryCode = currentLocale.last.toLowerCase();
 | ||
|       }
 | ||
|       Log.d("## locale: [$currentLocale]");
 | ||
| 
 | ||
|       if (lanCode.isNotEmpty && countryCode.isNotEmpty) {
 | ||
|         // CountryCodes.init(Locale(lanCode, countryCode));
 | ||
|       } else {
 | ||
|         // CountryCodes.init();
 | ||
|       }
 | ||
|     } else {
 | ||
|       // CountryCodes.init();
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   void _logDeviceType() async {
 | ||
|     setUserProperty("device_type", DeviceUtils.isTablet() ? "tablet" : "phone");
 | ||
| 
 | ||
|     final deviceId = await AppProperty.getInstance().getDeviceId();
 | ||
|     setDeviceId(deviceId);
 | ||
|   }
 | ||
| 
 | ||
|   @override
 | ||
|   Future setUserProperty(String key, String value) async {
 | ||
|     recordEvents("setUserProperty", {key: value});
 | ||
|     recordProperty(key, value);
 | ||
|     if (release) {
 | ||
|       await EventLogger.setUserProperty(key, value);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   static String buildVariantKey(String experimentName) {
 | ||
|     return "ab_$experimentName";
 | ||
|   }
 | ||
| 
 | ||
|   String getExperimentVariant(String experimentName) {
 | ||
|     return abTestExperimentVariant.value[experimentName] ?? "BASELINE";
 | ||
|   }
 | ||
| 
 | ||
|   Future<bool> setLocalABTest(ABTestExperiment experiment, {PropertyBundle? bundle}) async {
 | ||
|     Log.d("setLocalABTest: $experiment");
 | ||
|     String experimentName = experiment.name;
 | ||
|     final exp = await AppProperty.getInstance().getExperiment(experimentName, bundle: bundle);
 | ||
|     if (exp != null) {
 | ||
|       Log.w("Experiment already exists!");
 | ||
|       experiment = exp;
 | ||
|     }
 | ||
| 
 | ||
|     return await _applyExperiment(experiment);
 | ||
|   }
 | ||
| 
 | ||
|   Future removeExperiment(String experimentName) async {
 | ||
|     await AppProperty.getInstance().removeExperiment(experimentName);
 | ||
|     final data = Map<String, String>.of(abTestExperimentVariant.value);
 | ||
|     data.remove(experimentName);
 | ||
|     abTestExperimentVariant.addIfChanged(data);
 | ||
|   }
 | ||
| 
 | ||
|   Future<bool> _applyExperiment(ABTestExperiment experiment) async {
 | ||
|     final experimentName = experiment.name;
 | ||
|     if (experiment.isExpired()) {
 | ||
|       Log.w("Experiment($experimentName) is expired");
 | ||
|       await removeExperiment(experimentName);
 | ||
|       return false;
 | ||
|     }
 | ||
| 
 | ||
|     if (!experiment.isMatchAudience()) {
 | ||
|       Log.i("NOT match audience! $experiment! INTO BASELINE");
 | ||
|       return false;
 | ||
|     }
 | ||
| 
 | ||
|     String variantName = await AppProperty.getInstance().getExperimentVariant(experimentName);
 | ||
|     if (variantName.isEmpty) {
 | ||
|       variantName = await AppProperty.getInstance().setExperiment(experiment);
 | ||
|     }
 | ||
| 
 | ||
|     await setGuruUserProperty(buildVariantKey(experimentName), variantName);
 | ||
|     Log.i("==> Setup Local Experiment($experimentName) variantName: $variantName");
 | ||
| 
 | ||
|     final data = Map<String, String>.of(abTestExperimentVariant.value);
 | ||
|     data[experimentName] = variantName;
 | ||
|     abTestExperimentVariant.addIfChanged(data);
 | ||
|     return true;
 | ||
|   }
 | ||
| 
 | ||
|   void setDeviceId(String deviceId) {
 | ||
|     Log.d("setDeviceId: $deviceId");
 | ||
|     recordEvents("setDeviceId", {"userId": deviceId});
 | ||
|     recordProperty("deviceId", deviceId);
 | ||
|     if (deviceId.isNotEmpty) {
 | ||
|       AppProperty.getInstance().setAnalyticsDeviceId(deviceId);
 | ||
|       if (release) {
 | ||
|         EventLogger.setUserProperty("device_id", deviceId);
 | ||
|         EventLogger.setDeviceId(deviceId);
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   Future setUserId(String userId) async {
 | ||
|     Log.d("setUserId: $userId");
 | ||
|     recordEvents("setUserId", {"userId": userId});
 | ||
|     recordProperty("userId", userId);
 | ||
|     if (userId.isNotEmpty) {
 | ||
|       await AppProperty.getInstance().setUserId(userId);
 | ||
|       if (release) {
 | ||
|         EventLogger.setUserId(userId);
 | ||
|         FirebaseCrashlytics.instance.setUserIdentifier(userId);
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   void setAdjustId(String adjustId) {
 | ||
|     Log.d("setAdjustId: $adjustId");
 | ||
|     recordEvents("setAdjustId", {"adjustId": adjustId});
 | ||
|     recordProperty("adjustId", adjustId);
 | ||
|     if (adjustId.isNotEmpty) {
 | ||
|       AppProperty.getInstance().setAdjustId(adjustId);
 | ||
|       updateUserIdentification(adId: adjustId);
 | ||
|       if (release) {
 | ||
|         EventLogger.setAdjustId(adjustId);
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   void setFirebaseId(String firebaseId) {
 | ||
|     Log.d("setFirebaseId: $firebaseId");
 | ||
|     recordEvents("setFirebaseId", {"firebaseId": firebaseId});
 | ||
|     recordProperty("firebaseId", firebaseId);
 | ||
|     if (firebaseId.isNotEmpty) {
 | ||
|       AppProperty.getInstance().setFirebaseId(firebaseId);
 | ||
|       updateUserIdentification(firebaseAppInstanceId: firebaseId);
 | ||
|       if (release) {
 | ||
|         EventLogger.setFirebaseId(firebaseId);
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   void setAdId(String adId) {
 | ||
|     Log.d("setAdId: $adId");
 | ||
|     recordEvents("setAdId", {"adId": adId});
 | ||
|     recordProperty("adId", adId);
 | ||
|     AppProperty.getInstance().setAdId(adId);
 | ||
|     updateUserIdentification(gpsAdId: adId);
 | ||
|     if (release) {
 | ||
|       EventLogger.setAdId(adId);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   void setIdfa(String idfa) {
 | ||
|     Log.d("setIdfa: $idfa");
 | ||
|     recordEvents("setIdfa", {"idfa": idfa});
 | ||
|     recordProperty("idfa", idfa);
 | ||
|     AppProperty.getInstance().setIdfa(idfa);
 | ||
|     updateUserIdentification(idfa: idfa);
 | ||
|     if (release) {
 | ||
|       // 自打点中。idfa变是adId
 | ||
|       EventLogger.setAdId(idfa);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   void logScreen(String screenName) {
 | ||
|     recordEvents("logScreen", {"name": screenName});
 | ||
|     recordProperty("screen", screenName);
 | ||
|     if (release) {
 | ||
|       FirebaseCrashlytics.instance.log(screenName);
 | ||
|       EventLogger.logScreen(screenName);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   @override
 | ||
|   void setScreen(String screenName) {
 | ||
|     if (currentScreen != screenName) {
 | ||
|       currentScreen = screenName;
 | ||
|       logScreen(screenName);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   @override
 | ||
|   void logFirebase(String msg) async {
 | ||
|     if (release) {
 | ||
|       try {
 | ||
|         FirebaseCrashlytics.instance.log(msg);
 | ||
|         if (EventLogger.dumpLog) {
 | ||
|           Log.d("[Firebase]: $msg");
 | ||
|         }
 | ||
|       } catch (error, stacktrace) {}
 | ||
|     } else {
 | ||
|       Log.d("[Firebase]: $msg");
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   AppEventOptions? getOptions(String eventName) {
 | ||
|     return GuruAnalyticsStrategy.instance.getStrategyRule(eventName)?.getAppEventOptions();
 | ||
|   }
 | ||
| 
 | ||
|   @override
 | ||
|   void logEvent(String eventName, Map<String, dynamic> parameters, {AppEventOptions? options}) {
 | ||
|     refreshEventStatistic();
 | ||
|     // Firebase Facebook log event
 | ||
|     if (release) {
 | ||
|       EventLogger.logEvent(eventName, parameters, options: options ?? getOptions(eventName));
 | ||
|       _logAdjustEvent(eventName, parameters);
 | ||
|     } else {
 | ||
|       Log.d("logEvent: $eventName $parameters");
 | ||
|       EventLogger.transmit(eventName, parameters);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   @override
 | ||
|   void logEventEx(String eventName,
 | ||
|       {String? itemCategory,
 | ||
|       String? itemName,
 | ||
|       double? value,
 | ||
|       Map<String, dynamic> parameters = const {},
 | ||
|       AppEventOptions? options}) async {
 | ||
|     Map<String, dynamic> map = Map<String, dynamic>.from(parameters);
 | ||
|     if (itemCategory != null) {
 | ||
|       map["item_category"] = itemCategory;
 | ||
|     }
 | ||
| 
 | ||
|     if (itemName != null) {
 | ||
|       map["item_name"] = itemName;
 | ||
|     }
 | ||
| 
 | ||
|     if (value != null) {
 | ||
|       map["value"] = value;
 | ||
|     }
 | ||
| 
 | ||
|     logEvent(eventName, map, options: options);
 | ||
|   }
 | ||
| 
 | ||
|   Future refreshEventStatistic({bool force = false}) async {
 | ||
|     if (!GuruApp.instance.appSpec.deployment.enableAnalyticsStatistic) {
 | ||
|       return;
 | ||
|     }
 | ||
|     final now = DateTimeUtils.currentTimeInMillis();
 | ||
|     if (force || (now - latestFetchStatisticTs > DateTimeUtils.minuteInMillis * 2)) {
 | ||
|       EventLogger.getStatistic().then((statistic) {
 | ||
|         Log.d("Event Statistic:$statistic");
 | ||
|         if (statistic != GuruStatistic.invalid && guruEventStatistic.addIfChanged(statistic)) {
 | ||
|           setUserProperty("lgd", statistic.logged.toString());
 | ||
|           setUserProperty("uld", statistic.uploaded.toString());
 | ||
|         }
 | ||
|       });
 | ||
|       latestFetchStatisticTs = now;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   Future<String> zipGuruLogs() {
 | ||
|     return EventLogger.zipGuruLogs();
 | ||
|   }
 | ||
| 
 | ||
|   Map<String, dynamic> filterOutNulls(Map<String, dynamic> parameters) {
 | ||
|     final Map<String, dynamic> filtered = <String, dynamic>{};
 | ||
|     parameters.forEach((String key, dynamic value) {
 | ||
|       if (value != null) {
 | ||
|         filtered[key] = value;
 | ||
|       }
 | ||
|     });
 | ||
|     return filtered;
 | ||
|   }
 | ||
| 
 | ||
|   @override
 | ||
|   void logException(dynamic exception, {StackTrace? stacktrace}) async {
 | ||
|     if (release) {
 | ||
|       Log.d("exception! $exception");
 | ||
|       FirebaseCrashlytics.instance.log(exception.toString());
 | ||
|       FirebaseCrashlytics.instance
 | ||
|           .recordError(exception, stacktrace, printDetails: EventLogger.dumpLog);
 | ||
|     } else {
 | ||
|       Log.w("Occur Error! $exception $stacktrace", stackTrace: stacktrace);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   void logPurchase(double amount,
 | ||
|       {String currency = "",
 | ||
|       String contentId = "",
 | ||
|       String adPlatform = "",
 | ||
|       Map<String, dynamic> parameters = const <String, dynamic>{}}) async {
 | ||
|     Log.i("logPurchase:$amount, $currency, $contentId, $adPlatform, $parameters");
 | ||
|     try {
 | ||
|       await EventLogger.logFbPurchase(amount,
 | ||
|           currency: currency,
 | ||
|           contentId: contentId,
 | ||
|           adPlatform: adPlatform,
 | ||
|           additionParameters: parameters);
 | ||
|     } catch (error, stacktrace) {
 | ||
|       Log.w("logFbPurchase error$error, $stacktrace");
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   void logEventShare({String? itemCategory, String? itemName}) {
 | ||
|     logEvent("share", {
 | ||
|       "item_category": itemCategory,
 | ||
|       "item_name": itemName,
 | ||
|       "content_type": itemCategory,
 | ||
|       "item_id": itemName
 | ||
|     });
 | ||
|   }
 | ||
| 
 | ||
|   void logSpendCredits(String contentId, String contentType, int price,
 | ||
|       {required String virtualCurrencyName, required int balance, String scene = ''}) {
 | ||
|     final levelName = GuruApp.instance.protocol.getLevelName();
 | ||
|     if (release) {
 | ||
|       EventLogger.logSpendCredits(contentId, contentType, price,
 | ||
|           virtualCurrencyName: virtualCurrencyName, balance: balance, scene: scene);
 | ||
|     } else {
 | ||
|       final parameters = <String, dynamic>{
 | ||
|         "item_name": contentId,
 | ||
|         "item_category": contentType,
 | ||
|         "virtual_currency_name": virtualCurrencyName,
 | ||
|         "value": price,
 | ||
|         "balance": balance,
 | ||
|         "scene": scene,
 | ||
|         "level_name": levelName
 | ||
|       };
 | ||
|       Log.d("logEvent: spend_virtual_currency $parameters");
 | ||
|       EventLogger.transmit("spend_virtual_currency", parameters);
 | ||
|     }
 | ||
|     AiBi.instance.spendVirtualCurrency(balance, price.toDouble(), contentType);
 | ||
|   }
 | ||
| 
 | ||
|   Future<void> logEarnVirtualCurrency(
 | ||
|       {required String virtualCurrencyName,
 | ||
|       required String method,
 | ||
|       required int balance,
 | ||
|       required int value,
 | ||
|       String? specific,
 | ||
|       String? scene}) async {
 | ||
|     final levelName = GuruApp.instance.protocol.getLevelName();
 | ||
|     logEvent(
 | ||
|         "earn_virtual_currency",
 | ||
|         filterOutNulls(<String, dynamic>{
 | ||
|           "virtual_currency_name": virtualCurrencyName,
 | ||
|           "item_category": method,
 | ||
|           "item_name": specific,
 | ||
|           "value": value,
 | ||
|           "balance": balance,
 | ||
|           "level_name": levelName,
 | ||
|           "scene": scene
 | ||
|         }));
 | ||
|     AiBi.instance.earnVirtualCurrency(balance, value.toDouble(), method);
 | ||
|   }
 | ||
| 
 | ||
|   String? peekUserProperty(String key) {
 | ||
|     return Analytics.userProperties[key];
 | ||
|   }
 | ||
| 
 | ||
|   Future<void> setGuruUserProperty(String key, String value) async {
 | ||
|     recordProperty(key, value);
 | ||
|     return await EventLogger.setGuruUserProperty(key, value);
 | ||
|   }
 | ||
| 
 | ||
|   Future<void> logGuruEvent(String eventName, Map<String, dynamic> parameters) async {
 | ||
|     EventLogger.guruLogEvent(name: eventName, parameters: parameters);
 | ||
|   }
 | ||
| 
 | ||
|   Future<void> logFirebaseEvent(String eventName, Map<String, dynamic> parameters) async {
 | ||
|     if (release) {
 | ||
|       EventLogger.firebaseLogEvent(name: eventName, parameters: parameters);
 | ||
|     } else {
 | ||
|       Log.d("logEvent: $eventName $parameters");
 | ||
|     }
 | ||
|     EventLogger.transmit(eventName, parameters);
 | ||
|   }
 | ||
| }
 |