Updated on 2026-08-14

This commit is contained in:
Tangem 2022-10-25 14:51:50 +03:00
commit 5c8185de36
44 changed files with 779 additions and 388 deletions

View file

@ -122,12 +122,12 @@ dependencies {
// Firebase
implementation platform('com.google.firebase:firebase-bom:26.0.0')
implementation 'com.google.firebase:firebase-crashlytics'
implementation 'com.google.firebase:firebase-config-ktx'
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-crashlytics-ktx'
// AppsFlyer
// Analytics
implementation 'com.appsflyer:af-android-sdk:6.5.1'
implementation 'com.amplitude:android-sdk:2.36.1'
// WalletConnect
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'

View file

@ -15,6 +15,7 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

View file

@ -5,16 +5,16 @@ import android.content.Context
import android.content.pm.PackageManager
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.appsflyer.AppsFlyerLib
import com.tangem.Log
import com.tangem.LogFormat
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.domain.DomainLayer
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.GlobalAnalyticsHandler
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
@ -97,15 +97,19 @@ class TapApplication : Application(), ImageLoaderFactory {
private fun initWithConfigDependency(config: Config) {
shopService = TangemShopService(this, config.shopify!!)
initAppsFlyer(this, config)
initAnalytics(this, config)
initFeedbackManager(this, preferencesStorage, config)
}
private fun initAppsFlyer(context: Context, config: Config) {
AppsFlyerLib.getInstance().init(config.appsFlyerDevKey, null, context)
AppsFlyerLib.getInstance().start(context)
val analyticsHandler = GlobalAnalyticsHandler.createDefaultAnalyticHandlers(context)
store.dispatch(GlobalAction.SetAnanlyticHandlers(analyticsHandler))
private fun initAnalytics(application: Application, config: Config) {
val globalAnalyticsHandler = GlobalAnalyticsEventHandlerBuilder(
application = application,
config = config,
isDebug = BuildConfig.DEBUG,
logConfig = LogConfig.analyticsHandlers,
jsonConverter = MoshiJsonConverter.INSTANCE,
).default()
store.dispatch(GlobalAction.SetGlobalAnalyticsHandler(globalAnalyticsHandler))
}
private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) {

View file

@ -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<String, String>) {
Timber.d(jsonConverter.prettyPrint(PrintEventModel(name, event, params)))
}
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
Timber.e(error, jsonConverter.prettyPrint(PrintEventModel(name, "error", params)))
}
}
private data class PrintEventModel(
val client: String,
val event: String,
val params: Map<String, String>,
)

View file

@ -1,89 +0,0 @@
package com.tangem.tap.common.analytics
import com.shopify.buy3.Storefront
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.extensions.filterNotNull
abstract class AnalyticsHandler {
abstract fun triggerEvent(
event: AnalyticsEvent,
card: Card? = null,
blockchain: String? = null,
params: Map<String, String> = emptyMap()
)
abstract fun triggerEvent(
event: String,
params: Map<String, String>
)
abstract fun logCardSdkError(
error: TangemSdkError,
actionToLog: Analytics.ActionToLog,
parameters: Map<AnalyticsParam, String>? = null,
card: Card? = null
)
abstract fun logError(
error: Throwable,
params: Map<String, String> = emptyMap()
)
protected fun prepareParams(card: Card?, blockchain: String? = null): Map<String, String> {
return mapOf(
AnalyticsParam.FIRMWARE.param to card?.firmwareVersion?.stringValue,
AnalyticsParam.BATCH_ID.param to card?.batchId,
AnalyticsParam.BLOCKCHAIN.param to blockchain,
).filterNotNull()
}
fun logWcEvent(event: Analytics.WcAnalyticsEvent) {
when (event) {
is Analytics.WcAnalyticsEvent.Action -> {
triggerEvent(
event = AnalyticsEvent.WC_SUCCESS_RESPONSE,
params = mapOf(
AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action.name
)
)
}
is Analytics.WcAnalyticsEvent.Error -> {
val params = mapOf(
AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action?.name,
AnalyticsParam.ERROR_DESCRIPTION.param to event.error.message
)
.filterNotNull()
logError(event.error, params)
}
is Analytics.WcAnalyticsEvent.InvalidRequest ->
triggerEvent(
event = AnalyticsEvent.WC_INVALID_REQUEST,
params = mapOf(
AnalyticsParam.WALLET_CONNECT_REQUEST.param to event.json
).filterNotNull()
)
is Analytics.WcAnalyticsEvent.Session -> {
val analyticsEvent = when (event.event) {
Analytics.WcSessionEvent.Disconnect -> AnalyticsEvent.WC_SESSION_DISCONNECTED
Analytics.WcSessionEvent.Connect -> AnalyticsEvent.WC_NEW_SESSION
}
triggerEvent(
event = analyticsEvent,
params = mapOf(
AnalyticsParam.WALLET_CONNECT_DAPP_URL.param to event.url
).filterNotNull()
)
}
}
}
fun logShopifyOrder(order: Storefront.Order) {
triggerEvent(getOrderEvent(), getOrderParams(order))
}
protected abstract fun getOrderEvent(): String
protected abstract fun getOrderParams(order: Storefront.Order): Map<String, String>
}

