Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-05 13:49:17 +05:00
parent bc174f2f06
commit d16bca1c5b
21 changed files with 248 additions and 78 deletions

View file

@ -25,8 +25,6 @@ internal class DefaultTrackingContextProxy(private val abTestsManager: ABTestsMa
override fun setContext(scanResponse: ScanResponse) {
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
Analytics.setContext(userWalletId, scanResponse)
abTestsManager.setUserProperties(
userId = calculateUserIdHash(userWalletId),
batch = scanResponse.card.batchId,
@ -73,6 +71,12 @@ internal class DefaultTrackingContextProxy(private val abTestsManager: ABTestsMa
Analytics.removeContext()
}
override fun proceedWithContext(userWallet: UserWallet, action: () -> Unit) {
setContext(userWallet)
action()
eraseContext()
}
private fun calculateUserIdHash(userWalletId: UserWalletId?): String? {
return userWalletId?.value
?.calculateSha256()

View file

@ -30,6 +30,7 @@ class CardContextInterceptor(
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean {
return when (event) {
is IntroductionProcess.ButtonScanCardLegacy -> false
is IntroductionProcess.ButtonScanCard -> false
else -> true
}

View file

@ -14,6 +14,9 @@ class HotWalletContextInterceptor(
override fun intercept(params: MutableMap<String, String>) {
params[AnalyticsParam.PRODUCT_TYPE] = AnalyticsParam.ProductType.MobileWallet.value
params.remove(AnalyticsParam.BATCH)
params.remove(AnalyticsParam.FIRMWARE)
params.remove(AnalyticsParam.CURRENCY)
}
companion object {

View file

@ -7,6 +7,7 @@ import com.tangem.common.authentication.storage.AuthenticatedStorage
import com.tangem.common.json.TangemSdkAdapter
import com.tangem.common.services.secure.SecureStorage
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.scan.serialization.*
@ -124,6 +125,8 @@ internal object UserWalletsListManagerModule {
appPreferencesStore: AppPreferencesStore,
hotWalletAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
tangemHotSdk: TangemHotSdk,
trackingContextProxy: TrackingContextProxy,
analyticsEventHandler: AnalyticsEventHandler,
): UserWalletsListRepository {
val moshi = buildMoshi()
val secureStorage = buildSecureStorage(applicationContext = applicationContext)
@ -171,6 +174,8 @@ internal object UserWalletsListManagerModule {
savePersistentInformation = ProviderSuspend { true }, // Always save persistent information for now
hotWalletAccessCodeAttemptsRepository = hotWalletAccessCodeAttemptsRepository,
tangemHotSdk = tangemHotSdk,
trackingContextProxy = trackingContextProxy,
analyticsEventHandler = analyticsEventHandler,
)
}

View file

@ -7,6 +7,9 @@ import arrow.core.raise.either
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.Basic
import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
@ -50,6 +53,8 @@ internal class DefaultUserWalletsListRepository(
private val appPreferencesStore: AppPreferencesStore,
private val hotWalletAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
private val tangemHotSdk: TangemHotSdk,
private val trackingContextProxy: TrackingContextProxy,
private val analyticsEventHandler: AnalyticsEventHandler,
) : UserWalletsListRepository {
override val userWallets = MutableStateFlow<List<UserWallet>?>(null)
@ -235,6 +240,7 @@ internal class DefaultUserWalletsListRepository(
when (unlockMethod) {
UserWalletsListRepository.UnlockMethod.Biometric -> {
unlockAllWallets().bind()
trackSignInEvent(userWallet, Basic.SignedIn.SignInType.Biometric)
select(userWalletId)
}
UserWalletsListRepository.UnlockMethod.AccessCode -> {
@ -263,7 +269,10 @@ internal class DefaultUserWalletsListRepository(
removePasswordAttempts(userWallet)
sensitiveInformationRepository.getAll(listOf(encryptionKey))
.doOnSuccess { sensitiveInfo -> updateWallets { it?.updateWith(sensitiveInfo) } }
.doOnSuccess { sensitiveInfo ->
updateWallets { it?.updateWith(sensitiveInfo) }
trackSignInEvent(userWallet, Basic.SignedIn.SignInType.AccessCode)
}
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
}
is UserWalletsListRepository.UnlockMethod.Scan -> {
@ -291,7 +300,10 @@ internal class DefaultUserWalletsListRepository(
)
sensitiveInformationRepository.getAll(listOf(encryptionKey))
.doOnSuccess { sensitiveInfo -> updateWallets { it?.updateWith(sensitiveInfo) } }
.doOnSuccess { sensitiveInfo ->
updateWallets { it?.updateWith(sensitiveInfo) }
trackSignInEvent(userWallet, Basic.SignedIn.SignInType.Card)
}
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
}
}
@ -332,6 +344,9 @@ internal class DefaultUserWalletsListRepository(
sensitiveInformationRepository.getAll(allKeys)
.doOnSuccess { sensitiveInfo ->
updateWallets { wallets -> wallets?.updateWith(sensitiveInfo) }
selectedUserWallet.value?.let {
trackSignInEvent(it, Basic.SignedIn.SignInType.Biometric)
}
}
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
}
@ -508,4 +523,15 @@ internal class DefaultUserWalletsListRepository(
return lastOrNull()
}
private fun trackSignInEvent(userWallet: UserWallet, type: Basic.SignedIn.SignInType) {
trackingContextProxy.proceedWithContext(userWallet) {
analyticsEventHandler.send(
event = Basic.SignedIn(
signInType = type,
walletsCount = userWallets.value?.size ?: 0,
),
)
}
}
}

View file

@ -58,7 +58,7 @@ internal class WelcomeMiddleware {
.doOnSuccess { selectedUserWallet ->
sendSignedInAnalyticsEvent(
userWallet = selectedUserWallet,
signInType = Basic.SignedIn.SignInType.Biometric,
signInType = Basic.SignedInLegacy.SignInType.Biometric,
)
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
@ -80,7 +80,7 @@ internal class WelcomeMiddleware {
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Error(error))
}
.doOnSuccess {
sendSignedInAnalyticsEvent(userWallet, signInType = Basic.SignedIn.SignInType.Card)
sendSignedInAnalyticsEvent(userWallet, signInType = Basic.SignedInLegacy.SignInType.Card)
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Success)
@ -89,9 +89,7 @@ internal class WelcomeMiddleware {
}
}
private fun sendSignedInAnalyticsEvent(userWallet: UserWallet, signInType: Basic.SignedIn.SignInType) {
// TODO [REDACTED_TASK_KEY] [Hot Wallet] Analytics
private fun sendSignedInAnalyticsEvent(userWallet: UserWallet, signInType: Basic.SignedInLegacy.SignInType) {
if (userWallet !is UserWallet.Cold) {
return
}
@ -108,7 +106,7 @@ internal class WelcomeMiddleware {
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
Analytics.send(
event = Basic.SignedIn(
event = Basic.SignedInLegacy(
currency = currency,
batch = scanResponse.card.batchId,
signInType = signInType,

View file

@ -10,6 +10,9 @@ import com.arkivanov.essenty.lifecycle.subscribe
import com.google.android.material.snackbar.Snackbar
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
@ -23,6 +26,7 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.features.hotwallet.HotAccessCodeRequestComponent
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.features.hotwallet.accesscoderequest.proxy.HotWalletPasswordRequesterProxy
import com.tangem.features.walletconnect.components.WcRoutingComponent
import com.tangem.hot.sdk.TangemHotSdk
@ -59,6 +63,9 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
private val userWalletsListRepository: UserWalletsListRepository,
private val cardRepository: CardRepository,
private val onboardingRepository: OnboardingRepository,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val trackingContextProxy: TrackingContextProxy,
private val analyticsEventHandler: AnalyticsEventHandler,
) : RoutingComponent,
AppComponentContext by context,
SnackbarHandler {
@ -130,6 +137,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
)
}
else -> {
trackSignInEvent()
AppRoute.Wallet
}
}.also {
@ -214,4 +222,19 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound(onboardingScanResponse)))
}
}
private suspend fun trackSignInEvent() {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
val userWallets = userWalletsListRepository.userWalletsSync()
val selectedWallet = userWalletsListRepository.selectedUserWalletSync() ?: return
trackingContextProxy.proceedWithContext(selectedWallet) {
analyticsEventHandler.send(
event = Basic.SignedIn(
signInType = Basic.SignedIn.SignInType.NoSecurity,
walletsCount = userWallets.size,
),
)
}
}
}
}

View file

@ -83,12 +83,14 @@ sealed class AnalyticsParam {
data object Onboarding : ScreensSources("Onboarding")
data object LongTap : ScreensSources("Long Tap")
data object Markets : ScreensSources("Markets")
data object HotWallet : ScreensSources("Hot Wallet")
data object TangemPay : ScreensSources("Tangem Pay")
data object WalletSettings : ScreensSources("Wallet Settings")
data object Upgrade : ScreensSources("Upgrade")
data object HardwareWallet : ScreensSources("Hardware Wallet")
data object ImportWallet : ScreensSources("Import Wallet")
data object CreateNewWallet : ScreensSources("Create New Wallet")
data object AddNewWallet : ScreensSources("Add New Wallet")
data object CreateWallet : ScreensSources("Create Wallet")
}
sealed class TxSentFrom(val value: String) {

View file

@ -10,11 +10,11 @@ sealed class Basic(
) : Basic(
event = "Card Was Scanned",
params = mapOf(
AnalyticsParam.SOURCE to source.value,
AnalyticsParam.Key.SOURCE to source.value,
),
)
class SignedIn(
class SignedInLegacy(
currency: AnalyticsParam.WalletType,
batch: String,
signInType: SignInType,
@ -24,8 +24,8 @@ sealed class Basic(
) : Basic(
event = "Signed in",
params = buildMap {
put(AnalyticsParam.CURRENCY, currency.value)
put(AnalyticsParam.BATCH, batch)
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)
@ -39,10 +39,37 @@ sealed class Basic(
}
}
class SignedIn(
signInType: SignInType,
walletsCount: Int,
) : Basic(
event = "Signed in",
params = buildMap {
put("Sign in type", signInType.value)
put("Wallets Count", walletsCount.toString())
},
) {
enum class SignInType(val value: String) {
Card("Card"),
Biometric("Biometric"),
NoSecurity("No Security"),
AccessCode("Access Code"),
}
}
class ButtonBuy(
source: AnalyticsParam.ScreensSources,
) : Basic(
event = "Button - Buy",
params = buildMap {
put(AnalyticsParam.Key.SOURCE, source.value)
},
)
class ToppedUp(userWalletId: String, currency: AnalyticsParam.WalletType) :
Basic(
event = "Topped up",
params = mapOf(AnalyticsParam.CURRENCY to currency.value),
params = mapOf(AnalyticsParam.Key.CURRENCY to currency.value),
),
OneTimeAnalyticsEvent {
@ -53,16 +80,16 @@ sealed class Basic(
Basic(
event = "Transaction sent",
params = buildMap {
this[AnalyticsParam.SOURCE] = sentFrom.value
this[AnalyticsParam.Key.SOURCE] = sentFrom.value
if (sentFrom is AnalyticsParam.TxData) {
this[AnalyticsParam.BLOCKCHAIN] = sentFrom.blockchain
this[AnalyticsParam.TOKEN_PARAM] = sentFrom.token
this[AnalyticsParam.Key.BLOCKCHAIN] = sentFrom.blockchain
this[AnalyticsParam.Key.TOKEN_PARAM] = sentFrom.token
sentFrom.feeType?.value?.let {
this[AnalyticsParam.FEE_TYPE] = it
this[AnalyticsParam.Key.FEE_TYPE] = it
}
}
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
this[AnalyticsParam.PERMISSION_TYPE] = sentFrom.permissionType
this[AnalyticsParam.Key.PERMISSION_TYPE] = sentFrom.permissionType
}
this["Memo"] = memoType.name
},
@ -79,7 +106,7 @@ sealed class Basic(
class ButtonSupport(source: AnalyticsParam.ScreensSources) : Basic(
event = "Request Support",
params = mapOf(
AnalyticsParam.SOURCE to source.value,
AnalyticsParam.Key.SOURCE to source.value,
),
)
@ -89,7 +116,7 @@ sealed class Basic(
) : Basic(
event = "Biometry Failed",
params = mapOf(
AnalyticsParam.SOURCE to source.value,
AnalyticsParam.Key.SOURCE to source.value,
"Reason" to reason.value,
),
) {

View file

@ -0,0 +1,46 @@
package com.tangem.core.analytics.models.event
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
sealed class SignIn(
event: String,
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent("Sign In", event, params) {
data class ScreenOpened(
val walletsCount: Int,
) : SignIn(
event = "Sign In Screen Opened",
params = mapOf(
"Wallets Count" to walletsCount.toString(),
),
)
class ButtonUnlockAllWithBiometric : SignIn(event = "Button - Unlock All With Biometric")
class ButtonWallet(
signInType: SignInType,
) : SignIn(
event = "Button - Wallet",
params = buildMap {
put("Sign in type", signInType.value)
},
) {
enum class SignInType(val value: String) {
Card("Card"),
Biometric("Biometric"),
NoSecurity("No Security"),
AccessCode("Access Code"),
}
}
data class ButtonAddWallet(
val sources: AnalyticsParam.ScreensSources,
) : SignIn(
event = "Button - Add Wallet",
params = mapOf(
AnalyticsParam.SOURCE to sources.value,
),
)
}

View file

@ -23,4 +23,6 @@ interface TrackingContextProxy {
fun addHotWalletContext()
fun removeContext()
fun proceedWithContext(userWallet: UserWallet, action: () -> Unit)
}

View file

@ -1,6 +1,7 @@
package com.tangem.domain.card.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
sealed class IntroductionProcess(
event: String,
@ -10,5 +11,14 @@ sealed class IntroductionProcess(
object ScreenOpened : IntroductionProcess("Introduction Process Screen Opened")
object ButtonTokensList : IntroductionProcess("Button - Tokens List")
object ButtonBuyCards : IntroductionProcess("Button - Buy Cards")
object ButtonScanCard : IntroductionProcess("Button - Scan Card")
object ButtonScanCardLegacy : IntroductionProcess("Button - Scan Card")
class ButtonScanCard(
val source: AnalyticsParam.ScreensSources,
) : IntroductionProcess(
event = "Button - Scan Card",
params = mapOf(
AnalyticsParam.Key.SOURCE to source.value,
),
)
}

View file

@ -2,6 +2,8 @@ package com.tangem.features.createwalletselection
import com.tangem.common.routing.AppRoute
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.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
@ -9,8 +11,6 @@ import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.components.label.entity.LabelStyle
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.analytics.Shop
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.features.createwalletselection.entity.CreateWalletSelectionUM
import com.tangem.features.createwalletselection.impl.R
@ -101,8 +101,7 @@ internal class CreateWalletSelectionModel @Inject constructor(
}
private fun onBuyClick() {
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards)
analyticsEventHandler.send(Shop.ScreenOpened)
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.AddNewWallet))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}

View file

@ -6,8 +6,7 @@ import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic.SignedIn
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -19,8 +18,7 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.SaveWalletError
@ -51,7 +49,6 @@ internal class CreateWalletStartModel @Inject constructor(
private val scanCardProcessor: ScanCardProcessor,
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val settingsRepository: SettingsRepository,
private val analyticsEventHandler: AnalyticsEventHandler,
private val appRouter: AppRouter,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val saveWalletUseCase: SaveWalletUseCase,
@ -59,6 +56,7 @@ internal class CreateWalletStartModel @Inject constructor(
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val urlOpener: UrlOpener,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val params = paramsContainer.require<CreateWalletStartComponent.Params>()
@ -126,6 +124,9 @@ internal class CreateWalletStartModel @Inject constructor(
)
private fun onScanClick() {
analyticsEventHandler.send(
event = IntroductionProcess.ButtonScanCard(AnalyticsParam.ScreensSources.CreateNewWallet),
)
scanCard()
}
@ -134,6 +135,7 @@ internal class CreateWalletStartModel @Inject constructor(
}
private fun onBuyClick() {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.CreateNewWallet))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}
@ -199,28 +201,11 @@ internal class CreateWalletStartModel @Inject constructor(
},
ifRight = {
setLoading(false)
sendSignedInCardAnalyticsEvent(scanResponse = scanResponse, isImported = userWallet.isImported)
appRouter.replaceAll(AppRoute.Wallet)
},
)
}
private suspend fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse, isImported: Boolean) {
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
if (currency != null) {
analyticsEventHandler.send(
SignedIn(
currency = currency,
batch = scanResponse.card.batchId,
signInType = SignInType.Card,
walletsCount = userWalletsListRepository.userWalletsSync().size.toString(),
isImported = isImported,
hasBackup = scanResponse.card.backupStatus?.isActive,
),
)
}
}
private fun setLoading(isLoading: Boolean) {
uiState.update { it.copy(isScanInProgress = isLoading) }
}

View file

@ -4,6 +4,9 @@ import android.content.res.Resources
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.AppInstanceIdProvider
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.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -13,13 +16,14 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.domain.card.common.TapWorkarounds.isVisa
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.WalletMetaInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.feedback.models.WalletMetaInfo
import com.tangem.domain.feedback.repository.FeedbackFeatureToggles
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.details.component.DetailsComponent
@ -29,6 +33,7 @@ import com.tangem.features.details.entity.DetailsUM
import com.tangem.features.details.entity.SelectEmailFeedbackTypeBS
import com.tangem.features.details.utils.ItemsBuilder
import com.tangem.features.details.utils.SocialsBuilder
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.version.AppVersionProvider
import kotlinx.collections.immutable.ImmutableList
@ -60,6 +65,9 @@ internal class DetailsModel @Inject constructor(
private val getWalletsUseCase: GetWalletsUseCase,
private val feedbackFeatureToggles: FeedbackFeatureToggles,
override val dispatchers: CoroutineDispatcherProvider,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val params: DetailsComponent.Params = paramsContainer.require()
@ -216,7 +224,12 @@ internal class DetailsModel @Inject constructor(
private fun onBuyClick() {
modelScope.launch {
urlOpener.openUrl(buildBuyLink())
if (hotWalletFeatureToggles.isHotWalletEnabled) {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.Settings))
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
} else {
urlOpener.openUrl(buildBuyLink())
}
}
}

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.SignedIn
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
import com.tangem.core.analytics.models.Basic.SignedInLegacy
import com.tangem.core.analytics.models.Basic.SignedInLegacy.SignInType
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -127,7 +127,7 @@ internal class HomeModel @Inject constructor(
}
private fun onScanClick() {
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard)
analyticsEventHandler.send(IntroductionProcess.ButtonScanCardLegacy)
scanCard()
}
@ -212,7 +212,7 @@ internal class HomeModel @Inject constructor(
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
if (currency != null) {
analyticsEventHandler.send(
SignedIn(
SignedInLegacy(
currency = currency,
batch = scanResponse.card.batchId,
signInType = SignInType.Card,

View file

@ -5,8 +5,7 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic.SignedIn
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
@ -16,8 +15,7 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.SaveWalletError
@ -74,12 +72,16 @@ internal class CreateHardwareWalletModel @Inject constructor(
}
private fun onBuyTangemWalletClick() {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.CreateWallet))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}
}
private fun onScanDeviceClick() {
analyticsEventHandler.send(
event = IntroductionProcess.ButtonScanCard(AnalyticsParam.ScreensSources.CreateWallet),
)
scanCard()
}
@ -143,28 +145,11 @@ internal class CreateHardwareWalletModel @Inject constructor(
},
ifRight = {
setLoading(false)
sendSignedInCardAnalyticsEvent(scanResponse = scanResponse, isImported = userWallet.isImported)
router.replaceAll(AppRoute.Wallet)
},
)
}
private suspend fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse, isImported: Boolean) {
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
if (currency != null) {
analyticsEventHandler.send(
SignedIn(
currency = currency,
batch = scanResponse.card.batchId,
signInType = SignInType.Card,
walletsCount = userWalletsListRepository.userWalletsSync().size.toString(),
isImported = isImported,
hasBackup = scanResponse.card.backupStatus?.isActive,
),
)
}
}
private fun setLoading(isLoading: Boolean) {
uiState.update { it.copy(isScanInProgress = isLoading) }
}

View file

@ -7,6 +7,8 @@ import com.tangem.common.doOnResult
import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute
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.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -18,6 +20,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.toWrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
@ -85,12 +88,14 @@ internal class UpgradeWalletModel @Inject constructor(
}
private fun onBuyTangemWalletClick() {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.Upgrade))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}
}
private fun onContinueClick() {
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard(AnalyticsParam.ScreensSources.Upgrade))
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonStartUpgrade)
scanCard()
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.hotwallet.walletbackup.model
import com.tangem.common.routing.AppRoute
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.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -111,6 +112,7 @@ internal class WalletBackupModel @Inject constructor(
)
private fun onBuyClick() {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.Backup))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}

View file

@ -4,6 +4,7 @@ import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
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.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -160,6 +161,7 @@ internal class WalletHardwareBackupModel @Inject constructor(
}
private fun onBuyClick() {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.HardwareWallet))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}

View file

@ -3,6 +3,11 @@ package com.tangem.features.welcome.impl.model
import com.tangem.common.routing.AppRoute
import com.tangem.common.ui.userwallet.handle
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
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.SignIn
import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
@ -36,6 +41,8 @@ internal class WelcomeModel @Inject constructor(
private val nonBiometricUnlockWalletUseCase: NonBiometricUnlockWalletUseCase,
private val canUseBiometryUseCase: CanUseBiometryUseCase,
private val walletsRepository: WalletsRepository,
private val trackingContextProxy: TrackingContextProxy,
private val analyticsEventHandler: AnalyticsEventHandler,
userWalletsFetcherFactory: UserWalletsFetcher.Factory,
) : Model() {
@ -51,6 +58,14 @@ internal class WelcomeModel @Inject constructor(
modelScope.launch {
val userWallets = userWalletsListRepository.userWalletsSync()
val userWallet = userWallets.first { it.walletId == walletId }
trackingContextProxy.proceedWithContext(userWallet) {
val signInType = when {
!userWallet.isLocked -> SignIn.ButtonWallet.SignInType.NoSecurity
userWallet is UserWallet.Cold -> SignIn.ButtonWallet.SignInType.Card
else -> SignIn.ButtonWallet.SignInType.AccessCode
}
analyticsEventHandler.send(SignIn.ButtonWallet(signInType))
}
onUserWalletClick(userWallet)
}
},
@ -64,6 +79,8 @@ internal class WelcomeModel @Inject constructor(
userWalletsListRepository.load()
wallets.value = walletsFetcher.userWallets.first()
analyticsEventHandler.send(SignIn.ScreenOpened(wallets.value.size))
launch {
walletsFetcher.userWallets
.collectLatest {
@ -119,6 +136,7 @@ internal class WelcomeModel @Inject constructor(
showUnlockWithBiometricButton = canUnlockWithBiometrics(),
addWalletClick = ::addWalletClick,
onUnlockWithBiometricClick = {
analyticsEventHandler.send(SignIn.ButtonUnlockAllWithBiometric())
modelScope.launch {
userWalletsListRepository.unlockAllWallets()
.onRight {
@ -140,6 +158,7 @@ internal class WelcomeModel @Inject constructor(
}
private fun addWalletClick() {
analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.SignIn))
router.push(AppRoute.CreateWalletSelection)
}
@ -154,6 +173,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)
router.replaceAll(AppRoute.Wallet)
return@launch
}
@ -203,4 +223,16 @@ internal class WelcomeModel @Inject constructor(
}
}
}
private suspend fun trackSignInEvent(userWallet: UserWallet, type: Basic.SignedIn.SignInType) {
val walletsCount = userWalletsListRepository.userWalletsSync().size
trackingContextProxy.proceedWithContext(userWallet) {
analyticsEventHandler.send(
event = Basic.SignedIn(
signInType = type,
walletsCount = walletsCount,
),
)
}
}
}