diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 8a15017fe3..b8acdce7b5 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -14,7 +14,7 @@ import com.tangem.domain.common.LogConfig import com.tangem.network.common.MoshiConverter import com.tangem.tap.common.AndroidAssetReader import com.tangem.tap.common.AssetReader -import com.tangem.tap.common.analytics.AnalyticsEventHandlerBuilder +import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandlerBuilder import com.tangem.tap.common.feedback.AdditionalFeedbackInfo import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.images.createCoilImageLoader @@ -100,7 +100,7 @@ class TapApplication : Application(), ImageLoaderFactory { } private fun initAnalytics(application: Application, config: Config) { - val globalAnalyticsHandler = AnalyticsEventHandlerBuilder( + val globalAnalyticsHandler = GlobalAnalyticsEventHandlerBuilder( application = application, config = config, isDebug = BuildConfig.DEBUG, diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventHandlerBuilder.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventHandlerBuilder.kt deleted file mode 100644 index e5a2ec3283..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventHandlerBuilder.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.tangem.tap.common.analytics - -import android.app.Application -import com.tangem.common.json.MoshiJsonConverter -import com.tangem.domain.common.AnalyticsHandlersLogConfig -import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler -import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler -import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler -import com.tangem.tap.domain.configurable.config.Config - -/** -[REDACTED_AUTHOR] - */ -class AnalyticsEventHandlerBuilder( - private val application: Application, - private val config: Config, - private val isDebug: Boolean, - private val logConfig: AnalyticsHandlersLogConfig, - private val jsonConverter: MoshiJsonConverter, -) { - - private val analyticHandlers = mutableListOf() - - fun build(): GlobalAnalyticsEventHandler { - return GlobalAnalyticsHandler(analyticHandlers.toList()) - } - - fun default(): GlobalAnalyticsEventHandler { - addFirebaseAnalytics() - addAppsFlyer() - addAmplitude() - - return GlobalAnalyticsHandler(analyticHandlers.toList()) - } - - fun addFirebaseAnalytics(): AnalyticsEventHandlerBuilder { - if (isDebug) { - addEventLogger(logConfig.firebase, FirebaseAnalyticsHandler::class.java.simpleName) - } else { - analyticHandlers.add(FirebaseAnalyticsHandler()) - } - return this - } - - fun addAppsFlyer(): AnalyticsEventHandlerBuilder { - if (isDebug) { - addEventLogger(logConfig.appsFlyer, AppsFlyerAnalyticsHandler::class.java.simpleName) - } else { - analyticHandlers.add(AppsFlyerAnalyticsHandler(application, config.appsFlyerDevKey)) - } - return this - } - - fun addAmplitude(): AnalyticsEventHandlerBuilder { - if (isDebug) { - addEventLogger(logConfig.amplitude, AmplitudeAnalyticsHandler::class.java.simpleName) - } else { - analyticHandlers.add(AmplitudeAnalyticsHandler(application, config.amplitudeApiKey)) - } - return this - } - - private fun addEventLogger(logsEnabled: Boolean, name: String) { - // if (logsEnabled) analyticHandlers.add(AnalyticsLogsPrinter(name, jsonConverter)) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt new file mode 100644 index 0000000000..0865d2aa81 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt @@ -0,0 +1,26 @@ +package com.tangem.tap.common.analytics + +import com.tangem.common.json.MoshiJsonConverter +import com.tangem.tap.common.analytics.api.ErrorEventLogger +import com.tangem.tap.common.analytics.api.EventLogger +import timber.log.Timber + +class AnalyticsEventsLogger( + private val name: String, + private val jsonConverter: MoshiJsonConverter, +) : EventLogger, ErrorEventLogger { + + override fun logEvent(event: String, params: Map) { + Timber.d(jsonConverter.prettyPrint(PrintEventModel(name, event, params))) + } + + override fun logErrorEvent(error: Throwable, params: Map) { + Timber.e(error, jsonConverter.prettyPrint(PrintEventModel(name, "error", params))) + } +} + +private data class PrintEventModel( + val client: String, + val event: String, + val params: Map, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsInterfaces.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsInterfaces.kt new file mode 100644 index 0000000000..6fd6fc7dc8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsInterfaces.kt @@ -0,0 +1,3 @@ +package com.tangem.tap.common.analytics + + diff --git a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandlerBuilder.kt b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandlerBuilder.kt new file mode 100644 index 0000000000..8e2b439e08 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandlerBuilder.kt @@ -0,0 +1,79 @@ +package com.tangem.tap.common.analytics + +import android.app.Application +import com.tangem.common.json.MoshiJsonConverter +import com.tangem.domain.common.AnalyticsHandlersLogConfig +import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler +import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeClient +import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeLogClient +import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler +import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerClient +import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerLogClient +import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler +import com.tangem.tap.common.analytics.handlers.firebase.FirebaseClient +import com.tangem.tap.common.analytics.handlers.firebase.FirebaseLogClient +import com.tangem.tap.domain.configurable.config.Config + +/** +[REDACTED_AUTHOR] + */ +class GlobalAnalyticsEventHandlerBuilder( + val application: Application, + val config: Config, + val isDebug: Boolean, + val logConfig: AnalyticsHandlersLogConfig, + val jsonConverter: MoshiJsonConverter, +) { + + private val analyticHandlers = mutableListOf() + + fun build(): GlobalAnalyticsEventHandler { + return GlobalAnalyticsHandler(analyticHandlers.toList()) + } + + fun default(): GlobalAnalyticsEventHandler { + addFirebaseAnalytics() + addAppsFlyer() + addAmplitude() + + return GlobalAnalyticsHandler(analyticHandlers.toList()) + } + + fun addFirebaseAnalytics(): GlobalAnalyticsEventHandlerBuilder { + if (isDebug) { + if (logConfig.firebase) FirebaseLogClient(createLogger("FirebaseAnalyticsHandler")) else null + } else { + FirebaseClient() + }?.let { + analyticHandlers.add(FirebaseAnalyticsHandler(it)) + } + return this + } + + fun addAppsFlyer(): GlobalAnalyticsEventHandlerBuilder { + if (isDebug) { + if (logConfig.appsFlyer) AppsFlyerLogClient(createLogger("AppsFlyerAnalyticsHandler")) else null + } else { + AppsFlyerClient(application, config.appsFlyerDevKey) + }?.let { + analyticHandlers.add(AppsFlyerAnalyticsHandler(it)) + } + return this + } + + fun addAmplitude(): GlobalAnalyticsEventHandlerBuilder { + if (isDebug) { + if (logConfig.firebase) AmplitudeLogClient(createLogger("AmplitudeAnalyticsHandler")) else null + } else { + AmplitudeClient(application, config.amplitudeApiKey) + }?.let { + analyticHandlers.add(AmplitudeAnalyticsHandler(it)) + } + return this + } + + private fun createLogger(name: String): AnalyticsEventsLogger { + return AnalyticsEventsLogger(name, jsonConverter) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt index cd28857ae7..660664c1f7 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt @@ -4,12 +4,19 @@ import com.shopify.buy3.Storefront import com.tangem.blockchain.common.BlockchainError import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError +import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.BlockchainSdkErrorEventHandler +import com.tangem.tap.common.analytics.api.CardSdkErrorEventHandler +import com.tangem.tap.common.analytics.api.ErrorEventHandler +import com.tangem.tap.common.analytics.api.ErrorEventLogger +import com.tangem.tap.common.analytics.api.SdkErrorEventHandler +import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler import com.tangem.tap.common.extensions.filterNotNull interface GlobalAnalyticsEventHandler : AnalyticsEventHandler, - ShopifyOrderEventHandler, ErrorEventHandler, - SdkErrorEventHandler + SdkErrorEventHandler, + ShopifyOrderEventHandler class GlobalAnalyticsHandler( private val analyticsHandlers: List, @@ -28,15 +35,9 @@ class GlobalAnalyticsHandler( analyticsHandlers.forEach { it.handleAnalyticsEvent(event, params, card, blockchain) } } - override fun handleShopifyOrderEvent(order: Storefront.Order) { - analyticsHandlers.filterIsInstance().forEach { - it.handleShopifyOrderEvent(order) - } - } - override fun handleErrorEvent(error: Throwable, params: Map) { - analyticsHandlers.filterIsInstance().forEach { - it.handleErrorEvent(error, params) + analyticsHandlers.filterIsInstance().forEach { + it.logErrorEvent(error, params) } } @@ -61,6 +62,12 @@ class GlobalAnalyticsHandler( it.handleBlockchainSdkErrorEvent(error, action, params, card) } } + + override fun handleShopifyOrderEvent(order: Storefront.Order) { + analyticsHandlers.filterIsInstance().forEach { + it.handleShopifyOrderEvent(order) + } + } } fun GlobalAnalyticsEventHandler.logWcEvent(event: Analytics.WcAnalyticsEvent) { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/ClientApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/ClientApi.kt new file mode 100644 index 0000000000..3b5b0720f2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/ClientApi.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.common.analytics.api + +/** +[REDACTED_AUTHOR] + */ +interface EventLogger { + fun logEvent( + event: String, + params: Map = emptyMap(), + ) +} + +interface ErrorEventLogger { + fun logErrorEvent( + error: Throwable, + params: Map = emptyMap(), + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt similarity index 73% rename from app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventHandler.kt rename to app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt index d973b4fa2c..00cb845770 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt @@ -1,19 +1,23 @@ -package com.tangem.tap.common.analytics +package com.tangem.tap.common.analytics.api import com.shopify.buy3.Storefront import com.tangem.blockchain.common.BlockchainError import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.AnalyticsEvent +import com.tangem.tap.common.analytics.AnalyticsParam import com.tangem.tap.common.extensions.filterNotNull -interface EventHandler { +/** +[REDACTED_AUTHOR] + */ +interface AnalyticsEventHandler { fun handleEvent( event: String, params: Map = emptyMap(), ) -} -interface AnalyticsEventHandler : EventHandler { fun handleAnalyticsEvent( event: AnalyticsEvent, params: Map = emptyMap(), @@ -25,17 +29,11 @@ interface AnalyticsEventHandler : EventHandler { card: Card?, blockchain: String? = null, params: Map = emptyMap(), - ): Map { - return mapOf( - AnalyticsParam.FIRMWARE.param to card?.firmwareVersion?.stringValue, - AnalyticsParam.BATCH_ID.param to card?.batchId, - AnalyticsParam.BLOCKCHAIN.param to blockchain, - ).filterNotNull() + params - } -} - -interface ShopifyOrderEventHandler { - fun handleShopifyOrderEvent(order: Storefront.Order) + ): Map = mapOf( + AnalyticsParam.FIRMWARE.param to card?.firmwareVersion?.stringValue, + AnalyticsParam.BATCH_ID.param to card?.batchId, + AnalyticsParam.BLOCKCHAIN.param to blockchain, + ).filterNotNull() + params } interface ErrorEventHandler { @@ -63,4 +61,8 @@ interface BlockchainSdkErrorEventHandler { params: Map = mapOf(), card: Card? = null, ) -} \ No newline at end of file +} + +interface ShopifyOrderEventHandler { + fun handleShopifyOrderEvent(order: Storefront.Order) +} diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/SdkErrorConverters.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/SdkErrorConverters.kt new file mode 100644 index 0000000000..470933771a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/SdkErrorConverters.kt @@ -0,0 +1,88 @@ +package com.tangem.tap.common.analytics.converters + +import com.tangem.blockchain.common.BlockchainError +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.common.Converter +import com.tangem.common.core.TangemSdkError +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.AnalyticsParam +import com.tangem.tap.common.analytics.TangemSdk +import com.tangem.tap.features.demo.DemoTransactionSender + +/** +[REDACTED_AUTHOR] + */ +internal data class ErrorEvent( + val throwable: Throwable, + val params: Map, +) + +internal class CardSdkErrorConverter : Converter { + + override fun convert(value: Model): ErrorEvent? { + if (value.error is TangemSdkError.UserCancelled) return null + + val mutableParams = value.params.convertToParamValue().toMutableMap().apply { + putAll(value.cardParams) + } + mutableParams[AnalyticsParam.ACTION.param] = value.action.key + mutableParams[AnalyticsParam.ERROR_CODE.param] = value.error.code.toString() + mutableParams[AnalyticsParam.ERROR_DESCRIPTION.param] = value.error.javaClass.simpleName + mutableParams[AnalyticsParam.ERROR_KEY.param] = "TangemSdkError" + + return ErrorEvent(TangemSdk.map(value.error), mutableParams) + } + + internal data class Model( + val error: TangemSdkError, + val action: Analytics.ActionToLog, + val params: Map, + val cardParams: Map, + ) +} + +internal class BlockchainSdkErrorConverter( + private val cardSdkErrorConverter: CardSdkErrorConverter, +) : Converter { + + override fun convert(value: Model): ErrorEvent? { + val error = (value.error as? BlockchainSdkError) ?: return null + + if (error is BlockchainSdkError.WrappedTangemError) { + val tangemSdkError = (error.tangemError as? TangemSdkError) ?: return null + + val cardSdkModel = CardSdkErrorConverter.Model( + error = tangemSdkError, + action = value.action, + params = value.params, + cardParams = value.cardParams, + ) + return cardSdkErrorConverter.convert(cardSdkModel) + } + + if (error.customMessage.contains(DemoTransactionSender.ID)) return null + + val mutableParams = value.params.convertToParamValue().toMutableMap() + mutableParams[AnalyticsParam.ACTION.param] = value.action.key + mutableParams[AnalyticsParam.ERROR_CODE.param] = error.code.toString() + mutableParams[AnalyticsParam.ERROR_DESCRIPTION.param] = "${error.javaClass.simpleName}: ${error.customMessage}" + mutableParams[AnalyticsParam.ERROR_KEY.param] = "BlockchainSdkError" + + return ErrorEvent(value.error, mutableParams) + } + + data class Model( + val error: BlockchainError, + val action: Analytics.ActionToLog, + val params: Map, + val cardParams: Map, + ) +} + +internal fun Map.convertToParamValue(): Map { + return mutableMapOf().also { newMap -> + this.forEach { + newMap[it.key.param] = it.value + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt index 22baff0a2f..ab530b5829 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt @@ -1,28 +1,15 @@ package com.tangem.tap.common.analytics.handlers.amplitude -import android.app.Application -import com.amplitude.api.Amplitude -import com.amplitude.api.AmplitudeClient -import com.tangem.common.Converter import com.tangem.common.card.Card import com.tangem.tap.common.analytics.AnalyticsEvent -import com.tangem.tap.common.analytics.AnalyticsEventHandler -import org.json.JSONObject +import com.tangem.tap.common.analytics.api.AnalyticsEventHandler class AmplitudeAnalyticsHandler( - application: Application, - key: String, + private val client: AmplitudeAnalyticsClient, ) : AnalyticsEventHandler { - private val client: AmplitudeClient = Amplitude.getInstance() - - init { - client.initialize(application, key) - client.enableForegroundTracking(application) - } - override fun handleEvent(event: String, params: Map) { - client.logEvent(event, ParamsJSONObjectConverter().convert(params)) + client.logEvent(event, params) } override fun handleAnalyticsEvent( @@ -33,11 +20,4 @@ class AmplitudeAnalyticsHandler( ) { handleEvent(event.event, prepareParams(card, blockchain, params)) } - - class ParamsJSONObjectConverter : Converter, JSONObject> { - override fun convert(value: Map): JSONObject = JSONObject().apply { - value.forEach { this.put(it.key, it.value) } - } - } -} - +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt new file mode 100644 index 0000000000..d90ed084b5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt @@ -0,0 +1,36 @@ +package com.tangem.tap.common.analytics.handlers.amplitude + +import android.app.Application +import com.amplitude.api.Amplitude +import com.amplitude.api.AmplitudeClient +import com.tangem.common.Converter +import com.tangem.tap.common.analytics.api.EventLogger +import org.json.JSONObject + +/** +[REDACTED_AUTHOR] + */ +interface AmplitudeAnalyticsClient : EventLogger + +internal class AmplitudeClient( + application: Application, + key: String, +) : AmplitudeAnalyticsClient { + + private val client: AmplitudeClient = Amplitude.getInstance() + + init { + client.initialize(application, key) + client.enableForegroundTracking(application) + } + + override fun logEvent(event: String, params: Map) { + client.logEvent(event, ParamsToJSONObjectConverter().convert(params)) + } +} + +private class ParamsToJSONObjectConverter : Converter, JSONObject> { + override fun convert(value: Map): JSONObject = JSONObject().apply { + value.forEach { this.put(it.key, it.value) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt new file mode 100644 index 0000000000..b800259b5f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.common.analytics.handlers.amplitude + +import com.tangem.tap.common.analytics.AnalyticsEventsLogger + +/** +[REDACTED_AUTHOR] + */ +internal class AmplitudeLogClient( + private val logger: AnalyticsEventsLogger, +) : AmplitudeAnalyticsClient { + + override fun logEvent(event: String, params: Map) { + logger.logEvent(event, params) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt index 8b14fc2eba..a5ca83d4d5 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt @@ -1,30 +1,20 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer -import android.content.Context import com.appsflyer.AFInAppEventParameterName import com.appsflyer.AFInAppEventType -import com.appsflyer.AppsFlyerLib import com.shopify.buy3.Storefront import com.tangem.common.Converter import com.tangem.common.card.Card import com.tangem.tap.common.analytics.AnalyticsEvent -import com.tangem.tap.common.analytics.AnalyticsEventHandler -import com.tangem.tap.common.analytics.ShopifyOrderEventHandler +import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler class AppsFlyerAnalyticsHandler( - private val context: Context, - key: String, + private val client: AppsFlyerAnalyticsClient, ) : AnalyticsEventHandler, ShopifyOrderEventHandler { - private val appsFlyerLib: AppsFlyerLib = AppsFlyerLib.getInstance() - - init { - appsFlyerLib.init(key, null, context) - appsFlyerLib.start(context) - } - override fun handleEvent(event: String, params: Map) { - appsFlyerLib.logEvent(context, event, params) + client.logEvent(event, params) } override fun handleAnalyticsEvent( @@ -43,25 +33,26 @@ class AppsFlyerAnalyticsHandler( companion object { const val ORDER_EVENT = AFInAppEventType.PURCHASE } +} - class OrderToParamsConverter : Converter> { - override fun convert(value: Storefront.Order): Map { - val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown" +private class OrderToParamsConverter : Converter> { - val discountCode = - (value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code + override fun convert(value: Storefront.Order): Map { + val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown" - val discountParams = if (discountCode != null) { - mapOf(AFInAppEventParameterName.COUPON_CODE to discountCode) - } else { - mapOf() - } + val discountCode = + (value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code - return mapOf( - AFInAppEventParameterName.CONTENT_ID to sku, - AFInAppEventParameterName.REVENUE to value.totalPriceV2.amount, - AFInAppEventParameterName.CURRENCY to value.currencyCode.name, - ) + discountParams + val discountParams = if (discountCode != null) { + mapOf(AFInAppEventParameterName.COUPON_CODE to discountCode) + } else { + mapOf() } + + return mapOf( + AFInAppEventParameterName.CONTENT_ID to sku, + AFInAppEventParameterName.REVENUE to value.totalPriceV2.amount, + AFInAppEventParameterName.CURRENCY to value.currencyCode.name, + ) + discountParams } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt new file mode 100644 index 0000000000..6a7aa7cfed --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.common.analytics.handlers.appsFlyer + +import android.content.Context +import com.appsflyer.AppsFlyerLib +import com.tangem.tap.common.analytics.api.EventLogger + +/** +[REDACTED_AUTHOR] + */ +interface AppsFlyerAnalyticsClient : EventLogger + +internal class AppsFlyerClient( + private val context: Context, + key: String, +) : AppsFlyerAnalyticsClient { + + private val appsFlyerLib: AppsFlyerLib = AppsFlyerLib.getInstance() + + init { + appsFlyerLib.init(key, null, context) + appsFlyerLib.start(context) + } + + override fun logEvent(event: String, params: Map) { + appsFlyerLib.logEvent(context, event, params) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerLogClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerLogClient.kt new file mode 100644 index 0000000000..0d3737a161 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerLogClient.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.common.analytics.handlers.appsFlyer + +import com.tangem.tap.common.analytics.AnalyticsEventsLogger + +/** +[REDACTED_AUTHOR] + */ +internal class AppsFlyerLogClient( + private val logger: AnalyticsEventsLogger, +) : AppsFlyerAnalyticsClient { + + override fun logEvent(event: String, params: Map) { + logger.logEvent(event, params) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt index b2be460eb5..aba653613e 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt @@ -1,35 +1,27 @@ package com.tangem.tap.common.analytics.handlers.firebase -import android.os.Bundle -import androidx.core.os.bundleOf import com.google.firebase.analytics.FirebaseAnalytics -import com.google.firebase.analytics.ktx.analytics -import com.google.firebase.crashlytics.ktx.crashlytics -import com.google.firebase.ktx.Firebase import com.shopify.buy3.Storefront import com.tangem.blockchain.common.BlockchainError -import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.common.Converter import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsEvent -import com.tangem.tap.common.analytics.AnalyticsEventHandler import com.tangem.tap.common.analytics.AnalyticsParam -import com.tangem.tap.common.analytics.ErrorEventHandler -import com.tangem.tap.common.analytics.SdkErrorEventHandler -import com.tangem.tap.common.analytics.ShopifyOrderEventHandler -import com.tangem.tap.common.analytics.TangemSdk -import com.tangem.tap.features.demo.DemoTransactionSender +import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.ErrorEventHandler +import com.tangem.tap.common.analytics.api.SdkErrorEventHandler +import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler +import com.tangem.tap.common.analytics.converters.BlockchainSdkErrorConverter +import com.tangem.tap.common.analytics.converters.CardSdkErrorConverter -class FirebaseAnalyticsHandler : AnalyticsEventHandler, ShopifyOrderEventHandler, ErrorEventHandler, - SdkErrorEventHandler { - - private val fbAnalytics = Firebase.analytics - private val fbCrashlytics = Firebase.crashlytics +class FirebaseAnalyticsHandler( + private val client: FirebaseAnalyticsClient, +) : AnalyticsEventHandler, ErrorEventHandler, SdkErrorEventHandler, ShopifyOrderEventHandler { override fun handleEvent(event: String, params: Map) { - fbAnalytics.logEvent(event, params.toBundle()) + client.logEvent(event, params) } override fun handleAnalyticsEvent( @@ -38,16 +30,11 @@ class FirebaseAnalyticsHandler : AnalyticsEventHandler, ShopifyOrderEventHandler card: Card?, blockchain: String?, ) { - handleEvent(event.event, prepareParams(card, blockchain, params)) - } - - override fun handleShopifyOrderEvent(order: Storefront.Order) { - handleEvent(ORDER_EVENT, OrderToParamsConverter().convert(order)) + handleEvent(event.name, prepareParams(card, blockchain, params)) } override fun handleErrorEvent(error: Throwable, params: Map) { - params.forEach { (key, value) -> fbCrashlytics.setCustomKey(key, value) } - fbCrashlytics.recordException(error) + client.logErrorEvent(error, params) } override fun handleCardSdkErrorEvent( @@ -56,18 +43,11 @@ class FirebaseAnalyticsHandler : AnalyticsEventHandler, ShopifyOrderEventHandler params: Map, card: Card?, ) { - if (error is TangemSdkError.UserCancelled) return - - val mutableParams = params.toMutableMap() - if (card != null) mutableParams + prepareParams(card) - - mutableParams[AnalyticsParam.ACTION] = action.key - mutableParams[AnalyticsParam.ERROR_CODE] = error.code.toString() - mutableParams[AnalyticsParam.ERROR_DESCRIPTION] = error.javaClass.simpleName - mutableParams[AnalyticsParam.ERROR_KEY] = "TangemSdkError" - - mutableParams.forEach { fbCrashlytics.setCustomKey(it.key.param, it.value) } - fbCrashlytics.recordException(TangemSdk.map(error)) + val model = CardSdkErrorConverter.Model(error, action, params, prepareParams(card)) + val converter = CardSdkErrorConverter() + converter.convert(model)?.let { + handleErrorEvent(it.throwable, it.params) + } } override fun handleBlockchainSdkErrorEvent( @@ -76,52 +56,36 @@ class FirebaseAnalyticsHandler : AnalyticsEventHandler, ShopifyOrderEventHandler params: Map, card: Card?, ) { - val blockchainSdkError = (error as? BlockchainSdkError) ?: return - if (blockchainSdkError is BlockchainSdkError.WrappedTangemError) { - val tangemSdkError = (blockchainSdkError.tangemError as? TangemSdkError) ?: return - handleCardSdkErrorEvent(tangemSdkError, action, params, card) - return + val model = BlockchainSdkErrorConverter.Model(error, action, params, prepareParams(card)) + val converter = BlockchainSdkErrorConverter(CardSdkErrorConverter()) + converter.convert(model)?.let { + handleErrorEvent(it.throwable, it.params) + } + } + + override fun handleShopifyOrderEvent(order: Storefront.Order) { + handleEvent(FirebaseClient.ORDER_EVENT, OrderToParamsConverter().convert(order)) + } +} + +private class OrderToParamsConverter : Converter> { + override fun convert(value: Storefront.Order): Map { + val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown" + + val discountCode = + (value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code + + val discountParams = if (discountCode != null) { + mapOf(FirebaseAnalytics.Param.DISCOUNT to discountCode) + } else { + mapOf() } - if (blockchainSdkError.customMessage.contains(DemoTransactionSender.ID)) return - - val mutableParams = params.toMutableMap() - mutableParams[AnalyticsParam.ACTION] = action.key - mutableParams[AnalyticsParam.ERROR_CODE] = error.code.toString() - mutableParams[AnalyticsParam.ERROR_DESCRIPTION] = "${error.javaClass.simpleName}: ${error.customMessage}" - mutableParams[AnalyticsParam.ERROR_KEY] = "BlockchainSdkError" - - mutableParams.forEach { fbCrashlytics.setCustomKey(it.key.param, it.value) } - fbCrashlytics.recordException(error) - } - - private fun Map.toBundle(): Bundle { - return bundleOf(*this.toList().toTypedArray()) - } - - companion object { - const val ORDER_EVENT = FirebaseAnalytics.Event.PURCHASE - } - - class OrderToParamsConverter : Converter> { - override fun convert(value: Storefront.Order): Map { - val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown" - - val discountCode = - (value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code - - val discountParams = if (discountCode != null) { - mapOf(FirebaseAnalytics.Param.DISCOUNT to discountCode) - } else { - mapOf() - } - - return mapOf( - FirebaseAnalytics.Param.ITEM_ID to sku, - FirebaseAnalytics.Param.VALUE to value.totalPriceV2.amount, - FirebaseAnalytics.Param.CURRENCY to value.currencyCode.name, - ) + discountParams - } + return mapOf( + FirebaseAnalytics.Param.ITEM_ID to sku, + FirebaseAnalytics.Param.VALUE to value.totalPriceV2.amount, + FirebaseAnalytics.Param.CURRENCY to value.currencyCode.name, + ) + discountParams } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt new file mode 100644 index 0000000000..07ed4c4551 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt @@ -0,0 +1,36 @@ +package com.tangem.tap.common.analytics.handlers.firebase + +import android.os.Bundle +import androidx.core.os.bundleOf +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.analytics.ktx.analytics +import com.google.firebase.crashlytics.ktx.crashlytics +import com.google.firebase.ktx.Firebase +import com.tangem.tap.common.analytics.api.ErrorEventLogger +import com.tangem.tap.common.analytics.api.EventLogger + +/** +[REDACTED_AUTHOR] + */ +interface FirebaseAnalyticsClient : EventLogger, ErrorEventLogger + +internal class FirebaseClient : FirebaseAnalyticsClient { + + private val fbAnalytics = Firebase.analytics + private val fbCrashlytics = Firebase.crashlytics + + override fun logEvent(event: String, params: Map) { + fbAnalytics.logEvent(event, params.toBundle()) + } + + override fun logErrorEvent(error: Throwable, params: Map) { + params.forEach { fbCrashlytics.setCustomKey(it.key, it.value) } + fbCrashlytics.recordException(error) + } + + private fun Map.toBundle(): Bundle = bundleOf(*this.toList().toTypedArray()) + + companion object { + const val ORDER_EVENT = FirebaseAnalytics.Event.PURCHASE + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt new file mode 100644 index 0000000000..ccacc09f9e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt @@ -0,0 +1,19 @@ +package com.tangem.tap.common.analytics.handlers.firebase + +import com.tangem.tap.common.analytics.AnalyticsEventsLogger + +/** +[REDACTED_AUTHOR] + */ +internal class FirebaseLogClient( + private val logger: AnalyticsEventsLogger, +) : FirebaseAnalyticsClient { + + override fun logEvent(event: String, params: Map) { + logger.logEvent(event, params) + } + + override fun logErrorEvent(error: Throwable, params: Map) { + logger.logErrorEvent(error, params) + } +} \ No newline at end of file