View file

@ -0,0 +1,3 @@
package com.tangem.tap.common.analytics

View file

@ -1,61 +0,0 @@
package com.tangem.tap.common.analytics
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.card.Card
import com.tangem.common.core.TangemSdkError
class AppsFlyerAnalyticsHandler(val context: Context): AnalyticsHandler() {
override fun triggerEvent(
event: AnalyticsEvent,
card: Card?,
blockchain: String?,
params: Map<String, String>
) {
AppsFlyerLib.getInstance().logEvent(context, event.event, prepareParams(card, blockchain))
}
override fun logCardSdkError(
error: TangemSdkError,
actionToLog: Analytics.ActionToLog,
parameters: Map<AnalyticsParam, String>?,
card: Card?
) {
//TODO
}
override fun logError(error: Throwable, params: Map<String, String>) {
//TODO
}
override fun getOrderEvent(): String = AFInAppEventType.PURCHASE
override fun getOrderParams(order: Storefront.Order): Map<String, String> {
val sku = order.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
val discountCode =
(order.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
val discountParams = if (discountCode != null ){
mapOf(AFInAppEventParameterName.COUPON_CODE to discountCode)
} else {
mapOf()
}
return mapOf(
AFInAppEventParameterName.CONTENT_ID to sku,
AFInAppEventParameterName.REVENUE to order.totalPriceV2.amount,
AFInAppEventParameterName.CURRENCY to order.currencyCode.name
) + discountParams
}
override fun triggerEvent(event: String, params: Map<String, String>) {
AppsFlyerLib.getInstance().logEvent(context, event, params)
}
}

View file

@ -1,88 +0,0 @@
package com.tangem.tap.common.analytics
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.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
import com.shopify.buy3.Storefront
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
object FirebaseAnalyticsHandler : AnalyticsHandler() {
override fun triggerEvent(
event: AnalyticsEvent,
card: Card?,
blockchain: String?,
params: Map<String, String>
) {
val fullParams = prepareParams(card, blockchain) + params
Firebase.analytics.logEvent(event.event, fullParams.toBundle())
}
fun logException(name: String, throwable: Throwable) {
Firebase.analytics.logEvent(name, bundleOf(
"message" to (throwable.message ?: "none"),
"cause_message" to (throwable.cause?.message ?: "none"),
"stack_trace" to throwable.stackTraceToString()
))
}
private fun Map<String, String>.toBundle(): Bundle {
return bundleOf(*this.toList().toTypedArray())
}
override fun logCardSdkError(
error: TangemSdkError,
actionToLog: Analytics.ActionToLog,
parameters: Map<AnalyticsParam, String>?,
card: Card?,
) {
if (error is TangemSdkError.UserCancelled) return
val params = parameters?.toMutableMap() ?: mutableMapOf()
if (card != null) params + prepareParams(card)
params[AnalyticsParam.ACTION] = actionToLog.key
params[AnalyticsParam.ERROR_CODE] = error.code.toString()
params[AnalyticsParam.ERROR_DESCRIPTION] = error.javaClass.simpleName
params[AnalyticsParam.ERROR_KEY] = "TangemSdkError"
params.forEach {
FirebaseCrashlytics.getInstance().setCustomKey(it.key.param, it.value)
}
val cardError = TangemSdk.map(error)
FirebaseCrashlytics.getInstance().recordException(cardError)
}
override fun logError(error: Throwable, params: Map<String, String>) {
params.forEach { (key, value) -> FirebaseCrashlytics.getInstance().setCustomKey(key, value) }
FirebaseCrashlytics.getInstance().recordException(error)
}
override fun getOrderEvent(): String = FirebaseAnalytics.Event.PURCHASE
override fun getOrderParams(order: Storefront.Order): Map<String, String> {
val sku = order.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
val discountCode =
(order.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 order.totalPriceV2.amount,
FirebaseAnalytics.Param.CURRENCY to order.currencyCode.name
) + discountParams
}
override fun triggerEvent(event: String, params: Map<String, String>) {
Firebase.analytics.logEvent(event, params.toBundle())
}
}

View file

@ -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<AnalyticsEventHandler>()
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)
}
}

View file

@ -1,53 +1,109 @@
package com.tangem.tap.common.analytics
import android.content.Context
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
class GlobalAnalyticsHandler(val analyticsHandlers: List<AnalyticsHandler>) :
AnalyticsHandler() {
override fun triggerEvent(
interface GlobalAnalyticsEventHandler : AnalyticsEventHandler,
ErrorEventHandler,
SdkErrorEventHandler,
ShopifyOrderEventHandler
class GlobalAnalyticsHandler(
private val analyticsHandlers: List<AnalyticsEventHandler>,
) : GlobalAnalyticsEventHandler {
override fun handleEvent(event: String, params: Map<String, String>) {
analyticsHandlers.forEach { it.handleEvent(event, params) }
}
override fun handleAnalyticsEvent(
event: AnalyticsEvent,
params: Map<String, String>,
card: Card?,
blockchain: String?,
params: Map<String, String>
) {
analyticsHandlers.forEach { it.triggerEvent(event, card, blockchain, params) }
analyticsHandlers.forEach { it.handleAnalyticsEvent(event, params, card, blockchain) }
}
override fun triggerEvent(event: String, params: Map<String, String>) {
analyticsHandlers.forEach { it.triggerEvent(event, params) }
override fun handleErrorEvent(error: Throwable, params: Map<String, String>) {
analyticsHandlers.filterIsInstance<ErrorEventLogger>().forEach {
it.logErrorEvent(error, params)
}
}
override fun logCardSdkError(
override fun handleCardSdkErrorEvent(
error: TangemSdkError,
actionToLog: Analytics.ActionToLog,
parameters: Map<AnalyticsParam, String>?,
card: Card?
action: Analytics.ActionToLog,
params: Map<AnalyticsParam, String>,
card: Card?,
) {
analyticsHandlers.forEach { it.logCardSdkError(error, actionToLog, parameters, card) }
analyticsHandlers.filterIsInstance<CardSdkErrorEventHandler>().forEach {
it.handleCardSdkErrorEvent(error, action, params, card)
}
}
override fun logError(error: Throwable, params: Map<String, String>) {
analyticsHandlers.forEach { it.logError(error, params) }
override fun handleBlockchainSdkErrorEvent(
error: BlockchainError,
action: Analytics.ActionToLog,
params: Map<AnalyticsParam, String>,
card: Card?,
) {
analyticsHandlers.filterIsInstance<BlockchainSdkErrorEventHandler>().forEach {
it.handleBlockchainSdkErrorEvent(error, action, params, card)
}
}
override fun getOrderEvent(): String {
return ""
override fun handleShopifyOrderEvent(order: Storefront.Order) {
analyticsHandlers.filterIsInstance<ShopifyOrderEventHandler>().forEach {
it.handleShopifyOrderEvent(order)
}
}
}
override fun getOrderParams(order: Storefront.Order): Map<String, String> {
return emptyMap()
}
companion object {
fun createDefaultAnalyticHandlers(context: Context): GlobalAnalyticsHandler {
return GlobalAnalyticsHandler(
listOf(
FirebaseAnalyticsHandler,
AppsFlyerAnalyticsHandler(context)
)
fun GlobalAnalyticsEventHandler.logWcEvent(event: Analytics.WcAnalyticsEvent) {
when (event) {
is Analytics.WcAnalyticsEvent.Action -> {
handleAnalyticsEvent(
event = AnalyticsEvent.WC_SUCCESS_RESPONSE,
params = mapOf(
AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action.name,
),
)
}
is Analytics.WcAnalyticsEvent.Error -> {
val params = mapOf(
AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action?.name,
AnalyticsParam.ERROR_DESCRIPTION.param to event.error.message,
).filterNotNull()
handleErrorEvent(event.error, params)
}
is Analytics.WcAnalyticsEvent.InvalidRequest ->
handleAnalyticsEvent(
event = AnalyticsEvent.WC_INVALID_REQUEST,
params = mapOf(
AnalyticsParam.WALLET_CONNECT_REQUEST.param to event.json,
).filterNotNull(),
)
is Analytics.WcAnalyticsEvent.Session -> {
val analyticsEvent = when (event.event) {
Analytics.WcSessionEvent.Disconnect -> AnalyticsEvent.WC_SESSION_DISCONNECTED
Analytics.WcSessionEvent.Connect -> AnalyticsEvent.WC_NEW_SESSION
}
handleAnalyticsEvent(
event = analyticsEvent,
params = mapOf(
AnalyticsParam.WALLET_CONNECT_DAPP_URL.param to event.url,
).filterNotNull(),
)
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.tap.common.analytics.api
/**
[REDACTED_AUTHOR]
*/
interface EventLogger {
fun logEvent(
event: String,
params: Map<String, String> = emptyMap(),
)
}
interface ErrorEventLogger {
fun logErrorEvent(
error: Throwable,
params: Map<String, String> = emptyMap(),
)
}

View file

@ -0,0 +1,68 @@
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
/**
[REDACTED_AUTHOR]
*/
interface AnalyticsEventHandler {
fun handleEvent(
event: String,
params: Map<String, String> = emptyMap(),
)
fun handleAnalyticsEvent(
event: AnalyticsEvent,
params: Map<String, String> = emptyMap(),
card: Card? = null,
blockchain: String? = null,
)
fun prepareParams(
card: Card?,
blockchain: String? = null,
params: Map<String, String> = emptyMap(),
): Map<String, String> = mapOf(
AnalyticsParam.FIRMWARE.param to card?.firmwareVersion?.stringValue,
AnalyticsParam.BATCH_ID.param to card?.batchId,
AnalyticsParam.BLOCKCHAIN.param to blockchain,
).filterNotNull() + params
}
interface ErrorEventHandler {
fun handleErrorEvent(
error: Throwable,
params: Map<String, String> = emptyMap(),
)
}
interface SdkErrorEventHandler : CardSdkErrorEventHandler, BlockchainSdkErrorEventHandler
interface CardSdkErrorEventHandler {
fun handleCardSdkErrorEvent(
error: TangemSdkError,
action: Analytics.ActionToLog,
params: Map<AnalyticsParam, String> = emptyMap(),
card: Card? = null,
)
}
interface BlockchainSdkErrorEventHandler {
fun handleBlockchainSdkErrorEvent(
error: BlockchainError,
action: Analytics.ActionToLog,
params: Map<AnalyticsParam, String> = mapOf(),
card: Card? = null,
)
}
interface ShopifyOrderEventHandler {
fun handleShopifyOrderEvent(order: Storefront.Order)
}

View file

@ -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<String, String>,
)
internal class CardSdkErrorConverter : Converter<CardSdkErrorConverter.Model, ErrorEvent?> {
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<AnalyticsParam, String>,
val cardParams: Map<String, String>,
)
}
internal class BlockchainSdkErrorConverter(
private val cardSdkErrorConverter: CardSdkErrorConverter,
) : Converter<BlockchainSdkErrorConverter.Model, ErrorEvent?> {
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<AnalyticsParam, String>,
val cardParams: Map<String, String>,
)
}
internal fun Map<AnalyticsParam, String>.convertToParamValue(): Map<String, String> {
return mutableMapOf<String, String>().also { newMap ->
this.forEach {
newMap[it.key.param] = it.value
}
}
}

View file

@ -0,0 +1,23 @@
package com.tangem.tap.common.analytics.handlers.amplitude
import com.tangem.common.card.Card
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
class AmplitudeAnalyticsHandler(
private val client: AmplitudeAnalyticsClient,
) : AnalyticsEventHandler {
override fun handleEvent(event: String, params: Map<String, String>) {
client.logEvent(event, params)
}
override fun handleAnalyticsEvent(
event: AnalyticsEvent,
params: Map<String, String>,
card: Card?,
blockchain: String?,
) {
handleEvent(event.event, prepareParams(card, blockchain, params))
}
}

View file

@ -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<String, String>) {
client.logEvent(event, ParamsToJSONObjectConverter().convert(params))
}
}
private class ParamsToJSONObjectConverter : Converter<Map<String, String>, JSONObject> {
override fun convert(value: Map<String, String>): JSONObject = JSONObject().apply {
value.forEach { this.put(it.key, it.value) }
}
}

View file

@ -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<String, String>) {
logger.logEvent(event, params)
}
}

View file

@ -0,0 +1,58 @@
package com.tangem.tap.common.analytics.handlers.appsFlyer
import com.appsflyer.AFInAppEventParameterName
import com.appsflyer.AFInAppEventType
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.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
class AppsFlyerAnalyticsHandler(
private val client: AppsFlyerAnalyticsClient,
) : AnalyticsEventHandler, ShopifyOrderEventHandler {
override fun handleEvent(event: String, params: Map<String, String>) {
client.logEvent(event, params)
}
override fun handleAnalyticsEvent(
event: AnalyticsEvent,
params: Map<String, String>,
card: Card?,
blockchain: String?,
) {
handleEvent(event.event, prepareParams(card, blockchain, params))
}
override fun handleShopifyOrderEvent(order: Storefront.Order) {
handleEvent(ORDER_EVENT, OrderToParamsConverter().convert(order))
}
companion object {
const val ORDER_EVENT = AFInAppEventType.PURCHASE
}
}
private class OrderToParamsConverter : Converter<Storefront.Order, Map<String, String>> {
override fun convert(value: Storefront.Order): Map<String, String> {
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(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
}
}

View file

@ -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<String, String>) {
appsFlyerLib.logEvent(context, event, params)
}
}

View file

@ -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<String, String>) {
logger.logEvent(event, params)
}
}

View file

@ -0,0 +1,91 @@
package com.tangem.tap.common.analytics.handlers.firebase
import com.google.firebase.analytics.FirebaseAnalytics
import com.shopify.buy3.Storefront
import com.tangem.blockchain.common.BlockchainError
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.AnalyticsParam
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(
private val client: FirebaseAnalyticsClient,
) : AnalyticsEventHandler, ErrorEventHandler, SdkErrorEventHandler, ShopifyOrderEventHandler {
override fun handleEvent(event: String, params: Map<String, String>) {
client.logEvent(event, params)
}
override fun handleAnalyticsEvent(
event: AnalyticsEvent,
params: Map<String, String>,
card: Card?,
blockchain: String?,
) {
handleEvent(event.name, prepareParams(card, blockchain, params))
}
override fun handleErrorEvent(error: Throwable, params: Map<String, String>) {
client.logErrorEvent(error, params)
}
override fun handleCardSdkErrorEvent(
error: TangemSdkError,
action: Analytics.ActionToLog,
params: Map<AnalyticsParam, String>,
card: Card?,
) {
val model = CardSdkErrorConverter.Model(error, action, params, prepareParams(card))
val converter = CardSdkErrorConverter()
converter.convert(model)?.let {
handleErrorEvent(it.throwable, it.params)
}
}
override fun handleBlockchainSdkErrorEvent(
error: BlockchainError,
action: Analytics.ActionToLog,
params: Map<AnalyticsParam, String>,
card: Card?,
) {
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<Storefront.Order, Map<String, String>> {
override fun convert(value: Storefront.Order): Map<String, String> {
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
}
}

View file

@ -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<String, String>) {
fbAnalytics.logEvent(event, params.toBundle())
}
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
params.forEach { fbCrashlytics.setCustomKey(it.key, it.value) }
fbCrashlytics.recordException(error)
}
private fun Map<String, String>.toBundle(): Bundle = bundleOf(*this.toList().toTypedArray())
companion object {
const val ORDER_EVENT = FirebaseAnalytics.Event.PURCHASE
}
}

View file

@ -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<String, String>) {
logger.logEvent(event, params)
}
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
logger.logErrorEvent(error, params)
}
}

