Updated on 2026-08-14
This commit is contained in:
commit
a2a37bc920
105 changed files with 1734 additions and 1190 deletions
|
|
@ -153,7 +153,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
primaryButtonAction = state.dialog.onOk,
|
||||
)
|
||||
is WalletDialog.RussianCardholdersWarningDialog ->
|
||||
RussianCardholdersWarningBottomSheetDialog(context, state.dialog.topUpUrl)
|
||||
RussianCardholdersWarningBottomSheetDialog(context, state.dialog.data)
|
||||
else -> null
|
||||
}
|
||||
dialog?.show()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import com.tangem.domain.common.FeatureCoroutineExceptionHandler
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventFilter
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsFilterHolder
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerHolder
|
||||
import com.tangem.tap.common.analytics.api.ParamsInterceptor
|
||||
import com.tangem.tap.common.analytics.api.ParamsInterceptorHolder
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsEvent
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface GlobalAnalyticsEventHandler : AnalyticsEventHandler,
|
||||
AnalyticsHandlerHolder,
|
||||
AnalyticsFilterHolder,
|
||||
ParamsInterceptorHolder
|
||||
|
||||
object Analytics : GlobalAnalyticsEventHandler {
|
||||
|
||||
private val analyticsScope: CoroutineScope by lazy { createScope() }
|
||||
|
||||
private val handlers = mutableMapOf<String, AnalyticsHandler>()
|
||||
private val paramsInterceptors = mutableMapOf<String, ParamsInterceptor>()
|
||||
private val analyticsFilters = mutableSetOf<AnalyticsEventFilter>()
|
||||
|
||||
private val analyticsHandlers: List<AnalyticsHandler>
|
||||
get() = handlers.values.toList()
|
||||
|
||||
override fun addHandler(name: String, handler: AnalyticsHandler) {
|
||||
handlers[name] = handler
|
||||
}
|
||||
|
||||
override fun removeHandler(name: String): AnalyticsHandler? {
|
||||
return handlers.remove(name)
|
||||
}
|
||||
|
||||
override fun addFilter(filter: AnalyticsEventFilter) {
|
||||
analyticsFilters.add(filter)
|
||||
}
|
||||
|
||||
override fun removeFilter(filter: AnalyticsEventFilter): Boolean {
|
||||
return analyticsFilters.remove(filter)
|
||||
}
|
||||
|
||||
override fun addParamsInterceptor(interceptor: ParamsInterceptor) {
|
||||
paramsInterceptors[interceptor.id()] = interceptor
|
||||
}
|
||||
|
||||
override fun removeParamsInterceptor(interceptor: ParamsInterceptor): ParamsInterceptor? {
|
||||
return paramsInterceptors.remove(interceptor.id())
|
||||
}
|
||||
|
||||
override fun send(event: AnalyticsEvent) {
|
||||
analyticsScope.launch {
|
||||
event.params = applyParamsInterceptors(event)
|
||||
val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(event) }
|
||||
|
||||
when {
|
||||
eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(event) }
|
||||
eventFilter.canBeSent(event) -> {
|
||||
analyticsHandlers
|
||||
.filter { handler -> eventFilter.canBeConsumedByHandler(handler, event) }
|
||||
.forEach { handler -> handler.send(event) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyParamsInterceptors(event: AnalyticsEvent): MutableMap<String, String> {
|
||||
val interceptedParams = event.params.toMutableMap()
|
||||
paramsInterceptors.values
|
||||
.filter { it.canBeAppliedTo(event) }
|
||||
.forEach { it.intercept(interceptedParams) }
|
||||
|
||||
return interceptedParams
|
||||
}
|
||||
|
||||
private fun createScope(): CoroutineScope {
|
||||
val name = "Analytics"
|
||||
val dispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher()
|
||||
val exHandler = FeatureCoroutineExceptionHandler.create(name)
|
||||
return CoroutineScope(SupervisorJob() + dispatcher + CoroutineName(name) + exHandler)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
enum class AnalyticsEventAnOld(val event: String) {
|
||||
CARD_IS_SCANNED("card_is_scanned"),
|
||||
TRANSACTION_IS_SENT("transaction_is_sent"),
|
||||
READY_TO_SCAN("ready_to_scan"),
|
||||
DEMO_MODE_ACTIVATED("demo_mode_activated"),
|
||||
|
||||
APP_RATING_DISPLAYED("rate_app_warning_displayed"),
|
||||
APP_RATING_DISMISS("dismiss_rate_app_warning"),
|
||||
APP_RATING_NEGATIVE("negative_rate_app_feedback"),
|
||||
APP_RATING_POSITIVE("positive_rate_app_feedback"),
|
||||
|
||||
WC_SUCCESS_RESPONSE("wallet_connect_success_response"),
|
||||
WC_INVALID_REQUEST("wallet_connect_invalid_request"),
|
||||
WC_NEW_SESSION("wallet_connect_new_session"),
|
||||
WC_SESSION_DISCONNECTED("wallet_connect_session_disconnected"),
|
||||
|
||||
GET_CARD("get_card"),
|
||||
}
|
||||
|
||||
enum class AnalyticsParamAnOld(val param: String) {
|
||||
BLOCKCHAIN("blockchain"),
|
||||
CARD_ID("cardId"),
|
||||
BATCH_ID("batch_id"),
|
||||
FIRMWARE("firmware"),
|
||||
ACTION("action"),
|
||||
ERROR_DESCRIPTION("error_description"),
|
||||
ERROR_CODE("error_code"),
|
||||
NEW_SECURITY_OPTION("new_security_option"),
|
||||
ERROR_KEY("Tangem SDK error key"),
|
||||
|
||||
WALLET_CONNECT_ACTION("wallet_connect_action"),
|
||||
WALLET_CONNECT_REQUEST("wallet_connect_request"),
|
||||
WALLET_CONNECT_DAPP_URL("wallet_connect_dapp_url"),
|
||||
|
||||
SOURCE("source"),
|
||||
}
|
||||
|
||||
enum class GetCardSourceParamsAnOld(val param: String) {
|
||||
WELCOME("welcome"),
|
||||
ONBOARDING_BUY_MORE("wallet_onboaring_buy_more_cards"),
|
||||
ONBOARDING("wallet_onboarding")
|
||||
}
|
||||
|
||||
object AnalyticsAnOld {
|
||||
enum class ActionToLog(val key: String) {
|
||||
Scan("tap_scan_task"),
|
||||
SendTransaction("send_transaction"),
|
||||
WalletConnectSign("wallet_connect_personal_sign"),
|
||||
WalletConnectTransaction("wallet_connect_tx_sign"),
|
||||
ReadPinSettings("read_pin_settings"),
|
||||
ChangeSecOptions("change_sec_options"),
|
||||
CreateWallet("create_wallet"),
|
||||
PurgeWallet("purge_wallet"),
|
||||
WriteIssuerData("write_issuer_data"),
|
||||
}
|
||||
|
||||
enum class WcSessionEvent { Disconnect, Connect }
|
||||
|
||||
enum class WcAction { PersonalSign, SignTransaction, SendTransaction }
|
||||
|
||||
sealed class WcAnalyticsEvent {
|
||||
data class Error(val error: Throwable, val action: WcAction?) :
|
||||
WcAnalyticsEvent()
|
||||
|
||||
data class Session(val event: WcSessionEvent, val url: String?) : WcAnalyticsEvent()
|
||||
data class Action(val action: WcAction) : WcAnalyticsEvent()
|
||||
data class InvalidRequest(val json: String?) : WcAnalyticsEvent()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventFilter
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
import com.tangem.tap.common.analytics.api.ParamsInterceptor
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AnalyticsFactory {
|
||||
|
||||
private val builders = mutableListOf<AnalyticsHandlerBuilder>()
|
||||
private val filters = mutableListOf<AnalyticsEventFilter>()
|
||||
private val interceptors = mutableListOf<ParamsInterceptor>()
|
||||
|
||||
fun addHandlerBuilder(builder: AnalyticsHandlerBuilder) {
|
||||
builders.add(builder)
|
||||
}
|
||||
|
||||
fun addFilter(filter: AnalyticsEventFilter) {
|
||||
filters.add(filter)
|
||||
}
|
||||
|
||||
fun addParamsInterceptor(interceptor: ParamsInterceptor) {
|
||||
interceptors.add(interceptor)
|
||||
}
|
||||
|
||||
fun build(analytics: Analytics, data: AnalyticsHandlerBuilder.Data) {
|
||||
builders.mapNotNull { it.build(data) }.forEach { analytics.addHandler(it.id(), it) }
|
||||
filters.forEach { analytics.addFilter(it) }
|
||||
interceptors.forEach { analytics.addParamsInterceptor(it) }
|
||||
|
||||
builders.clear()
|
||||
filters.clear()
|
||||
interceptors.clear()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
|
||||
|
|
@ -1,79 +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.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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
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.analytics.events.AnalyticsEvent
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
|
||||
interface GlobalAnalyticsEventHandler : AnalyticsEventHandler,
|
||||
ErrorEventHandler,
|
||||
SdkErrorEventHandler,
|
||||
ShopifyOrderEventHandler {
|
||||
|
||||
fun attachToAllEvents(key: String, value: String)
|
||||
|
||||
companion object {
|
||||
fun stub(): GlobalAnalyticsEventHandler {
|
||||
return object : GlobalAnalyticsEventHandler {
|
||||
override fun attachToAllEvents(key: String, value: String) {}
|
||||
override fun handleEvent(event: String, params: Map<String, String>) {}
|
||||
override fun handleErrorEvent(error: Throwable, params: Map<String, String>) {}
|
||||
override fun handleCardSdkErrorEvent(
|
||||
error: TangemSdkError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String>,
|
||||
card: Card?,
|
||||
) {
|
||||
}
|
||||
|
||||
override fun handleBlockchainSdkErrorEvent(
|
||||
error: BlockchainError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String>,
|
||||
card: Card?,
|
||||
) {
|
||||
}
|
||||
|
||||
override fun handleShopifyOrderEvent(order: Storefront.Order) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GlobalAnalyticsHandler(
|
||||
private val analyticsHandlers: List<AnalyticsEventHandler>,
|
||||
) : GlobalAnalyticsEventHandler {
|
||||
|
||||
private val attachToAllEventsParams: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
override fun attachToAllEvents(key: String, value: String) {
|
||||
attachToAllEventsParams[key] = value
|
||||
}
|
||||
|
||||
override fun handleEvent(event: String, params: Map<String, String>) {
|
||||
analyticsHandlers.forEach { it.handleEvent(event, params) }
|
||||
}
|
||||
|
||||
override fun handleAnalyticsEvent(
|
||||
event: AnalyticsEvent,
|
||||
card: Card?,
|
||||
blockchain: String?,
|
||||
) {
|
||||
analyticsHandlers.forEach { it.handleAnalyticsEvent(event, card, blockchain) }
|
||||
}
|
||||
|
||||
override fun handleAnalyticsEvent(
|
||||
event: AnalyticsEventAnOld,
|
||||
params: Map<String, String>,
|
||||
card: Card?,
|
||||
blockchain: String?,
|
||||
) {
|
||||
analyticsHandlers.forEach { it.handleAnalyticsEvent(event, params, card, blockchain) }
|
||||
}
|
||||
|
||||
override fun handleErrorEvent(error: Throwable, params: Map<String, String>) {
|
||||
analyticsHandlers.filterIsInstance<ErrorEventLogger>().forEach {
|
||||
it.logErrorEvent(error, params)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleCardSdkErrorEvent(
|
||||
error: TangemSdkError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String>,
|
||||
card: Card?,
|
||||
) {
|
||||
analyticsHandlers.filterIsInstance<CardSdkErrorEventHandler>().forEach {
|
||||
it.handleCardSdkErrorEvent(error, action, params, card)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleBlockchainSdkErrorEvent(
|
||||
error: BlockchainError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String>,
|
||||
card: Card?,
|
||||
) {
|
||||
analyticsHandlers.filterIsInstance<BlockchainSdkErrorEventHandler>().forEach {
|
||||
it.handleBlockchainSdkErrorEvent(error, action, params, card)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleShopifyOrderEvent(order: Storefront.Order) {
|
||||
analyticsHandlers.filterIsInstance<ShopifyOrderEventHandler>().forEach {
|
||||
it.handleShopifyOrderEvent(order)
|
||||
}
|
||||
}
|
||||
|
||||
override fun prepareParams(card: Card?, blockchain: String?, params: Map<String, String>): Map<String, String> {
|
||||
return super.prepareParams(card, blockchain, params).toMutableMap().apply {
|
||||
putAll(attachToAllEventsParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun GlobalAnalyticsEventHandler.logWcEvent(event: AnalyticsAnOld.WcAnalyticsEvent) {
|
||||
when (event) {
|
||||
is AnalyticsAnOld.WcAnalyticsEvent.Action -> {
|
||||
handleAnalyticsEvent(
|
||||
event = AnalyticsEventAnOld.WC_SUCCESS_RESPONSE,
|
||||
params = mapOf(
|
||||
AnalyticsParamAnOld.WALLET_CONNECT_ACTION.param to event.action.name,
|
||||
),
|
||||
)
|
||||
}
|
||||
is AnalyticsAnOld.WcAnalyticsEvent.Error -> {
|
||||
val params = mapOf(
|
||||
AnalyticsParamAnOld.WALLET_CONNECT_ACTION.param to event.action?.name,
|
||||
AnalyticsParamAnOld.ERROR_DESCRIPTION.param to event.error.message,
|
||||
).filterNotNull()
|
||||
handleErrorEvent(event.error, params)
|
||||
}
|
||||
is AnalyticsAnOld.WcAnalyticsEvent.InvalidRequest ->
|
||||
handleAnalyticsEvent(
|
||||
event = AnalyticsEventAnOld.WC_INVALID_REQUEST,
|
||||
params = mapOf(
|
||||
AnalyticsParamAnOld.WALLET_CONNECT_REQUEST.param to event.json,
|
||||
).filterNotNull(),
|
||||
)
|
||||
is AnalyticsAnOld.WcAnalyticsEvent.Session -> {
|
||||
val analyticsEvent = when (event.event) {
|
||||
AnalyticsAnOld.WcSessionEvent.Disconnect -> AnalyticsEventAnOld.WC_SESSION_DISCONNECTED
|
||||
AnalyticsAnOld.WcSessionEvent.Connect -> AnalyticsEventAnOld.WC_NEW_SESSION
|
||||
}
|
||||
handleAnalyticsEvent(
|
||||
event = analyticsEvent,
|
||||
params = mapOf(
|
||||
AnalyticsParamAnOld.WALLET_CONNECT_DAPP_URL.param to event.url,
|
||||
).filterNotNull(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.tap.common.analytics.api
|
||||
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
* The filter is used to separate events to send and determine which handler it can be processed with.
|
||||
*/
|
||||
interface AnalyticsEventFilter {
|
||||
/**
|
||||
* Recognizes the event and checks if the given filter can be applied to the event.
|
||||
*/
|
||||
fun canBeAppliedTo(event: AnalyticsEvent): Boolean
|
||||
|
||||
/**
|
||||
* An internal filter check that, on external or internal conditions, recognizes the possibility of
|
||||
* sending an event.
|
||||
*/
|
||||
fun canBeSent(event: AnalyticsEvent): Boolean
|
||||
|
||||
/**
|
||||
* Performs a check to see if the event can be dispatched by a specific handler
|
||||
*/
|
||||
fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean
|
||||
}
|
||||
|
||||
interface AnalyticsFilterHolder {
|
||||
fun addFilter(filter: AnalyticsEventFilter)
|
||||
fun removeFilter(filter: AnalyticsEventFilter): Boolean
|
||||
}
|
||||
|
|
@ -1,88 +1,50 @@
|
|||
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.AnalyticsAnOld
|
||||
import com.tangem.tap.common.analytics.AnalyticsEventAnOld
|
||||
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
|
||||
import android.app.Application
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.domain.common.AnalyticsHandlersLogConfig
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
import com.tangem.tap.domain.configurable.config.Config
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface AnalyticsEventHandler {
|
||||
fun handleEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
)
|
||||
fun send(event: AnalyticsEvent)
|
||||
}
|
||||
|
||||
fun handleAnalyticsEvent(
|
||||
event: AnalyticsEvent,
|
||||
card: Card? = null,
|
||||
blockchain: String? = null,
|
||||
) {
|
||||
handleEvent(
|
||||
event = prepareEventString(event.category, event.event),
|
||||
params = prepareParams(card, blockchain, event.params),
|
||||
)
|
||||
interface AnalyticsHandler : AnalyticsEventHandler {
|
||||
fun id(): String
|
||||
|
||||
fun send(event: String, params: Map<String, String> = emptyMap())
|
||||
|
||||
override fun send(event: AnalyticsEvent) {
|
||||
send(prepareEventString(event), event.params)
|
||||
}
|
||||
|
||||
@Deprecated("Migrate to AnalyticsEvent")
|
||||
fun handleAnalyticsEvent(
|
||||
event: AnalyticsEventAnOld,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
card: Card? = null,
|
||||
blockchain: String? = null,
|
||||
) {
|
||||
handleEvent(event.event, prepareParams(card, blockchain, params))
|
||||
}
|
||||
|
||||
fun prepareParams(
|
||||
card: Card? = null,
|
||||
blockchain: String? = null,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
): Map<String, String> = mapOf(
|
||||
AnalyticsParam.Firmware to card?.firmwareVersion?.stringValue,
|
||||
AnalyticsParam.BatchId to card?.batchId,
|
||||
AnalyticsParam.Blockchain to blockchain,
|
||||
).filterNotNull() + params
|
||||
|
||||
fun prepareEventString(category: String, event: String): String {
|
||||
return "[$category] $event"
|
||||
}
|
||||
fun prepareEventString(event: AnalyticsEvent): String = "[${event.category}] ${event.event}"
|
||||
}
|
||||
|
||||
interface ErrorEventHandler {
|
||||
fun handleErrorEvent(
|
||||
fun send(
|
||||
error: Throwable,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
)
|
||||
}
|
||||
|
||||
interface SdkErrorEventHandler : CardSdkErrorEventHandler, BlockchainSdkErrorEventHandler
|
||||
interface AnalyticsHandlerHolder {
|
||||
fun addHandler(name: String, handler: AnalyticsHandler)
|
||||
fun removeHandler(name: String): AnalyticsHandler?
|
||||
}
|
||||
|
||||
interface CardSdkErrorEventHandler {
|
||||
fun handleCardSdkErrorEvent(
|
||||
error: TangemSdkError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String> = emptyMap(),
|
||||
card: Card? = null,
|
||||
interface AnalyticsHandlerBuilder {
|
||||
fun build(data: Data): AnalyticsHandler?
|
||||
|
||||
data class Data(
|
||||
val application: Application,
|
||||
val config: Config,
|
||||
val isDebug: Boolean,
|
||||
val logConfig: AnalyticsHandlersLogConfig,
|
||||
val jsonConverter: MoshiJsonConverter,
|
||||
)
|
||||
}
|
||||
|
||||
interface BlockchainSdkErrorEventHandler {
|
||||
fun handleBlockchainSdkErrorEvent(
|
||||
error: BlockchainError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String> = mapOf(),
|
||||
card: Card? = null,
|
||||
)
|
||||
}
|
||||
|
||||
interface ShopifyOrderEventHandler {
|
||||
fun handleShopifyOrderEvent(order: Storefront.Order)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.tap.common.analytics.api
|
||||
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ParamsInterceptor {
|
||||
fun id(): String
|
||||
fun canBeAppliedTo(event: AnalyticsEvent): Boolean
|
||||
fun intercept(params: MutableMap<String, String>)
|
||||
}
|
||||
|
||||
interface ParamsInterceptorHolder {
|
||||
fun addParamsInterceptor(interceptor: ParamsInterceptor)
|
||||
fun removeParamsInterceptor(interceptor: ParamsInterceptor): ParamsInterceptor?
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.features.demo.DemoTransactionSender
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AnalyticsErrorConverter : Converter<Throwable, Map<String, String>> {
|
||||
fun canBeHandled(error: Throwable): Boolean {
|
||||
return when (error) {
|
||||
is TangemSdkError.UserCancelled -> false
|
||||
is BlockchainSdkError -> !error.customMessage.contains(DemoTransactionSender.ID)
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
override fun convert(value: Throwable): Map<String, String> {
|
||||
return when (value) {
|
||||
is BlockchainSdkError -> BlockchainSdkErrorConverter(CardSdkErrorConverter()).convert(value)
|
||||
is TangemSdkError -> CardSdkErrorConverter().convert(value)
|
||||
else -> ThrowableErrorConverter().convert(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ThrowableErrorConverter : Converter<Throwable, Map<String, String>> {
|
||||
override fun convert(value: Throwable): Map<String, String> {
|
||||
val unknown = "unknown"
|
||||
return mapOf(
|
||||
AnalyticsParam.ErrorKey to AnalyticsParam.Error.App.value,
|
||||
AnalyticsParam.ErrorDescription to (value.message ?: value.cause?.message ?: unknown),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class CardSdkErrorConverter : Converter<TangemSdkError, Map<String, String>> {
|
||||
|
||||
override fun convert(value: TangemSdkError): Map<String, String> {
|
||||
if (value is TangemSdkError.UserCancelled) return emptyMap()
|
||||
|
||||
return mapOf(
|
||||
AnalyticsParam.ErrorKey to AnalyticsParam.Error.CardSdk.value,
|
||||
AnalyticsParam.ErrorCode to value.code.toString(),
|
||||
AnalyticsParam.ErrorDescription to value.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class BlockchainSdkErrorConverter(
|
||||
private val cardSdkErrorConverter: CardSdkErrorConverter,
|
||||
) : Converter<BlockchainSdkError, Map<String, String>> {
|
||||
|
||||
override fun convert(value: BlockchainSdkError): Map<String, String> {
|
||||
if (value.customMessage.contains(DemoTransactionSender.ID)) return emptyMap()
|
||||
|
||||
if (value is BlockchainSdkError.WrappedTangemError) {
|
||||
return (value.tangemError as? TangemSdkError)?.let { cardSdkErrorConverter.convert(it) } ?: emptyMap()
|
||||
}
|
||||
|
||||
return mapOf(
|
||||
AnalyticsParam.ErrorKey to AnalyticsParam.Error.BlockchainSdk.value,
|
||||
AnalyticsParam.ErrorCode to value.code.toString(),
|
||||
AnalyticsParam.ErrorDescription to "${value.javaClass.simpleName}: ${value.customMessage}",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.analytics.filters.BasicTopUpFilter
|
||||
import com.tangem.tap.domain.extensions.getUserWalletId
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.reducers.calculateTotalCryptoAmount
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class BasicSignInEventConverter(
|
||||
private val scanResponse: ScanResponse,
|
||||
) : Converter<WalletState, Basic.SignedIn?> {
|
||||
|
||||
override fun convert(value: WalletState): Basic.SignedIn? {
|
||||
if (!statesIsReadyToCreateEvent(scanResponse, value)) return null
|
||||
val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null
|
||||
|
||||
return Basic.SignedIn(
|
||||
state = AnalyticsParam.CardBalanceState.from(value.walletsData),
|
||||
currency = cardCurrency,
|
||||
batch = scanResponse.card.batchId,
|
||||
).apply {
|
||||
filterData = scanResponse.card.getUserWalletId()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BasicTopUpEventConverter(
|
||||
private val scanResponse: ScanResponse,
|
||||
) : Converter<WalletState, Basic.ToppedUp?> {
|
||||
|
||||
override fun convert(value: WalletState): Basic.ToppedUp? {
|
||||
if (!statesIsReadyToCreateEvent(scanResponse, value)) return null
|
||||
val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null
|
||||
|
||||
val data = BasicTopUpFilter.Data(
|
||||
walletId = scanResponse.card.getUserWalletId(),
|
||||
cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsData),
|
||||
)
|
||||
|
||||
return Basic.ToppedUp(cardCurrency).apply { filterData = data }
|
||||
}
|
||||
}
|
||||
|
||||
private fun AnalyticsParam.CardBalanceState.Companion.from(walletsData: List<WalletData>): AnalyticsParam.CardBalanceState {
|
||||
val totalCryptoAmount = walletsData.calculateTotalCryptoAmount()
|
||||
return when {
|
||||
totalCryptoAmount.isZero() -> AnalyticsParam.CardBalanceState.Empty
|
||||
else -> AnalyticsParam.CardBalanceState.Full
|
||||
}
|
||||
}
|
||||
|
||||
private fun statesIsReadyToCreateEvent(scanResponse: ScanResponse, state: WalletState): Boolean {
|
||||
if (scanResponse.card.isMultiwalletAllowed && state.missingDerivations.isNotEmpty()) return false
|
||||
if (state.walletsData.isEmpty()) return false
|
||||
|
||||
val totalBalanceState = state.totalBalance?.state ?: return false
|
||||
if (totalBalanceState == ProgressState.Loading || totalBalanceState == ProgressState.Refreshing) return false
|
||||
|
||||
val balancesCount = state.walletsData.map {
|
||||
if (it.currencyData.amount == null) 0 else 1
|
||||
}.reduce { acc, i -> acc + i }
|
||||
|
||||
if (balancesCount != state.wallets.size) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.domain.common.SaltPayWorkaround
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ParamCardCurrencyConverter : Converter<ScanResponse, AnalyticsParam.CardCurrency?> {
|
||||
|
||||
override fun convert(value: ScanResponse): AnalyticsParam.CardCurrency? {
|
||||
if (value.card.isMultiwalletAllowed) return AnalyticsParam.CardCurrency.MultiCurrency
|
||||
|
||||
val type = when {
|
||||
value.isTangemNote() -> {
|
||||
value.card.getTangemNoteBlockchain()?.let { AnalyticsParam.CurrencyType.Blockchain(it) }
|
||||
}
|
||||
value.isTangemTwins() -> AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin)
|
||||
value.isSaltPay() -> AnalyticsParam.CurrencyType.Token(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay))
|
||||
value.getPrimaryToken() != null -> AnalyticsParam.CurrencyType.Token(value.getPrimaryToken()!!)
|
||||
else -> null
|
||||
} ?: return null
|
||||
|
||||
return AnalyticsParam.CardCurrency.SingleCurrency(type)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
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.AnalyticsAnOld
|
||||
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
|
||||
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[AnalyticsParamAnOld.ACTION.param] = value.action.key
|
||||
mutableParams[AnalyticsParamAnOld.ERROR_CODE.param] = value.error.code.toString()
|
||||
mutableParams[AnalyticsParamAnOld.ERROR_DESCRIPTION.param] = value.error.javaClass.simpleName
|
||||
mutableParams[AnalyticsParamAnOld.ERROR_KEY.param] = "TangemSdkError"
|
||||
|
||||
return ErrorEvent(TangemSdk.map(value.error), mutableParams)
|
||||
}
|
||||
|
||||
internal data class Model(
|
||||
val error: TangemSdkError,
|
||||
val action: AnalyticsAnOld.ActionToLog,
|
||||
val params: Map<AnalyticsParamAnOld, 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[AnalyticsParamAnOld.ACTION.param] = value.action.key
|
||||
mutableParams[AnalyticsParamAnOld.ERROR_CODE.param] = error.code.toString()
|
||||
mutableParams[AnalyticsParamAnOld.ERROR_DESCRIPTION.param] = "${error.javaClass.simpleName}: ${error.customMessage}"
|
||||
mutableParams[AnalyticsParamAnOld.ERROR_KEY.param] = "BlockchainSdkError"
|
||||
|
||||
return ErrorEvent(value.error, mutableParams)
|
||||
}
|
||||
|
||||
data class Model(
|
||||
val error: BlockchainError,
|
||||
val action: AnalyticsAnOld.ActionToLog,
|
||||
val params: Map<AnalyticsParamAnOld, String>,
|
||||
val cardParams: Map<String, String>,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun Map<AnalyticsParamAnOld, String>.convertToParamValue(): Map<String, String> {
|
||||
return mutableMapOf<String, String>().also { newMap ->
|
||||
this.forEach {
|
||||
newMap[it.key.param] = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.shopify.buy3.Storefront
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.tap.common.analytics.events.Shop
|
||||
import com.tangem.tap.common.shop.data.ProductType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ShopOrderToEventConverter : Converter<Pair<Storefront.Order, ProductType>, Shop.Purchased> {
|
||||
|
||||
override fun convert(value: Pair<Storefront.Order, ProductType>): Shop.Purchased {
|
||||
val order = value.first
|
||||
val productType = value.second
|
||||
|
||||
val sku = order.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: productType.sku
|
||||
val count = when (productType) {
|
||||
ProductType.WALLET_2_CARDS -> "2"
|
||||
ProductType.WALLET_3_CARDS -> "3"
|
||||
}
|
||||
val amount = "${order.totalPriceV2.amount} ${order.totalPriceV2.currencyCode.name}"
|
||||
val code = (order.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
|
||||
|
||||
return Shop.Purchased(sku, count, amount, code)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,5 +6,7 @@ package com.tangem.tap.common.analytics.events
|
|||
sealed class AnalyticsEvent(
|
||||
val category: String,
|
||||
val event: String,
|
||||
val params: Map<String, String> = mapOf(),
|
||||
var params: Map<String, String> = mapOf(),
|
||||
val error: Throwable? = null,
|
||||
var filterData: Any? = null,
|
||||
)
|
||||
|
|
@ -1,22 +1,27 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
|
||||
sealed class AnalyticsParam {
|
||||
|
||||
sealed class CurrencyType(val value: String) {
|
||||
class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol)
|
||||
class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency)
|
||||
class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol)
|
||||
class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency) : CurrencyType(fiatCurrency.symbol)
|
||||
class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol)
|
||||
}
|
||||
|
||||
// Multicurrency or CurrencyType
|
||||
// MultiCurrency or CurrencyType
|
||||
sealed class CardCurrency(val value: String) {
|
||||
object MultiCurrency : CardCurrency("Multicurrency")
|
||||
class SingleCurrency(type: CurrencyType) : CardCurrency(type.value)
|
||||
}
|
||||
|
||||
sealed class CardState(val value: String) {
|
||||
object Empty : CardState("Empty")
|
||||
object Full : CardState("Full")
|
||||
sealed class CardBalanceState(val value: String) {
|
||||
object Empty : CardBalanceState("Empty")
|
||||
object Full : CardBalanceState("Full")
|
||||
companion object
|
||||
}
|
||||
|
||||
sealed class RateApp(val value: String) {
|
||||
|
|
@ -39,20 +44,26 @@ sealed class AnalyticsParam {
|
|||
object AccessCode : SecurityMode("Access Code")
|
||||
object Passcode : SecurityMode("Passcode")
|
||||
object LongTap : SecurityMode("Long Tap")
|
||||
|
||||
companion object {
|
||||
fun from(option: SecurityOption): SecurityMode = when (option) {
|
||||
SecurityOption.AccessCode -> AccessCode
|
||||
SecurityOption.PassCode -> Passcode
|
||||
SecurityOption.LongTap -> LongTap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SocialNetwork(val value: String) {
|
||||
object Facebook : SocialNetwork("Facebook")
|
||||
object Instagram : SocialNetwork("Instagram")
|
||||
object Youtube : SocialNetwork("Youtube")
|
||||
object Twitter : SocialNetwork("Twitter")
|
||||
object LinkedIn : SocialNetwork("LinkedIn")
|
||||
object GitHub : SocialNetwork("GitHub")
|
||||
sealed class Error(val value: String) {
|
||||
object App : Error("App Error")
|
||||
object CardSdk : Error("Card Sdk Error")
|
||||
object BlockchainSdk : Error("Blockchain Sdk Error")
|
||||
}
|
||||
|
||||
companion object Key {
|
||||
const val Blockchain = "blockchain"
|
||||
const val BatchId = "batch_id"
|
||||
const val Firmware = "firmware"
|
||||
const val BatchId = "Batch"
|
||||
const val ErrorDescription = "Error Description"
|
||||
const val ErrorCode = "Error Code"
|
||||
const val ErrorKey = "Error Key"
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,11 @@ package com.tangem.tap.common.analytics.events
|
|||
sealed class Basic(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Basic", event, params) {
|
||||
error: Throwable? = null,
|
||||
) : AnalyticsEvent("Basic", event, params, error) {
|
||||
|
||||
sealed class SignedIn(
|
||||
state: AnalyticsParam.CardState,
|
||||
class SignedIn(
|
||||
state: AnalyticsParam.CardBalanceState,
|
||||
currency: AnalyticsParam.CardCurrency,
|
||||
batch: String,
|
||||
) : Basic(
|
||||
|
|
@ -17,7 +18,7 @@ sealed class Basic(
|
|||
params = mapOf(
|
||||
"State" to state.value,
|
||||
"Currency" to currency.value,
|
||||
"Batch" to batch,
|
||||
AnalyticsParam.BatchId to batch,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -25,4 +26,9 @@ sealed class Basic(
|
|||
event = "Topped up",
|
||||
params = mapOf("Currency" to currency.value),
|
||||
)
|
||||
|
||||
class ScanError(error: Throwable) : Basic(
|
||||
event = "Scan",
|
||||
error = error,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ sealed class MainScreen(
|
|||
params = mapOf("Currency Type" to currencyType.value),
|
||||
)
|
||||
|
||||
sealed class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen(
|
||||
class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen(
|
||||
event = "Notice - Rate The App Button Tapped",
|
||||
params = mapOf("Result" to result.value),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -10,7 +14,18 @@ sealed class ManageTokens(
|
|||
|
||||
class ScreenOpened : ManageTokens("Manage Tokens Screen Opened")
|
||||
class TokenSearched : ManageTokens("Token Searched")
|
||||
class TokenSwitcherChanged : ManageTokens("Token Switcher Changed")
|
||||
|
||||
class TokenSwitcherChanged(
|
||||
type: AnalyticsParam.CurrencyType,
|
||||
state: AnalyticsParam.OnOffState,
|
||||
) : ManageTokens(
|
||||
"Token Switcher Changed",
|
||||
params = mapOf(
|
||||
"Token" to type.value,
|
||||
"State" to state.value,
|
||||
),
|
||||
)
|
||||
|
||||
class ButtonSaveChanges : ManageTokens("Button - Save Changes")
|
||||
class ButtonCustomToken : ManageTokens("Button - Custom Token")
|
||||
|
||||
|
|
@ -21,13 +36,26 @@ sealed class ManageTokens(
|
|||
|
||||
class ScreenOpened : ManageTokens("Custom Token Screen Opened")
|
||||
|
||||
class TokenWasAdded(symbol: String, network: String, address: String) : ManageTokens(
|
||||
class TokenWasAdded(customCurrency: CustomCurrency) : ManageTokens(
|
||||
event = "Custom Token Was Added",
|
||||
params = mapOf(
|
||||
"Symbol" to symbol,
|
||||
"Network" to network,
|
||||
"Address" to address,
|
||||
),
|
||||
)
|
||||
params = convertToParam(customCurrency),
|
||||
) {
|
||||
companion object {
|
||||
private fun convertToParam(customCurrency: CustomCurrency): Map<String, String> = with(customCurrency) {
|
||||
return when (this) {
|
||||
is CustomCurrency.CustomBlockchain -> mapOf(
|
||||
"Token" to network.currency,
|
||||
"Derivation Path" to derivationPath?.rawPath,
|
||||
).filterNotNull()
|
||||
is CustomCurrency.CustomToken -> mapOf(
|
||||
"Token" to token.symbol,
|
||||
"Derivation Path" to derivationPath?.rawPath,
|
||||
"Network Id" to network.toNetworkId(),
|
||||
"Contract Address" to token.contractAddress,
|
||||
).filterNotNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,9 +34,9 @@ sealed class Onboarding(
|
|||
class AccessCodeEntered : Backup("Access Code Entered")
|
||||
class AccessCodeReEntered : Backup("Access Code Re-entered")
|
||||
|
||||
class Finished(cardsCount: String) : Backup(
|
||||
class Finished(cardsCount: Int) : Backup(
|
||||
event = "Backup Finished",
|
||||
params = mapOf("Cards count" to cardsCount),
|
||||
params = mapOf("Cards count" to "$cardsCount"),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -65,12 +65,12 @@ sealed class Onboarding(
|
|||
class SetupFinished : Twins("Twin Setup Finished")
|
||||
}
|
||||
|
||||
class PinCodeSet: Onboarding("Onboarding", "PIN code set")
|
||||
class ButtonConnect: Onboarding("Onboarding", "Button - Connect")
|
||||
class KYCStarted: Onboarding("Onboarding", "KYC started")
|
||||
class KYCInProgress: Onboarding("Onboarding", "KYC in progress")
|
||||
class KYCRejected: Onboarding("Onboarding", "KYC rejected")
|
||||
class ClaimScreenOpened: Onboarding("Onboarding", "Claim screen opened")
|
||||
class ButtonClaim: Onboarding("Onboarding", "Button - Claim")
|
||||
class ClaimWasSuccessfully: Onboarding("Onboarding", "Claim was successfully")
|
||||
class PinCodeSet : Onboarding("Onboarding", "PIN code set")
|
||||
class ButtonConnect : Onboarding("Onboarding", "Button - Connect")
|
||||
class KYCStarted : Onboarding("Onboarding", "KYC started")
|
||||
class KYCInProgress : Onboarding("Onboarding", "KYC in progress")
|
||||
class KYCRejected : Onboarding("Onboarding", "KYC rejected")
|
||||
class ClaimScreenOpened : Onboarding("Onboarding", "Claim screen opened")
|
||||
class ButtonClaim : Onboarding("Onboarding", "Button - Claim")
|
||||
class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim was successfully")
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.tap.features.details.ui.details.SocialNetwork
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -7,7 +9,8 @@ sealed class Settings(
|
|||
category: String = "Settings",
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(category, event, params) {
|
||||
error: Throwable? = null,
|
||||
) : AnalyticsEvent(category, event, params, error) {
|
||||
|
||||
class ScreenOpened : Settings(event = "Settings Screen Opened")
|
||||
class ButtonChat : Settings(event = "Button - Chat")
|
||||
|
|
@ -18,29 +21,35 @@ sealed class Settings(
|
|||
class ButtonAppSettings : Settings(event = "Button - App Settings")
|
||||
class ButtonCreateBackup : Settings(event = "Button - Create Backup")
|
||||
|
||||
sealed class ButtonSocialNetwork(network: AnalyticsParam.SocialNetwork) : Settings(
|
||||
class ButtonSocialNetwork(network: SocialNetwork) : Settings(
|
||||
event = "Button - Social Network",
|
||||
params = mapOf("Network" to network.value),
|
||||
params = mapOf("Network" to network.id),
|
||||
)
|
||||
|
||||
sealed class CardSettings(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Settings("Settings / Card Settings", event, params) {
|
||||
error: Throwable? = null,
|
||||
) : Settings("Settings / Card Settings", event, params, error) {
|
||||
|
||||
class ButtonFactoryReset : CardSettings("Button - Factory Reset")
|
||||
class FactoryResetFinished : CardSettings("Factory Reset Finished")
|
||||
class FactoryResetFinished(error: Throwable? = null) : CardSettings(
|
||||
event = "Factory Reset Finished",
|
||||
error = error,
|
||||
)
|
||||
|
||||
class UserCodeChanged : CardSettings("User Code Changed")
|
||||
class ButtonChangeSecurityMode : CardSettings("Button - Change Security Mode")
|
||||
|
||||
sealed class ButtonChangeUserCode(type: AnalyticsParam.UserCode) : CardSettings(
|
||||
class ButtonChangeUserCode(type: AnalyticsParam.UserCode) : CardSettings(
|
||||
event = "Button - Change User Code",
|
||||
params = mapOf("Type" to type.value),
|
||||
)
|
||||
|
||||
sealed class SecurityModeChanged(mode: AnalyticsParam.SecurityMode) : CardSettings(
|
||||
class SecurityModeChanged(mode: AnalyticsParam.SecurityMode, error: Throwable? = null) : CardSettings(
|
||||
event = "Security Mode Changed",
|
||||
params = mapOf("Mode" to mode.value),
|
||||
error = error,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -10,9 +12,14 @@ sealed class Shop(
|
|||
|
||||
class ScreenOpened : IntroductionProcess("Shop Screen Opened")
|
||||
|
||||
class Purchased(productSku: String) : Shop(
|
||||
class Purchased(sku: String, count: String, amount: String, couponCode: String?) : Shop(
|
||||
event = "Purchased",
|
||||
params = mapOf("SKU" to productSku),
|
||||
params = mapOf(
|
||||
"SKU" to sku,
|
||||
"Count" to count,
|
||||
"Amount" to amount,
|
||||
"Coupon Code" to couponCode,
|
||||
).filterNotNull(),
|
||||
)
|
||||
|
||||
class Redirected(partnerName: String) : Shop(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam.CurrencyType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -7,15 +9,41 @@ sealed class Token(
|
|||
category: String,
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(category, event, params) {
|
||||
error: Throwable? = null,
|
||||
) : AnalyticsEvent(category, event, params, error) {
|
||||
|
||||
class Refreshed : Token("Token", "Refreshed")
|
||||
class ButtonRemoveToken : Token("Token", "Button - Remove Token")
|
||||
class ButtonExplore : Token("Token", "Button - Explore")
|
||||
class ButtonBuy(token: String) : Token("Token", "Button - Buy", mapOf("Token" to token))
|
||||
class ButtonSell(token: String) : Token("Token", "Button - Sell", mapOf("Token" to token))
|
||||
class ButtonExchange(token: String) : Token("Token", "Button - Exchange", mapOf("Token" to token))
|
||||
class ButtonSend(token: String) : Token("Token", "Button - Send", mapOf("Token" to token))
|
||||
|
||||
class ButtonRemoveToken(type: CurrencyType) : Token(
|
||||
"Token",
|
||||
"Button - Remove Token",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class ButtonBuy(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Button - Buy",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class ButtonSell(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Button - Sell",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class ButtonExchange(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Button - Exchange",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
class ButtonSend(type: CurrencyType) : Token(
|
||||
category = "Token",
|
||||
event = "Button - Send",
|
||||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
sealed class Recieve(
|
||||
event: String,
|
||||
|
|
@ -30,16 +58,18 @@ sealed class Token(
|
|||
sealed class Send(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Token("Token / Send", event, params) {
|
||||
error: Throwable? = null,
|
||||
) : Token("Token / Send", event, params, error) {
|
||||
|
||||
class ScreenOpened : Send("Send Screen Opened")
|
||||
class ButtonPaste : Send("Button - Paste")
|
||||
class ButtonQRCode : Send("Button - QR Code")
|
||||
class ButtonSwapCurrency : Send("Button - Swap Currency")
|
||||
|
||||
class TransactionSent(token: AnalyticsParam.CurrencyType) : Send(
|
||||
class TransactionSent(type: CurrencyType, error: Throwable? = null) : Send(
|
||||
event = "Transaction Sent",
|
||||
params = mapOf("Token" to token.value),
|
||||
params = mapOf("Token" to type.value),
|
||||
error = error,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,13 @@ package com.tangem.tap.common.analytics.events
|
|||
sealed class WalletConnect(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Wallet Connect", event, params) {
|
||||
error: Throwable? = null,
|
||||
) : AnalyticsEvent("Wallet Connect", event, params, error) {
|
||||
|
||||
class NewSessionEstablished : WalletConnect("New Session Established")
|
||||
class SessionDisconnected : WalletConnect("Session Disconnected")
|
||||
class RequestSigned : WalletConnect("Request Signed")
|
||||
|
||||
class SignError(error: Throwable) : WalletConnect("Sign", error = error)
|
||||
class TransactionError(error: Throwable) : WalletConnect("Transaction", error = error)
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.common.analytics.filters
|
||||
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventFilter
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class BasicSignInFilter : AnalyticsEventFilter {
|
||||
|
||||
private val alreadySignedIn = mutableSetOf<String>()
|
||||
|
||||
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = event is Basic.SignedIn
|
||||
|
||||
override fun canBeSent(event: AnalyticsEvent): Boolean {
|
||||
val userWalletId = event.filterData as? String ?: return false
|
||||
|
||||
val canBeSent = !alreadySignedIn.contains(userWalletId)
|
||||
alreadySignedIn.add(userWalletId)
|
||||
|
||||
return canBeSent
|
||||
}
|
||||
|
||||
override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean = true
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.tap.common.analytics.filters
|
||||
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventFilter
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.persistence.ToppedUpWalletStorage
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class BasicTopUpFilter(
|
||||
private val topupWalletStorage: ToppedUpWalletStorage,
|
||||
) : AnalyticsEventFilter {
|
||||
|
||||
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = event is Basic.ToppedUp
|
||||
|
||||
override fun canBeSent(event: AnalyticsEvent): Boolean {
|
||||
val data = event.filterData as? Data ?: return false
|
||||
|
||||
val walletInfo = topupWalletStorage.restore(data.walletId).guard {
|
||||
val newWalletInfo = Data(
|
||||
walletId = data.walletId,
|
||||
cardBalanceState = data.cardBalanceState,
|
||||
)
|
||||
topupWalletStorage.save(newWalletInfo)
|
||||
return false
|
||||
}
|
||||
|
||||
if (walletInfo.isToppedUp) return false
|
||||
|
||||
return if (!walletInfo.isToppedUp && data.isToppedUp) {
|
||||
topupWalletStorage.save(walletInfo.copy(cardBalanceState = AnalyticsParam.CardBalanceState.Full))
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean = true
|
||||
|
||||
data class Data(
|
||||
val walletId: String,
|
||||
val cardBalanceState: AnalyticsParam.CardBalanceState,
|
||||
) {
|
||||
val isToppedUp: Boolean = cardBalanceState == AnalyticsParam.CardBalanceState.Full
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.tap.common.analytics.filters
|
||||
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventFilter
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.events.Shop
|
||||
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ShopPurchasedEventFilter : AnalyticsEventFilter {
|
||||
|
||||
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = event is Shop.Purchased
|
||||
|
||||
override fun canBeSent(event: AnalyticsEvent): Boolean = true
|
||||
|
||||
override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean {
|
||||
return when (handler) {
|
||||
is AppsFlyerAnalyticsHandler, is FirebaseAnalyticsHandler -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,27 @@
|
|||
package com.tangem.tap.common.analytics.handlers.amplitude
|
||||
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
|
||||
class AmplitudeAnalyticsHandler(
|
||||
private val client: AmplitudeAnalyticsClient,
|
||||
) : AnalyticsEventHandler {
|
||||
) : AnalyticsHandler {
|
||||
|
||||
override fun handleEvent(event: String, params: Map<String, String>) {
|
||||
override fun id(): String = ID
|
||||
|
||||
override fun send(event: String, params: Map<String, String>) {
|
||||
client.logEvent(event, params)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ID = "Amplitude"
|
||||
}
|
||||
|
||||
class Builder : AnalyticsHandlerBuilder {
|
||||
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when {
|
||||
!data.isDebug -> AmplitudeClient(data.application, data.config.amplitudeApiKey)
|
||||
data.isDebug && data.logConfig.amplitude -> AmplitudeLogClient(data.jsonConverter)
|
||||
else -> null
|
||||
}?.let { AmplitudeAnalyticsHandler(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
package com.tangem.tap.common.analytics.handlers.amplitude
|
||||
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.tap.common.analytics.AnalyticsEventsLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AmplitudeLogClient(
|
||||
private val logger: AnalyticsEventsLogger,
|
||||
jsonConverter: MoshiJsonConverter,
|
||||
) : AmplitudeAnalyticsClient {
|
||||
|
||||
private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(AmplitudeAnalyticsHandler.ID, jsonConverter)
|
||||
|
||||
override fun logEvent(event: String, params: Map<String, String>) {
|
||||
logger.logEvent(event, params)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,27 @@
|
|||
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.tap.common.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
|
||||
class AppsFlyerAnalyticsHandler(
|
||||
private val client: AppsFlyerAnalyticsClient,
|
||||
) : AnalyticsEventHandler, ShopifyOrderEventHandler {
|
||||
) : AnalyticsHandler {
|
||||
|
||||
override fun handleEvent(event: String, params: Map<String, String>) {
|
||||
override fun id(): String = ID
|
||||
|
||||
override fun send(event: String, params: Map<String, String>) {
|
||||
client.logEvent(event, params)
|
||||
}
|
||||
|
||||
override fun handleShopifyOrderEvent(order: Storefront.Order) {
|
||||
handleEvent(ORDER_EVENT, OrderToParamsConverter().convert(order))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ORDER_EVENT = AFInAppEventType.PURCHASE
|
||||
const val ID = "AppsFlyer"
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
class Builder : AnalyticsHandlerBuilder {
|
||||
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when {
|
||||
!data.isDebug -> AppsFlyerClient(data.application, data.config.appsFlyerDevKey)
|
||||
data.isDebug && data.logConfig.appsFlyer -> AppsFlyerLogClient(data.jsonConverter)
|
||||
else -> null
|
||||
}?.let { AppsFlyerAnalyticsHandler(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
package com.tangem.tap.common.analytics.handlers.appsFlyer
|
||||
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.tap.common.analytics.AnalyticsEventsLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AppsFlyerLogClient(
|
||||
private val logger: AnalyticsEventsLogger,
|
||||
jsonConverter: MoshiJsonConverter,
|
||||
) : AppsFlyerAnalyticsClient {
|
||||
|
||||
private val logger = AnalyticsEventsLogger(AppsFlyerAnalyticsHandler.ID, jsonConverter)
|
||||
|
||||
override fun logEvent(event: String, params: Map<String, String>) {
|
||||
logger.logEvent(event, params)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,81 +1,50 @@
|
|||
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.AnalyticsAnOld
|
||||
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
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
|
||||
import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsEvent
|
||||
|
||||
class FirebaseAnalyticsHandler(
|
||||
private val client: FirebaseAnalyticsClient,
|
||||
) : AnalyticsEventHandler, ErrorEventHandler, SdkErrorEventHandler, ShopifyOrderEventHandler {
|
||||
) : AnalyticsHandler, ErrorEventHandler {
|
||||
|
||||
override fun handleEvent(event: String, params: Map<String, String>) {
|
||||
override fun id(): String = ID
|
||||
|
||||
override fun send(event: String, params: Map<String, String>) {
|
||||
client.logEvent(event, params)
|
||||
}
|
||||
|
||||
override fun handleErrorEvent(error: Throwable, params: Map<String, String>) {
|
||||
override fun send(event: AnalyticsEvent) {
|
||||
when (event.error) {
|
||||
null -> super.send(event)
|
||||
else -> {
|
||||
val errorConverter = AnalyticsErrorConverter()
|
||||
if (!errorConverter.canBeHandled(event.error)) return
|
||||
|
||||
val errorParams = errorConverter.convert(event.error).toMutableMap()
|
||||
errorParams["Category"] = event.category
|
||||
errorParams["Event"] = event.event
|
||||
errorParams.putAll(event.params)
|
||||
send(event.error, errorParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun send(error: Throwable, params: Map<String, String>) {
|
||||
client.logErrorEvent(error, params)
|
||||
}
|
||||
|
||||
override fun handleCardSdkErrorEvent(
|
||||
error: TangemSdkError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String>,
|
||||
card: Card?,
|
||||
) {
|
||||
val model = CardSdkErrorConverter.Model(error, action, params, prepareParams(card))
|
||||
val converter = CardSdkErrorConverter()
|
||||
converter.convert(model)?.let {
|
||||
handleErrorEvent(it.throwable, it.params)
|
||||
}
|
||||
companion object {
|
||||
const val ID = "Firebase"
|
||||
}
|
||||
|
||||
override fun handleBlockchainSdkErrorEvent(
|
||||
error: BlockchainError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, 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
|
||||
class Builder : AnalyticsHandlerBuilder {
|
||||
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when {
|
||||
!data.isDebug -> FirebaseClient()
|
||||
data.isDebug && data.logConfig.firebase -> FirebaseLogClient(data.jsonConverter)
|
||||
else -> null
|
||||
}?.let { FirebaseAnalyticsHandler(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
package com.tangem.tap.common.analytics.handlers.firebase
|
||||
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.tap.common.analytics.AnalyticsEventsLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class FirebaseLogClient(
|
||||
private val logger: AnalyticsEventsLogger,
|
||||
jsonConverter: MoshiJsonConverter,
|
||||
) : FirebaseAnalyticsClient {
|
||||
|
||||
private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(FirebaseAnalyticsHandler.ID, jsonConverter)
|
||||
|
||||
override fun logEvent(event: String, params: Map<String, String>) {
|
||||
logger.logEvent(event, params)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.tap.common.analytics.paramsInterceptor
|
||||
|
||||
import com.tangem.tap.common.analytics.api.ParamsInterceptor
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class BatchIdParamsInterceptor(
|
||||
val batchId: String,
|
||||
) : ParamsInterceptor {
|
||||
|
||||
override fun id(): String = this::class.java.simpleName
|
||||
|
||||
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true
|
||||
|
||||
override fun intercept(params: MutableMap<String, String>) {
|
||||
params[AnalyticsParam.BatchId] = batchId
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import android.view.inputmethod.InputMethodManager
|
|||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
|
|
@ -57,7 +58,7 @@ fun View.getString(@StringRes id: Int, vararg formatArgs: String): String {
|
|||
return context.getString(id, *formatArgs)
|
||||
}
|
||||
|
||||
fun View.getQuantityString(@StringRes id: Int, quantity: Int): String {
|
||||
fun View.getQuantityString(@PluralsRes id: Int, quantity: Int): String {
|
||||
return context.resources.getQuantityString(id, quantity, quantity)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,11 +55,10 @@ suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
|
|||
}
|
||||
}
|
||||
|
||||
fun WalletManager?.getToUpUrl(): String? {
|
||||
fun WalletManager.getTopUpUrl(): String? {
|
||||
val globalState = store.state.globalState
|
||||
val wallet = this?.wallet ?: return null
|
||||
|
||||
val defaultAddress = wallet.address
|
||||
|
||||
return globalState.exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Buy,
|
||||
blockchain = wallet.blockchain,
|
||||
|
|
|
|||
16
app/src/main/java/com/tangem/tap/common/moshi/Adapters.kt
Normal file
16
app/src/main/java/com/tangem/tap/common/moshi/Adapters.kt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.tap.common.moshi
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.ToJson
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class BigDecimalAdapter {
|
||||
@FromJson
|
||||
fun fromJson(value: String) = BigDecimal(value)
|
||||
|
||||
@ToJson
|
||||
fun toJson(value: BigDecimal) = value.toString()
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ 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.GlobalAnalyticsEventHandler
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.feedback.FeedbackData
|
||||
import com.tangem.tap.common.feedback.FeedbackManager
|
||||
|
|
@ -89,7 +88,6 @@ 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 SetGlobalAnalyticsHandler(val analyticsHandler: GlobalAnalyticsEventHandler) : GlobalAction()
|
||||
|
||||
data class SendEmail(val feedbackData: FeedbackData) : GlobalAction()
|
||||
data class OpenChat(val feedbackData: FeedbackData, val zendeskConfig: ZendeskConfig? = null) : GlobalAction()
|
||||
|
|
|
|||
|
|
@ -167,7 +167,6 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
scope.launch {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(null)
|
||||
val result = tangemSdkManager.scanProduct(
|
||||
store.state.globalState.analyticsHandler,
|
||||
userTokensRepository,
|
||||
action.additionalBlockchainsToDerive,
|
||||
action.messageResId,
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
globalState.copy(configManager = action.configManager)
|
||||
}
|
||||
is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.redux.global
|
||||
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
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,11 +26,9 @@ data class GlobalState(
|
|||
val dialog: StateDialog? = null,
|
||||
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
|
||||
val resources: AndroidResources = AndroidResources(),
|
||||
val analyticsHandler: GlobalAnalyticsEventHandler = GlobalAnalyticsEventHandler.stub(),
|
||||
val userCountryCode: String? = null,
|
||||
) : StateType
|
||||
|
||||
|
||||
data class AndroidResources(
|
||||
val strings: RString = RString(),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -4,8 +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.common.extensions.ifNotNull
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.converters.ShopOrderToEventConverter
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
import com.tangem.tap.common.shop.data.ProductType
|
||||
import com.tangem.tap.common.shop.data.TangemProduct
|
||||
|
|
@ -23,7 +23,6 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
|
||||
private val shopifyService = ShopifyService(application, shopifyShop)
|
||||
|
||||
|
||||
private val checkouts = mutableMapOf<ProductType, Storefront.Checkout>()
|
||||
private val variants = mutableMapOf<ProductType, Storefront.ProductVariant>()
|
||||
|
||||
|
|
@ -41,24 +40,27 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
variants.putAll(availableVariants)
|
||||
|
||||
if (variants.size < SKUS_TO_DISPLAY.size) {
|
||||
return Result.failure(Exception(
|
||||
"Shopify: products are missing, " +
|
||||
"\nproducts available: ${variants.keys.map { it.sku }}"))
|
||||
return Result.failure(
|
||||
Exception(
|
||||
"Shopify: products are missing, " +
|
||||
"\nproducts available: ${variants.keys.map { it.sku }}",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val twoCardsProduct = TangemProduct(
|
||||
type = ProductType.WALLET_2_CARDS,
|
||||
totalSum = TotalSum(
|
||||
finalValue = variants[ProductType.WALLET_2_CARDS]?.priceV2?.format(),
|
||||
beforeDiscount = variants[ProductType.WALLET_2_CARDS]?.compareAtPriceV2?.format()
|
||||
)
|
||||
beforeDiscount = variants[ProductType.WALLET_2_CARDS]?.compareAtPriceV2?.format(),
|
||||
),
|
||||
)
|
||||
val threeCardsProduct = TangemProduct(
|
||||
type = ProductType.WALLET_3_CARDS,
|
||||
totalSum = TotalSum(
|
||||
finalValue = variants[ProductType.WALLET_3_CARDS]?.priceV2?.format(),
|
||||
beforeDiscount = variants[ProductType.WALLET_3_CARDS]?.compareAtPriceV2?.format()
|
||||
)
|
||||
beforeDiscount = variants[ProductType.WALLET_3_CARDS]?.compareAtPriceV2?.format(),
|
||||
),
|
||||
)
|
||||
createCheckouts()
|
||||
return Result.success(listOf(twoCardsProduct, threeCardsProduct))
|
||||
|
|
@ -86,7 +88,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
val totalPrice = checkouts[productType]!!.totalPriceV2.amount
|
||||
googlePayService.payWithGooglePay(
|
||||
totalPriceCents = totalPrice, currencyCode = checkouts[productType]!!.currencyCode.name,
|
||||
merchantID = shopifyService.shop.merchantID
|
||||
merchantID = shopifyService.shop.merchantID,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +107,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
suspend fun handleGooglePayResult(
|
||||
resultCode: Int,
|
||||
data: Intent?,
|
||||
productType: ProductType
|
||||
productType: ProductType,
|
||||
): Result<Unit> {
|
||||
val result = googlePayService.handleResponseFromGooglePay(resultCode, data)
|
||||
result.onSuccess {
|
||||
|
|
@ -120,7 +122,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
|
||||
private suspend fun completeTokenizedPayment(
|
||||
paymentData: PaymentData,
|
||||
productType: ProductType
|
||||
productType: ProductType,
|
||||
): Result<Storefront.Checkout> {
|
||||
val checkout = checkouts[productType]!!
|
||||
val googlePayResponse =
|
||||
|
|
@ -141,19 +143,18 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
phone = addressGPay.phoneNumber
|
||||
}
|
||||
|
||||
|
||||
val payment = Storefront.TokenizedPaymentInputV3(
|
||||
amount,
|
||||
idempotencyKey,
|
||||
address,
|
||||
paymentData.toJson(),
|
||||
Storefront.PaymentTokenType.GOOGLE_PAY
|
||||
Storefront.PaymentTokenType.GOOGLE_PAY,
|
||||
)
|
||||
.setTest(true)
|
||||
|
||||
return shopifyService.completeWithTokenizedPayment(
|
||||
payment = payment,
|
||||
checkoutID = checkout.id
|
||||
checkoutID = checkout.id,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -182,12 +183,12 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
productType,
|
||||
TotalSum(
|
||||
finalValue = it.totalPriceV2.format(),
|
||||
beforeDiscount = variants[productType]!!.compareAtPriceV2.format()
|
||||
beforeDiscount = variants[productType]!!.compareAtPriceV2.format(),
|
||||
),
|
||||
appliedDiscount = it.getAppliedDiscount()
|
||||
)
|
||||
appliedDiscount = it.getAppliedDiscount(),
|
||||
),
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
return Result.failure(result.exceptionOrNull()!!)
|
||||
}
|
||||
|
|
@ -196,10 +197,13 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
return checkouts[productType]!!.webUrl
|
||||
}
|
||||
|
||||
suspend fun waitForCheckout(productType: ProductType, analyticsHandler: GlobalAnalyticsEventHandler?) {
|
||||
suspend fun waitForCheckout(productType: ProductType) {
|
||||
val result = shopifyService.checkout(true, checkouts[productType]!!.id)
|
||||
result.onSuccess {
|
||||
ifNotNull(analyticsHandler, it.order) { handler, order -> handler.handleShopifyOrderEvent(order) }
|
||||
result.onSuccess { checkout ->
|
||||
checkout.order?.let {
|
||||
val event = ShopOrderToEventConverter().convert(it to productType)
|
||||
Analytics.send(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue