Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-10 16:14:43 +05:00
parent facd6bb2ca
commit cf0bce7e61
16 changed files with 91 additions and 51 deletions

View file

@ -71,12 +71,6 @@ 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

@ -10,8 +10,6 @@ sealed class SignIn(
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent("Sign In", event, params) {
class ScreenOpened : SignIn(event = "Sign In Screen Opened")
class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
class ButtonCardSignIn : SignIn(event = "Button - Card Sign In")
}

View file

@ -3,6 +3,7 @@ package com.tangem.tap.common.analytics.paramsInterceptor
import com.tangem.core.analytics.api.ParamsInterceptor
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.SignIn
import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
import com.tangem.domain.card.common.util.cardTypesResolver
@ -30,8 +31,11 @@ class CardContextInterceptor(
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean {
return when (event) {
is IntroductionProcess.ButtonScanCardLegacy -> false
is IntroductionProcess.ButtonScanCard -> false
is IntroductionProcess.ButtonScanCard,
is IntroductionProcess.ButtonScanCardLegacy,
is SignIn.ScreenOpened,
is SignIn.ButtonAddWallet,
-> false
else -> true
}
}

View file

@ -3,6 +3,8 @@ package com.tangem.tap.common.analytics.paramsInterceptor
import com.tangem.core.analytics.api.ParamsInterceptor
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.SignIn
import com.tangem.domain.card.analytics.IntroductionProcess
class HotWalletContextInterceptor(
val parent: ParamsInterceptor? = null,
@ -10,7 +12,16 @@ class HotWalletContextInterceptor(
override fun id(): String = HotWalletContextInterceptor.id()
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean {
return when (event) {
is SignIn.ScreenOpened,
is SignIn.ButtonAddWallet,
is SignIn.ButtonUnlockAllWithBiometric,
is IntroductionProcess.ButtonScanCard,
-> false
else -> true
}
}
override fun intercept(params: MutableMap<String, String>) {
params[AnalyticsParam.PRODUCT_TYPE] = AnalyticsParam.ProductType.MobileWallet.value

View file

@ -525,13 +525,12 @@ internal class DefaultUserWalletsListRepository(
}
private fun trackSignInEvent(userWallet: UserWallet, type: Basic.SignedIn.SignInType) {
trackingContextProxy.proceedWithContext(userWallet) {
analyticsEventHandler.send(
event = Basic.SignedIn(
signInType = type,
walletsCount = userWallets.value?.size ?: 0,
),
)
}
trackingContextProxy.addContext(userWallet)
analyticsEventHandler.send(
event = Basic.SignedIn(
signInType = type,
walletsCount = userWallets.value?.size ?: 0,
),
)
}
}

View file

@ -227,14 +227,13 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
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,
),
)
}
trackingContextProxy.addContext(selectedWallet)
analyticsEventHandler.send(
event = Basic.SignedIn(
signInType = Basic.SignedIn.SignInType.NoSecurity,
walletsCount = userWallets.size,
),
)
}
}
}

View file

