Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-04 15:18:02 +04:00
parent 85545863e3
commit 926e89e975
16 changed files with 158 additions and 223 deletions

View file

@ -1,15 +0,0 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.models.AnalyticsEvent
/**
[REDACTED_AUTHOR]
*/
sealed class SignIn(
event: String,
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent("Sign In", event, params) {
class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
class ButtonCardSignIn : SignIn(event = "Button - Card Sign In")
}

View file

@ -8,6 +8,7 @@ import arrow.core.right
import com.tangem.common.*
import com.tangem.common.core.TangemSdkError
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.datasource.local.preferences.AppPreferencesStore
@ -19,10 +20,7 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository.LockMethod
import com.tangem.domain.common.wallets.error.*
import com.tangem.domain.hotwallet.repository.HotWalletRepository
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isImported
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.models.wallet.*
import com.tangem.domain.wallets.R
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
@ -261,7 +259,7 @@ internal class DefaultUserWalletsListRepository(
when (unlockMethod) {
UserWalletsListRepository.UnlockMethod.Biometric -> {
unlockAllWallets().bind()
trackSignInEvent(userWallet, Basic.SignedIn.SignInType.Biometric)
trackSignInEvent(userWallet, AnalyticsParam.SignInType.Biometric)
select(userWalletId)
}
UserWalletsListRepository.UnlockMethod.AccessCode -> {
@ -292,7 +290,7 @@ internal class DefaultUserWalletsListRepository(
sensitiveInformationRepository.getAll(listOf(encryptionKey))
.doOnSuccess { sensitiveInfo ->
updateWallets { it?.updateWith(sensitiveInfo) }
trackSignInEvent(userWallet, Basic.SignedIn.SignInType.AccessCode)
trackSignInEvent(userWallet, AnalyticsParam.SignInType.AccessCode)
}
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
}
@ -330,7 +328,7 @@ internal class DefaultUserWalletsListRepository(
walletIdToDerivedKeys = mapOf(userWallet.walletId to scanResponse.derivedKeys),
)
}
trackSignInEvent(userWallet, Basic.SignedIn.SignInType.Card)
trackSignInEvent(userWallet, AnalyticsParam.SignInType.Card)
}
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
}
@ -373,7 +371,7 @@ internal class DefaultUserWalletsListRepository(
.doOnSuccess { sensitiveInfo ->
updateWallets { wallets -> wallets?.updateWith(sensitiveInfo) }
selectedUserWallet.value?.let {
trackSignInEvent(it, Basic.SignedIn.SignInType.Biometric)
trackSignInEvent(it, AnalyticsParam.SignInType.Biometric)
}
}
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
@ -602,18 +600,14 @@ internal class DefaultUserWalletsListRepository(
return lastOrNull()
}
private fun trackSignInEvent(userWallet: UserWallet, type: Basic.SignedIn.SignInType) {
private fun trackSignInEvent(userWallet: UserWallet, type: AnalyticsParam.SignInType) {
trackingContextProxy.addContext(userWallet)
val isBackedUp = when (userWallet) {
is UserWallet.Cold -> userWallet.scanResponse.card.backupStatus?.isActive == true
is UserWallet.Hot -> userWallet.backedUp
}
analyticsEventHandler.send(
event = Basic.SignedIn(
signInType = type,
walletsCount = userWallets.value?.size ?: 0,
isImported = userWallet.isImported(),
hasBackup = isBackedUp,
isBackedUp = userWallet.isBackedUpForAnalytics(),
),
)
}

View file

@ -14,6 +14,7 @@ import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.common.ui.notifications.NotificationId
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.core.analytics.utils.TrackingContextProxy
@ -30,7 +31,7 @@ import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isBackedUpForAnalytics
import com.tangem.domain.models.wallet.isImported
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.notifications.repository.NotificationsRepository
@ -360,16 +361,12 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
val userWallets = userWalletsListRepository.userWalletsSync()
val selectedWallet = userWalletsListRepository.selectedUserWalletSync() ?: return
trackingContextProxy.addContext(selectedWallet)
val isBackedUp = when (selectedWallet) {
is UserWallet.Cold -> selectedWallet.scanResponse.card.backupStatus?.isActive == true
is UserWallet.Hot -> selectedWallet.backedUp
}
analyticsEventHandler.send(
event = Basic.SignedIn(
signInType = Basic.SignedIn.SignInType.NoSecurity,
signInType = AnalyticsParam.SignInType.NoSecurity,
walletsCount = userWallets.size,
isImported = selectedWallet.isImported(),
hasBackup = isBackedUp,
isBackedUp = selectedWallet.isBackedUpForAnalytics(),
),
)
}

View file

@ -239,6 +239,13 @@ sealed class AnalyticsParam {
MobileWallet("Mobile Wallet"),
}
enum class SignInType(val value: String) {
Card("Card"),
Biometric("Biometric"),
NoSecurity("No Security"),
AccessCode("Access Code"),
}
companion object Key {
const val BLOCKCHAIN = "Blockchain"
const val TOKEN_PARAM = "Token"
@ -297,6 +304,11 @@ sealed class AnalyticsParam {
const val REFERRAL_ID = "Referral_ID"
const val SEARCHED = "Searched"
const val RATE_TYPE = "Rate Type"
const val SIGN_IN_TYPE = "Sign in type"
const val WALLETS_COUNT = "Wallets Count"
const val WALLET_TYPE = "Wallet Type"
const val BACKUPED = "Backuped"
const val MEMO = "Memo"
}
}

View file

@ -2,65 +2,41 @@ package com.tangem.core.analytics.models
sealed class Basic(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Basic", event, params) {
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category = "Basic", event = event, params = params) {
/**
* Tracks card scanning from specific entry points (Introduction, Main, My Wallets, Sign In).
* The originating screen is reported via the [AnalyticsParam.SOURCE] parameter.
*/
class CardWasScanned(
source: AnalyticsParam.ScreensSources,
) : Basic(
event = "Card Was Scanned",
params = mapOf(
AnalyticsParam.Key.SOURCE to source.value,
AnalyticsParam.SOURCE to source.value,
),
)
class SignedInLegacy(
currency: AnalyticsParam.WalletType,
batch: String,
signInType: SignInType,
walletsCount: String,
isImported: Boolean,
hasBackup: Boolean?,
) : Basic(
event = "Signed in",
params = buildMap {
put(AnalyticsParam.Key.CURRENCY, currency.value)
put(AnalyticsParam.Key.BATCH, batch)
put("Wallet Type", if (isImported) "Seed Phrase" else "Seedless")
put("Sign in type", signInType.name)
put("Wallets Count", walletsCount)
if (hasBackup != null) {
put("Backuped", if (hasBackup) "Yes" else "No")
}
},
) {
enum class SignInType {
Card, Biometric
}
}
), CriticalEvent
/**
* Tracks any sign-in into a wallet (card scan, FaceID, or wallet switch).
* Counted as a single sign-in per session subsequent card scans within the same session are ignored.
*/
class SignedIn(
signInType: SignInType,
signInType: AnalyticsParam.SignInType,
walletsCount: Int,
isImported: Boolean,
hasBackup: Boolean?,
isBackedUp: Boolean,
) : Basic(
event = "Signed in",
params = buildMap {
put("Sign in type", signInType.value)
put("Wallets Count", walletsCount.toString())
put("Wallet Type", if (isImported) "Seed Phrase" else "Seedless")
if (hasBackup != null) {
put("Backuped", if (hasBackup) "Yes" else "No")
}
put(AnalyticsParam.SIGN_IN_TYPE, signInType.value)
put(AnalyticsParam.WALLETS_COUNT, walletsCount.toString())
put(AnalyticsParam.WALLET_TYPE, if (isImported) "Seed Phrase" else "Seedless")
put(AnalyticsParam.BACKUPED, if (isBackedUp) "Yes" else "No")
},
) {
enum class SignInType(val value: String) {
Card("Card"),
Biometric("Biometric"),
NoSecurity("No Security"),
AccessCode("Access Code"),
}
), CriticalEvent, OneTimePerSessionEvent {
override val oneTimeEventId: String = id
}
class ButtonBuy(
@ -68,39 +44,46 @@ sealed class Basic(
) : Basic(
event = "Button - Buy",
params = buildMap {
put(AnalyticsParam.Key.SOURCE, source.value)
put(AnalyticsParam.SOURCE, source.value)
},
)
class ToppedUp(userWalletId: String, currency: AnalyticsParam.WalletType) :
/**
* Tracks the first time a user wallet is topped up. Sent once per wallet, when the balance
* transitions from zero to positive (Total Balance for multi-currency wallets, or Balance for Note).
* A wallet scanned with a non-zero balance does not count as a top-up the event must be sent
* only after all tokens have finished loading.
*/
class ToppedUp(userWalletId: String, walletType: AnalyticsParam.WalletType) :
Basic(
event = "Topped up",
params = mapOf(AnalyticsParam.Key.CURRENCY to currency.value),
params = mapOf(AnalyticsParam.CURRENCY to walletType.value),
),
OneTimeAnalyticsEvent {
OneTimeAnalyticsEvent, AppsFlyerIncludedEvent, CriticalEvent {
override val oneTimeEventId: String = id + userWalletId
}
/**
* Tracks transaction submission from various screens (Send, Swap, WalletConnect, Sell, Approve, Staking).
*/
class TransactionSent(sentFrom: AnalyticsParam.TxSentFrom, memoType: MemoType) :
Basic(
event = "Transaction sent",
params = buildMap {
this[AnalyticsParam.Key.SOURCE] = sentFrom.value
put(AnalyticsParam.SOURCE, sentFrom.value)
if (sentFrom is AnalyticsParam.TxData) {
this[AnalyticsParam.Key.BLOCKCHAIN] = sentFrom.blockchain
this[AnalyticsParam.Key.TOKEN_PARAM] = sentFrom.token
sentFrom.feeType?.value?.let {
this[AnalyticsParam.Key.FEE_TYPE] = it
}
this[AnalyticsParam.Key.FEE_TOKEN] = sentFrom.feeToken
put(AnalyticsParam.BLOCKCHAIN, sentFrom.blockchain)
put(AnalyticsParam.TOKEN_PARAM, sentFrom.token)
sentFrom.feeType?.value?.let { put(AnalyticsParam.FEE_TYPE, it) }
put(AnalyticsParam.FEE_TOKEN, sentFrom.feeToken)
}
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
this[AnalyticsParam.Key.PERMISSION_TYPE] = sentFrom.permissionType
put(AnalyticsParam.PERMISSION_TYPE, sentFrom.permissionType)
}
this["Memo"] = memoType.name
put(AnalyticsParam.MEMO, memoType.name)
},
), AppsFlyerIncludedEvent {
), AppsFlyerIncludedEvent, CriticalEvent {
enum class MemoType {
Empty, Full, Null
}
@ -110,31 +93,33 @@ sealed class Basic(
}
}
/**
* Tracks the user invoking the "Request Support" email flow from various screens of the app.
*/
class ButtonSupport(source: AnalyticsParam.ScreensSources) : Basic(
event = "Request Support",
params = mapOf(
AnalyticsParam.Key.SOURCE to source.value,
AnalyticsParam.SOURCE to source.value,
),
), CriticalEvent
/**
* Tracks loading of the user's total balance after sign-in. Reports whether the balance is
* empty, has funds, failed to load, or could not be returned because of a custom token.
*/
class BalanceLoaded(balance: AnalyticsParam.CardBalanceState, tokensCount: Int?) : Basic(
event = "Balance Loaded",
params = buildMap {
put(AnalyticsParam.BALANCE, balance.value)
tokensCount?.let { put(AnalyticsParam.TOKENS_COUNT, it.toString()) }
},
), AppsFlyerIncludedEvent, CriticalEvent
class TokenBalance(balance: AnalyticsParam.EmptyFull, token: String) : Basic(
event = "Token Balance",
params = mapOf(
AnalyticsParam.STATE to balance.value,
AnalyticsParam.TOKEN_PARAM to token,
),
)
class BiometryFailed(
source: AnalyticsParam.ScreensSources,
reason: BiometricFailReason,
) : Basic(
event = "Biometry Failed",
params = mapOf(
AnalyticsParam.Key.SOURCE to source.value,
"Reason" to reason.value,
),
) {
sealed class BiometricFailReason(val value: String) {
data object AuthenticationLockout : BiometricFailReason("BiometricsAuthenticationLockout")
data object AuthenticationLockoutPermanent : BiometricFailReason("BiometricsAuthenticationLockoutPermanent")
data object BiometricsAuthenticationDisabled : BiometricFailReason("BiometricsAuthenticationDisabled")
data object AllKeysInvalidated : BiometricFailReason("AllKeysInvalidated")
data object AuthenticationCancelled : BiometricFailReason("AuthenticationCancelled")
data object AuthenticationAlreadyInProgress : BiometricFailReason("AuthenticationAlreadyInProgress")
data class Other(val reason: String) : BiometricFailReason(reason)
}
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.core.analytics.models
/**
* Marker interface for analytics events that require special attention.
*
* Implemented by events listed in the analytics specification (events.csv).
*/
interface CriticalEvent

View file

@ -2,20 +2,22 @@ package com.tangem.core.analytics.models.event
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.CriticalEvent
sealed class SignIn(
event: String,
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent("Sign In", event, params) {
) : AnalyticsEvent(category = "Sign In", event = event, params = params) {
data class ScreenOpened(
val walletsCount: Int,
) : SignIn(
/**
* Tracks the user landing on the app's sign-in screen when a saved card exists.
*/
class ScreenOpened(walletsCount: Int) : SignIn(
event = "Sign In Screen Opened",
params = mapOf(
"Wallets Count" to walletsCount.toString(),
AnalyticsParam.WALLETS_COUNT to walletsCount.toString(),
),
)
), CriticalEvent
class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
@ -26,24 +28,17 @@ sealed class SignIn(
) : SignIn(event = "Error - Biometric Updated")
class ButtonWallet(
signInType: SignInType,
signInType: AnalyticsParam.SignInType,
walletsCount: Int,
) : SignIn(
event = "Button - Wallet",
params = buildMap {
put("Wallets Count", walletsCount.toString())
put("Sign in type", signInType.value)
put(AnalyticsParam.WALLETS_COUNT, walletsCount.toString())
put(AnalyticsParam.SIGN_IN_TYPE, signInType.value)
},
) {
enum class SignInType(val value: String) {
Card("Card"),
Biometric("Biometric"),
NoSecurity("No Security"),
AccessCode("Access Code"),
}
}
)
data class ButtonAddWallet(
class ButtonAddWallet(
val sources: AnalyticsParam.ScreensSources,
) : SignIn(
event = "Button - Add Wallet",

View file

@ -2,6 +2,7 @@ package com.tangem.domain.card.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.CriticalEvent
import com.tangem.core.analytics.models.getReferralParams
sealed class IntroductionProcess(
@ -9,11 +10,17 @@ sealed class IntroductionProcess(
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent("Introduction Process", event, params) {
class ScreenOpened : IntroductionProcess("Introduction Process Screen Opened")
/**
* Tracks the user opening the Introduction Process screen.
*/
class ScreenOpened : IntroductionProcess("Introduction Process Screen Opened"), CriticalEvent
class ButtonTokensList : IntroductionProcess("Button - Tokens List")
class ButtonBuyCards : IntroductionProcess("Button - Buy Cards")
class ButtonScanCardLegacy : IntroductionProcess("Button - Scan Card")
/**
* Tracks opening the Create Wallet introduction screen.
*/
class CreateWalletIntroScreenOpened(
referralId: String?,
) : IntroductionProcess(
@ -21,7 +28,7 @@ sealed class IntroductionProcess(
params = buildMap {
putAll(getReferralParams(referralId))
},
)
), CriticalEvent
class ButtonScanCard(
val source: AnalyticsParam.ScreensSources,

View file

@ -93,6 +93,13 @@ fun UserWallet.isImported(): Boolean {
}
}
fun UserWallet.isBackedUpForAnalytics(): Boolean {
return when (this) {
is UserWallet.Cold -> scanResponse.card.backupStatus?.isActive == true
is UserWallet.Hot -> backedUp
}
}
fun UserWallet.copy(name: String = this.name, walletId: UserWalletId = this.walletId): UserWallet = when (this) {
is UserWallet.Cold -> this.copy(name = name, walletId = walletId)
is UserWallet.Hot -> this.copy(name = name, walletId = walletId)

View file

@ -8,8 +8,8 @@ import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic.SignedInLegacy
import com.tangem.core.analytics.models.Basic.SignedInLegacy.SignInType
import com.tangem.core.analytics.models.AnalyticsParam.SignInType
import com.tangem.core.analytics.models.Basic.SignedIn
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -20,9 +20,7 @@ import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.message.dialog.Dialogs
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
import com.tangem.domain.card.analytics.Shop
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.SaveWalletError
@ -34,18 +32,18 @@ import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.feature.referral.domain.ShouldShowMobileWalletPromoUseCase
import com.tangem.features.home.api.HomeComponent
import com.tangem.features.home.impl.ui.state.HomeUM
import com.tangem.features.home.impl.ui.state.Stories
import com.tangem.features.home.impl.ui.state.getRestrictedStories
import com.tangem.feature.referral.domain.ShouldShowMobileWalletPromoUseCase
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.Debouncer
import com.tangem.utils.logging.TangemLogger
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import com.tangem.utils.logging.TangemLogger
import java.util.Locale
import javax.inject.Inject
@ -218,20 +216,15 @@ internal class HomeModel @Inject constructor(
}
private suspend fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse, isImported: Boolean) {
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
if (currency != null) {
analyticsEventHandler.send(
SignedInLegacy(
currency = currency,
batch = scanResponse.card.batchId,
SignedIn(
signInType = SignInType.Card,
walletsCount = userWalletsListRepository.userWalletsSync().size.toString(),
walletsCount = userWalletsListRepository.userWalletsSync().size,
isImported = isImported,
hasBackup = scanResponse.card.backupStatus?.isActive,
isBackedUp = scanResponse.card.backupStatus?.isActive == true,
),
)
}
}
private fun setLoading(isLoading: Boolean) {
_uiState.update { it.copy(scanInProgress = isLoading) }

View file

@ -243,10 +243,6 @@ internal class WalletModel @Inject constructor(
} else {
null
}
val isBackedUp = when (selectedWallet) {
is UserWallet.Cold -> selectedWallet.scanResponse.card.backupStatus?.isActive == true
is UserWallet.Hot -> selectedWallet.backedUp
}
val result = getAppThemeModeUseCase().firstOrNull()
val theme = result?.getOrElse { AppThemeMode.FOLLOW_SYSTEM } ?: AppThemeMode.FOLLOW_SYSTEM
val appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }.code
@ -254,7 +250,7 @@ internal class WalletModel @Inject constructor(
WalletScreenAnalyticsEvent.MainScreen.ScreenOpened(
hasMobileWallet = hasMobileWallet,
accountsCount = accountsCount,
isBackedUp = isBackedUp,
isBackedUp = selectedWallet.isBackedUpForAnalytics(),
theme = theme.value,
isImported = selectedWallet.isImported(),
referralId = appsFlyerStore.get()?.refcode,

View file

@ -1,19 +1,20 @@
package com.tangem.feature.wallet.child.wallet.model.intents
import arrow.core.getOrElse
import com.tangem.utils.logging.TangemLogger
import com.tangem.common.routing.AppRoute.*
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.notifications.NotificationId
import com.tangem.common.ui.userwallet.handle
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.Basic.ButtonSupport
import com.tangem.core.analytics.models.event.AssetsDiscoveryAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.review.ReviewManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.assetsdiscovery.usecase.AcknowledgeAssetsDiscoveryCompletionUseCase
import com.tangem.domain.card.SetCardWasScannedUseCase
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
@ -41,16 +42,15 @@ import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.Program
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.PromotionBannerClicked
import com.tangem.domain.tokens.model.details.NavigationAction
import com.tangem.domain.assetsdiscovery.usecase.AcknowledgeAssetsDiscoveryCompletionUseCase
import com.tangem.domain.wallets.usecase.*
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope

View file

@ -1,56 +1,14 @@
package com.tangem.feature.wallet.presentation.wallet.analytics
import com.tangem.core.analytics.models.*
import com.tangem.domain.models.wallet.UserWalletId
sealed class WalletScreenAnalyticsEvent {
sealed class Basic(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category = "Basic", event = event, params = params) {
class WalletToppedUp(userWalletId: UserWalletId, walletType: AnalyticsParam.WalletType) :
Basic(
event = "Topped up",
params = mapOf(AnalyticsParam.CURRENCY to walletType.value),
),
OneTimeAnalyticsEvent, AppsFlyerIncludedEvent {
override val oneTimeEventId: String = id + userWalletId.stringValue
}
class CardWasScanned(source: AnalyticsParam.ScreensSources) : Basic(
event = "Card Was Scanned",
params = mapOf(
AnalyticsParam.SOURCE to source.value,
),
)
class BalanceLoaded(balance: AnalyticsParam.CardBalanceState, tokensCount: Int?) : Basic(
event = "Balance Loaded",
params = buildMap {
put(AnalyticsParam.BALANCE, balance.value)
tokensCount?.let { put(AnalyticsParam.TOKENS_COUNT, it.toString()) }
},
), AppsFlyerIncludedEvent
class TokenBalance(balance: AnalyticsParam.EmptyFull, token: String) : Basic(
event = "Token Balance",
params = mapOf(
AnalyticsParam.STATE to balance.value,
AnalyticsParam.TOKEN_PARAM to token,
),
)
}
sealed class MainScreen(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category = "Main Screen", event = event, params = params) {
class ScreenOpenedLegacy : MainScreen(event = "Screen opened")
data class ScreenOpened(
private val hasMobileWallet: Boolean,
private val accountsCount: Int?,

View file

@ -8,6 +8,7 @@ import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.common.extensions.isZero
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.analytics.CheckIsWalletToppedUpUseCase
@ -18,7 +19,6 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
@ -206,7 +206,12 @@ internal class TokenListAnalyticsSender @Inject constructor(
AnalyticsParam.WalletType.SingleCurrency(currency.currency.name)
}
analyticsEventHandler.send(Basic.WalletToppedUp(userWallet.walletId, walletType))
analyticsEventHandler.send(
Basic.ToppedUp(
userWalletId = userWallet.walletId.stringValue,
walletType = walletType,
),
)
}
}

View file

@ -3,12 +3,12 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.common.extensions.isZero
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetPrimaryCurrencyTransformer
import dagger.assisted.Assisted
@ -70,7 +70,7 @@ internal class PrimaryCurrencySubscriber @AssistedInject constructor(
cardBalanceState?.let { balanceState ->
// do not send tokens count for single currency wallet
analyticsEventHandler.send(
event = WalletScreenAnalyticsEvent.Basic.BalanceLoaded(
event = Basic.BalanceLoaded(
balance = balanceState,
tokensCount = null,
),

View file

@ -19,10 +19,7 @@ import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.SaveWalletError
import com.tangem.domain.common.wallets.error.UnlockWalletError
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isImported
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.models.wallet.*
import com.tangem.domain.settings.CanUseBiometryUseCase
import com.tangem.domain.settings.HotWalletRestrictionManager
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
@ -76,9 +73,9 @@ internal class WelcomeModel @Inject constructor(
val userWallet = userWallets.first { it.walletId == walletId }
trackingContextProxy.addContext(userWallet)
val signInType = when {
!userWallet.isLocked -> SignIn.ButtonWallet.SignInType.NoSecurity
userWallet is UserWallet.Cold -> SignIn.ButtonWallet.SignInType.Card
else -> SignIn.ButtonWallet.SignInType.AccessCode
!userWallet.isLocked -> AnalyticsParam.SignInType.NoSecurity
userWallet is UserWallet.Cold -> AnalyticsParam.SignInType.Card
else -> AnalyticsParam.SignInType.AccessCode
}
analyticsEventHandler.send(
event = SignIn.ButtonWallet(
@ -256,7 +253,7 @@ internal class WelcomeModel @Inject constructor(
if (userWallet.isLocked.not()) {
// If the wallet is not locked, we can proceed to the wallet screen directly
userWalletsListRepository.select(userWallet.walletId)
trackSignInEvent(userWallet, Basic.SignedIn.SignInType.NoSecurity)
trackSignInEvent(userWallet, AnalyticsParam.SignInType.NoSecurity)
router.replaceAll(AppRoute.Wallet)
return@launch
}
@ -317,19 +314,15 @@ internal class WelcomeModel @Inject constructor(
}
}
private suspend fun trackSignInEvent(userWallet: UserWallet, type: Basic.SignedIn.SignInType) {
private suspend fun trackSignInEvent(userWallet: UserWallet, type: AnalyticsParam.SignInType) {
val walletsCount = userWalletsListRepository.userWalletsSync().size
trackingContextProxy.addContext(userWallet)
val isBackedUp = when (userWallet) {
is UserWallet.Cold -> userWallet.scanResponse.card.backupStatus?.isActive == true
is UserWallet.Hot -> userWallet.backedUp
}
analyticsEventHandler.send(
event = Basic.SignedIn(
signInType = type,
walletsCount = walletsCount,
isImported = userWallet.isImported(),
hasBackup = isBackedUp,
isBackedUp = userWallet.isBackedUpForAnalytics(),
),
)
}