View file

@ -1,51 +0,0 @@
package com.tangem.tap.common.extensions
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.common.BlockchainError
import com.tangem.blockchain.common.BlockchainSdkError
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.AnalyticsHandler
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.features.demo.DemoTransactionSender
/**
[REDACTED_AUTHOR]
*/
fun AnalyticsHandler.logSendTransactionError(
error: BlockchainError,
action: Analytics.ActionToLog,
parameters: Map<AnalyticsParam, String>? = mapOf(),
card: Card? = null,
) {
when (val blockchainSdkError = (error as BlockchainSdkError)) {
is BlockchainSdkError.WrappedTangemError -> {
val tangemSdkError = (blockchainSdkError.tangemError as? TangemSdkError) ?: return
logCardSdkError(
error = tangemSdkError,
actionToLog = action,
parameters = parameters,
card = card,
)
}
else -> {
when {
blockchainSdkError.customMessage.contains(DemoTransactionSender.ID) -> return
else -> {
val params = parameters?.toMutableMap() ?: mutableMapOf()
params[AnalyticsParam.ACTION] = action.key
params[AnalyticsParam.ERROR_CODE] = error.code.toString()
params[AnalyticsParam.ERROR_DESCRIPTION] = "${error.javaClass.simpleName}: ${error.customMessage}"
params[AnalyticsParam.ERROR_KEY] = "BlockchainSdkError"
FirebaseCrashlytics.getInstance().apply {
params.forEach { setCustomKey(it.key.param, it.value) }
recordException(error)
}
}
}
}
}
}