@ -1,6 +1,8 @@
package com.tangem.common.ui.userwallet
import com.tangem.common.ui.R
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.SignIn
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
@ -13,6 +15,7 @@ import com.tangem.domain.common.wallets.error.UnlockWalletError.UnableToUnlock.R
inline fun UnlockWalletError.handle(
onAlreadyUnlocked: () -> Unit = {},
onUserCancelled: () -> Unit = {},
analyticsEventHandler: AnalyticsEventHandler,
noinline showMessage: (EventMessage) -> Unit,
) {
when (this) {
@ -30,15 +33,20 @@ inline fun UnlockWalletError.handle(
// This should never happen in this flow, as we always check for the wallet existence before unlocking
showMessage(SnackbarMessage(TextReference.Res(R.string.generic_error)))
}
is UnlockWalletError.UnableToUnlock -> handleUnableToUnlock(this, showMessage)
is UnlockWalletError.UnableToUnlock -> handleUnableToUnlock(this, analyticsEventHandler, showMessage)
}
}
fun handleUnableToUnlock(error: UnlockWalletError.UnableToUnlock, showDialog: (DialogMessage) -> Unit) {
fun handleUnableToUnlock(
error: UnlockWalletError.UnableToUnlock,
analyticsEventHandler: AnalyticsEventHandler,
showDialog: (DialogMessage) -> Unit,
) {
val dialogMessage = when (error) {
is UnlockWalletError.UnableToUnlock.WithReason -> {
when (error.reason) {
Reason.AllKeysInvalidated -> {
analyticsEventHandler.send(SignIn.ErrorBiometricUpdated())
DialogMessage(
title = resourceReference(R.string.biometric_updated_warning_title),
message = resourceReference(R.string.biometric_updated_warning_description),

View file

@ -83,7 +83,7 @@ sealed class AnalyticsParam {
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 CreateWalletIntro : ScreensSources("Create Wallet Intro")
data object AddNewWallet : ScreensSources("Add New Wallet")
data object CreateWallet : ScreensSources("Create Wallet")
}

View file

@ -17,13 +17,19 @@ sealed class SignIn(
),
)
class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
class ButtonUnlockAllWithBiometric : SignIn(event = "Button - Unlock All With Biometric")
class ErrorBiometricUpdated : SignIn(event = "Error - Biometric Updated")
class ButtonWallet(
signInType: SignInType,
walletsCount: Int,
) : SignIn(
event = "Button - Wallet",
params = buildMap {
put("Wallets Count", walletsCount.toString())
put("Sign in type", signInType.value)
},
) {

View file

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

View file

@ -13,6 +13,8 @@ sealed class IntroductionProcess(
class ButtonBuyCards : IntroductionProcess("Button - Buy Cards")
class ButtonScanCardLegacy : IntroductionProcess("Button - Scan Card")
class CreateWalletIntroScreenOpened : IntroductionProcess("Create Wallet Intro Screen Opened")
class ButtonScanCard(
val source: AnalyticsParam.ScreensSources,
) : IntroductionProcess(

View file

@ -127,9 +127,15 @@ internal class CreateWalletStartModel @Inject constructor(
},
)
init {
analyticsEventHandler.send(
event = IntroductionProcess.CreateWalletIntroScreenOpened(),
)
}
private fun onScanClick() {
analyticsEventHandler.send(
event = IntroductionProcess.ButtonScanCard(AnalyticsParam.ScreensSources.CreateNewWallet),
event = IntroductionProcess.ButtonScanCard(AnalyticsParam.ScreensSources.CreateWalletIntro),
)
scanCard()
}
@ -137,7 +143,7 @@ internal class CreateWalletStartModel @Inject constructor(
private fun onStartWithMobileWalletClick() {
analyticsEventHandler.send(
event = OnboardingAnalyticsEvent.Onboarding.ButtonMobileWallet(
source = AnalyticsParam.ScreensSources.CreateNewWallet.value,
source = AnalyticsParam.ScreensSources.CreateWalletIntro.value,
),
)
if (!isHotWalletCreationSupported()) {
@ -147,11 +153,11 @@ internal class CreateWalletStartModel @Inject constructor(
return
}
router.push(AppRoute.CreateMobileWallet(AnalyticsParam.ScreensSources.CreateNewWallet.value))
router.push(AppRoute.CreateMobileWallet(AnalyticsParam.ScreensSources.CreateWalletIntro.value))
}
private fun onBuyClick() {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.CreateNewWallet))
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.CreateWalletIntro))
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}

View file

@ -3,6 +3,9 @@ package com.tangem.features.details.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.event.SignIn
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
@ -39,6 +42,7 @@ internal class UserWalletListModel @Inject constructor(
private val userWalletSaver: UserWalletSaver,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val unlockWalletUseCase: UnlockWalletUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val isWalletSavingInProgress: MutableStateFlow<Boolean> = MutableStateFlow(value = false)
@ -87,6 +91,7 @@ internal class UserWalletListModel @Inject constructor(
private fun onAddNewWalletClick() {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.SignIn))
router.push(AppRoute.CreateWalletSelection)
} else {
withProgress(isWalletSavingInProgress) {
@ -105,6 +110,7 @@ internal class UserWalletListModel @Inject constructor(
error.handle(
onUserCancelled = {},
onAlreadyUnlocked = { router.push(AppRoute.WalletSettings(userWalletId)) },
analyticsEventHandler = analyticsEventHandler,
showMessage = messageSender::send,
)
}

View file

@ -1,5 +1,7 @@
package com.tangem.features.hotwallet.accesscoderequest
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.SignIn
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.ui.components.fields.PinTextColor
@ -33,6 +35,7 @@ internal class HotAccessCodeRequestModel @Inject constructor(
private val hotAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
private val userWalletsListRepository: UserWalletsListRepository,
private val canUseBiometryUseCase: CanUseBiometryUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val result = MutableStateFlow<HotWalletPasswordRequester.Result?>(null)
@ -108,6 +111,7 @@ internal class HotAccessCodeRequestModel @Inject constructor(
onAccessCodeChange = ::onAccessCodeChange,
accessCode = "",
useBiometricClick = {
analyticsEventHandler.send(SignIn.ButtonBiometricSignIn())
dismissState()
result.value = HotWalletPasswordRequester.Result.UseBiometry
},

View file

@ -199,6 +199,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
error.handle(
onAlreadyUnlocked = {},
onUserCancelled = {},
analyticsEventHandler = analyticsEventHandler,
showMessage = uiMessageSender::send,
)
}

View file

@ -58,14 +58,18 @@ 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))
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
}
analyticsEventHandler.send(
event = SignIn.ButtonWallet(
signInType = signInType,
walletsCount = userWallets.size,
),
)
onUserWalletClick(userWallet)
}
},
@ -210,6 +214,7 @@ internal class WelcomeModel @Inject constructor(
router.replaceAll(AppRoute.Wallet)
},
onUserCancelled = { onUserCancelled() },
analyticsEventHandler = analyticsEventHandler,
showMessage = uiMessageSender::send,
)
}
@ -226,13 +231,12 @@ 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,
),
)
}
trackingContextProxy.addContext(userWallet)
analyticsEventHandler.send(
event = Basic.SignedIn(
signInType = type,
walletsCount = walletsCount,
),
)
}
}