guru_sdk/guru_app/lib/analytics/guru_analytics.dart

811 lines
27 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/// 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 "";
}
/// 如果他不是完全使用 10 组成的字符串
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);
}
}