View file

@ -5,7 +5,7 @@ import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemError
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.feedback.FeedbackData
import com.tangem.tap.common.feedback.FeedbackManager
@ -89,7 +89,7 @@ sealed class GlobalAction : Action {
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction()
data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction()
data class SetAnanlyticHandlers(val analyticsHandlers: GlobalAnalyticsHandler) : GlobalAction()
data class SetGlobalAnalyticsHandler(val analyticsHandler: GlobalAnalyticsEventHandler) : GlobalAction()
data class SendEmail(val feedbackData: FeedbackData) : GlobalAction()
data class OpenChat(val feedbackData: FeedbackData, val zendeskConfig: ZendeskConfig? = null) : GlobalAction()

View file

@ -163,7 +163,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
is GlobalAction.ScanCard -> {
scope.launch {
val result = tangemSdkManager.scanProduct(
store.state.globalState.analyticsHandlers,
store.state.globalState.analyticsHandler,
userTokensRepository,
action.additionalBlockchainsToDerive,
action.messageResId,

View file

@ -48,8 +48,7 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
globalState.copy(configManager = action.configManager)
}
is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager)
is GlobalAction.SetAnanlyticHandlers ->
globalState.copy(analyticsHandlers = action.analyticsHandlers)
is GlobalAction.SetGlobalAnalyticsHandler -> globalState.copy(analyticsHandler = action.analyticsHandler)
is GlobalAction.UpdateWalletSignedHashes -> {
val card = globalState.scanResponse?.card ?: return globalState
val wallet = card.wallet(action.walletPublicKey) ?: return globalState

View file

@ -1,7 +1,7 @@
package com.tangem.tap.common.redux.global
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.feedback.FeedbackManager
import com.tangem.tap.common.redux.StateDialog
@ -27,7 +27,7 @@ data class GlobalState(
val dialog: StateDialog? = null,
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
val resources: AndroidResources = AndroidResources(),
val analyticsHandlers: AnalyticsHandler? = null,
val analyticsHandler: GlobalAnalyticsEventHandler? = null,
val userCountryCode: String? = null,
) : StateType

View file

@ -4,7 +4,8 @@ import android.app.Application
import android.content.Intent
import com.google.android.gms.wallet.PaymentData
import com.shopify.buy3.Storefront
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.common.extensions.ifNotNull
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.common.shop.data.ProductType
import com.tangem.tap.common.shop.data.TangemProduct
@ -195,15 +196,10 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
return checkouts[productType]!!.webUrl
}
suspend fun waitForCheckout(
productType: ProductType,
analyticsHandler: AnalyticsHandler?
) {
suspend fun waitForCheckout(productType: ProductType, analyticsHandler: GlobalAnalyticsEventHandler?) {
val result = shopifyService.checkout(true, checkouts[productType]!!.id)
result.onSuccess {
if (it.order != null) {
analyticsHandler?.logShopifyOrder(order = it.order)
}
ifNotNull(analyticsHandler, it.order) { handler, order -> handler.handleShopifyOrderEvent(order) }
}
}

View file

@ -28,8 +28,8 @@ import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.operations.pins.SetUserCodeCommand
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
import com.tangem.tap.domain.tasks.product.ScanProductTask
@ -45,12 +45,12 @@ import kotlin.coroutines.suspendCoroutine
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
suspend fun scanProduct(
analyticsHandler: AnalyticsHandler?,
analyticsHandler: GlobalAnalyticsEventHandler?,
userTokensRepository: UserTokensRepository,
additionalBlockchainsToDerive: Collection<Blockchain>? = null,
messageRes: Int? = null,
): CompletionResult<ScanResponse> {
analyticsHandler?.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
analyticsHandler?.handleAnalyticsEvent(AnalyticsEvent.READY_TO_SCAN, card = null)
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
return runTaskAsyncReturnOnMain(
@ -70,30 +70,30 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
}
private fun sendScanResultsToAnalytics(
analyticsHandler: AnalyticsHandler?,
analyticsHandler: GlobalAnalyticsEventHandler?,
result: CompletionResult<ScanResponse>,
) {
when (result) {
is CompletionResult.Success -> {
analyticsHandler?.triggerEvent(
analyticsHandler?.handleAnalyticsEvent(
event = AnalyticsEvent.CARD_IS_SCANNED,
card = result.data.card,
blockchain = result.data.walletData?.blockchain,
)
if (DemoHelper.isDemoCard(result.data)) {
analyticsHandler?.triggerEvent(
analyticsHandler?.handleAnalyticsEvent(
event = AnalyticsEvent.DEMO_MODE_ACTIVATED,
params = mapOf(AnalyticsParam.CARD_ID.param to result.data.card.cardId),
card = result.data.card,
blockchain = result.data.walletData?.blockchain,
params = mapOf(AnalyticsParam.CARD_ID.param to result.data.card.cardId),
)
}
}
is CompletionResult.Failure ->
(result.error as? TangemSdkError)?.let { error ->
analyticsHandler?.logCardSdkError(
analyticsHandler?.handleCardSdkErrorEvent(
error = error,
actionToLog = Analytics.ActionToLog.Scan,
action = Analytics.ActionToLog.Scan,
)
}
}
@ -101,7 +101,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
suspend fun createWallet(cardId: String?): CompletionResult<Card> {
return runTaskAsyncReturnOnMain(
CreateWalletAndRescanTask(), cardId,
CreateWalletAndRescanTask(),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body)),
)
}

View file

@ -16,6 +16,7 @@ data class Config(
val mercuryoWidgetId: String = "",
val mercuryoSecret: String = "",
val appsFlyerDevKey: String = "",
val amplitudeApiKey: String = "",
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
val isSendingToPayIdEnabled: Boolean = true,
val isTopUpEnabled: Boolean = false,
@ -90,6 +91,7 @@ class ConfigManager {
tronGridApiKey = values.tronGridApiKey
),
appsFlyerDevKey = values.appsFlyerDevKey,
amplitudeApiKey = values.amplitudeApiKey,
shopify = values.shopifyShop,
zendesk = values.zendesk,
saltPayConfig = values.saltPay,
@ -107,6 +109,7 @@ class ConfigManager {
infuraProjectId = values.infuraProjectId
),
appsFlyerDevKey = values.appsFlyerDevKey,
amplitudeApiKey = values.amplitudeApiKey,
shopify = values.shopifyShop,
zendesk = values.zendesk,
saltPayConfig = values.saltPay,

View file

@ -30,9 +30,13 @@ class ConfigValueModel(
val zendesk: ZendeskConfig?,
val saltPay: SaltPayConfig,
val tronGridApiKey: String,
val amplitudeApiKey: String,
)
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {
class ConfigModel(
val features: FeatureModel?,
val configValues: ConfigValueModel?,
) {
companion object {
fun empty(): ConfigModel = ConfigModel(null, null)
}

View file

@ -15,13 +15,13 @@ import com.tangem.network.common.MoshiConverter
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.tap.common.AssetReader
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
import com.tangem.tap.tangemSdkManager
class TwinCardsManager(
private val card: Card,
assetReader: AssetReader,
val analyticsHandler: AnalyticsHandler?
val analyticsHandler: GlobalAnalyticsEventHandler?
) {
private val currentCardId: String = card.cardId
@ -37,7 +37,7 @@ class TwinCardsManager(
is CompletionResult.Success -> currentCardPublicKey = response.data.wallet.publicKey.toHexString()
is CompletionResult.Failure -> {
(response.error as? TangemSdkError)?.let { error ->
analyticsHandler?.logCardSdkError(
analyticsHandler?.handleCardSdkErrorEvent(
error,
Analytics.ActionToLog.CreateWallet,
card = card
@ -67,7 +67,7 @@ class TwinCardsManager(
}
is CompletionResult.Failure -> {
(response.error as? TangemSdkError)?.let { error ->
analyticsHandler?.logCardSdkError(
analyticsHandler?.handleCardSdkErrorEvent(
error,
Analytics.ActionToLog.CreateWallet,
card = card
@ -87,7 +87,7 @@ class TwinCardsManager(
is CompletionResult.Success -> Result.Success(response.data)
is CompletionResult.Failure -> {
(response.error as? TangemSdkError)?.let { error ->
analyticsHandler?.logCardSdkError(
analyticsHandler?.handleCardSdkErrorEvent(
error,
Analytics.ActionToLog.WriteIssuerData,
card = card

View file

@ -5,6 +5,7 @@ import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.guard
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.logWcEvent
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
@ -222,7 +223,7 @@ class WalletConnectManager {
}
private fun onSessionClosed(session: WCSession) {
store.state.globalState.analyticsHandlers?.logWcEvent(
store.state.globalState.analyticsHandler?.logWcEvent(
Analytics.WcAnalyticsEvent.Session(
Analytics.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url
)
@ -371,7 +372,7 @@ class WalletConnectManager {
),
)
}
store.state.globalState.analyticsHandlers?.logWcEvent(
store.state.globalState.analyticsHandler?.logWcEvent(
Analytics.WcAnalyticsEvent.Session(
Analytics.WcSessionEvent.Connect, peer.url,
),
@ -386,7 +387,7 @@ class WalletConnectManager {
}
client.onEthSendTransaction = { id: Long, transaction: WCEthereumTransaction ->
Timber.d("onEthSendTransaction: $transaction")
store.state.globalState.analyticsHandlers?.logWcEvent(
store.state.globalState.analyticsHandler?.logWcEvent(
Analytics.WcAnalyticsEvent.Action(
Analytics.WcAction.SendTransaction
)
@ -404,7 +405,7 @@ class WalletConnectManager {
}
client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction ->
Timber.d("onEthSignTransaction: $transaction")
store.state.globalState.analyticsHandlers?.logWcEvent(
store.state.globalState.analyticsHandler?.logWcEvent(
Analytics.WcAnalyticsEvent.Action(
Analytics.WcAction.SignTransaction
)
@ -422,7 +423,7 @@ class WalletConnectManager {
}
client.onEthSign = { id: Long, message: WCEthereumSignMessage ->
Timber.d("onEthSign: $message")
store.state.globalState.analyticsHandlers?.logWcEvent(
store.state.globalState.analyticsHandler?.logWcEvent(
Analytics.WcAnalyticsEvent.Action(
Analytics.WcAction.PersonalSign
)
@ -498,7 +499,7 @@ class WalletConnectManager {
val message = EthSignHelper.tryToParseEthTypedMessage(request)
if (message != null) {
Timber.d("onEthSign_v4: $message")
store.state.globalState.analyticsHandlers?.logWcEvent(
store.state.globalState.analyticsHandler?.logWcEvent(
Analytics.WcAnalyticsEvent.Action(
Analytics.WcAction.PersonalSign
)

View file

@ -25,7 +25,6 @@ import com.tangem.common.extensions.toHexString
import com.tangem.crypto.CryptoUtils
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.logSendTransactionError
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
@ -144,7 +143,7 @@ class WalletConnectSdkHelper {
HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash
}
is SimpleResult.Failure -> {
store.state.globalState.analyticsHandlers?.logSendTransactionError(
store.state.globalState.analyticsHandler?.handleBlockchainSdkErrorEvent(
result.error,
Analytics.ActionToLog.WalletConnectTransaction,
)
@ -174,7 +173,7 @@ class WalletConnectSdkHelper {
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandlers?.logCardSdkError(
store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent(
error,
Analytics.ActionToLog.WalletConnectSign,
)
@ -203,7 +202,7 @@ class WalletConnectSdkHelper {
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandlers?.logCardSdkError(
store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent(
error,
Analytics.ActionToLog.WalletConnectTransaction,
)
@ -279,7 +278,7 @@ class WalletConnectSdkHelper {
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandlers?.logCardSdkError(
store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent(
error,
Analytics.ActionToLog.WalletConnectSign,
)

View file

@ -110,7 +110,7 @@ class DetailsMiddleware {
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandlers?.logCardSdkError(
store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent(
error,
Analytics.ActionToLog.PurgeWallet,
card = store.state.detailsState.scanResponse?.card,
@ -154,10 +154,10 @@ class DetailsMiddleware {
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandlers?.logCardSdkError(
store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent(
error = error,
actionToLog = Analytics.ActionToLog.ChangeSecOptions,
parameters = mapOf(
action = Analytics.ActionToLog.ChangeSecOptions,
params = mapOf(
AnalyticsParam.NEW_SECURITY_OPTION to
(selectedOption?.name ?: ""),
),

View file

@ -91,7 +91,7 @@ private fun handleHomeAction(appState: () -> AppState?, action: Action, dispatch
RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> store.dispatchOpenUrl(BUY_WALLET_URL)
else -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
}
store.state.globalState.analyticsHandlers?.triggerEvent(
store.state.globalState.analyticsHandler?.handleAnalyticsEvent(
event = AnalyticsEvent.GET_CARD,
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.WELCOME.param),
)

View file

@ -139,7 +139,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
val manager = TwinCardsManager(
card = getScanResponse().card,
assetReader = action.reader,
analyticsHandler = store.state.globalState.analyticsHandlers
analyticsHandler = store.state.globalState.analyticsHandler
)
store.dispatch(TwinCardsAction.CardsManager.Set(manager))

View file

@ -23,7 +23,6 @@ import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.logSendTransactionError
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.AppDialog
@ -222,7 +221,7 @@ private fun sendTransaction(
when (sendResult) {
is SimpleResult.Success -> {
store.state.globalState.analyticsHandlers?.triggerEvent(
store.state.globalState.analyticsHandler?.handleAnalyticsEvent(
event = AnalyticsEvent.TRANSACTION_IS_SENT,
card = card,
blockchain = walletManager.wallet.blockchain.currency,
@ -251,10 +250,10 @@ private fun sendTransaction(
feeAmount = feeAmount,
destinationAddress = destinationAddress,
)
store.state.globalState.analyticsHandlers?.logSendTransactionError(
store.state.globalState.analyticsHandler?.handleBlockchainSdkErrorEvent(
error = sendResult.error,
action = Analytics.ActionToLog.SendTransaction,
parameters = mapOf(AnalyticsParam.BLOCKCHAIN to walletManager.wallet.blockchain.currency),
params = mapOf(AnalyticsParam.BLOCKCHAIN to walletManager.wallet.blockchain.currency),
card = card,
)

View file

@ -121,7 +121,7 @@ private fun handle(action: Action) {
scope.launch {
shopService.waitForCheckout(
productType = shopState.selectedProduct,
analyticsHandler = store.state.globalState.analyticsHandlers
analyticsHandler = store.state.globalState.analyticsHandler
)
}
}

View file

@ -186,7 +186,7 @@ class WalletMiddleware {
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandlers?.logCardSdkError(
store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent(
error,
Analytics.ActionToLog.CreateWallet,
card = store.state.detailsState.scanResponse?.card

View file

@ -25,10 +25,10 @@ import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.wallet.R
import java.math.BigDecimal
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.math.BigDecimal
class WarningsMiddleware {
fun handle(action: WalletAction.Warnings, globalState: GlobalState?) {
@ -81,7 +81,7 @@ class WarningsMiddleware {
preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds()
}
if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) {
store.state.globalState.analyticsHandlers?.triggerEvent(AnalyticsEvent.APP_RATING_DISPLAYED)
store.state.globalState.analyticsHandler?.handleAnalyticsEvent(AnalyticsEvent.APP_RATING_DISPLAYED)
addWarningMessage(WarningMessagesManager.appRatingWarning(), true)
}
}

View file

@ -107,14 +107,14 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
binding.groupControlsTemporary.hide()
binding.groupControlsRating.show()
binding.btnClose.show()
val analyticsHandler = store.state.globalState.analyticsHandlers
val analyticsHandler = store.state.globalState.analyticsHandler
binding.btnClose.setOnClickListener {
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS)
analyticsHandler?.handleAnalyticsEvent(AnalyticsEvent.APP_RATING_DISMISS)
store.dispatch(GlobalAction.HideWarningMessage(warning))
store.dispatch(WalletAction.Warnings.AppRating.RemindLater)
}
binding.btnCanBeBetter.setOnClickListener {
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE)
analyticsHandler?.handleAnalyticsEvent(AnalyticsEvent.APP_RATING_NEGATIVE)
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
store.dispatch(GlobalAction.HideWarningMessage(warning))
store.dispatch(GlobalAction.SendEmail(RateCanBeBetterEmail()))
@ -122,7 +122,7 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
binding.btnReallyCool.setOnClickListener {
val activity = binding.root.context.getActivity() ?: return@setOnClickListener
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE)
analyticsHandler?.handleAnalyticsEvent(AnalyticsEvent.APP_RATING_POSITIVE)
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
val reviewManager = ReviewManagerFactory.create(activity)
val task = reviewManager.requestReviewFlow()

View file

@ -0,0 +1,8 @@
package com.tangem.common
/**
[REDACTED_AUTHOR]
*/
interface Converter<I, O> {
fun convert(value: I): O
}

View file

@ -7,6 +7,7 @@ object LogConfig {
val storeAction: Boolean = BuildConfig.DEBUG
val zendesk: Boolean = false
val network: NetworkLogConfig = NetworkLogConfig
val analyticsHandlers: AnalyticsHandlersLogConfig = AnalyticsHandlersLogConfig
}
object NetworkLogConfig {
@ -15,4 +16,10 @@ object NetworkLogConfig {
val tangemTechService: Boolean = BuildConfig.DEBUG
val paymentologyApiService: Boolean = BuildConfig.DEBUG
val blockchainSdkNetwork: Boolean = BuildConfig.DEBUG
}
object AnalyticsHandlersLogConfig {
val firebase: Boolean = false
val appsFlyer: Boolean = false
val amplitude: Boolean = false
}