Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-16 12:17:45 +03:00
parent c562c3fbba
commit 8968f1822a
10 changed files with 146 additions and 7 deletions

View file

@ -3,10 +3,13 @@ package com.tangem.tap.common.analytics
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.utils.AnalyticsContextProxy
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.tap.common.extensions.addContext
import com.tangem.tap.common.extensions.addHotWalletContext
import com.tangem.tap.common.extensions.eraseContext
import com.tangem.tap.common.extensions.removeContext
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.extensions.setHotWalletContext
/**
[REDACTED_AUTHOR]
@ -17,6 +20,14 @@ internal class DefaultAnalyticsContextProxy : AnalyticsContextProxy {
Analytics.setContext(scanResponse)
}
override fun addContext(userWallet: UserWallet) {
Analytics.addContext(userWallet)
}
override fun setHotWalletContext() {
Analytics.setHotWalletContext()
}
override fun eraseContext() {
Analytics.eraseContext()
}
@ -25,6 +36,10 @@ internal class DefaultAnalyticsContextProxy : AnalyticsContextProxy {
Analytics.addContext(scanResponse)
}
override fun addHotWalletContext() {
Analytics.addHotWalletContext()
}
override fun removeContext() {
Analytics.removeContext()
}

View file

@ -0,0 +1,22 @@
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
class HotWalletContextInterceptor(
val parent: ParamsInterceptor? = null,
) : ParamsInterceptor {
override fun id(): String = HotWalletContextInterceptor.id()
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true
override fun intercept(params: MutableMap<String, String>) {
params[AnalyticsParam.PRODUCT_TYPE] = "Mobile Wallet"
}
companion object {
fun id(): String = HotWalletContextInterceptor::class.java.simpleName
}
}

View file

@ -9,7 +9,7 @@ import com.tangem.domain.models.scan.ScanResponse
*/
class LinkedCardContextInterceptor(
scanResponse: ScanResponse,
val parent: LinkedCardContextInterceptor? = null,
val parent: ParamsInterceptor? = null,
) : ParamsInterceptor {
private val contextInterceptor = CardContextInterceptor(scanResponse)

View file

@ -4,6 +4,7 @@ import com.tangem.core.analytics.Analytics
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.tap.common.analytics.paramsInterceptor.HotWalletContextInterceptor
import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor
/**
@ -24,19 +25,46 @@ fun Analytics.setContext(scanResponse: ScanResponse) {
fun Analytics.setContext(userWallet: UserWallet) {
setUserId(userWallet.walletId.stringValue)
// TODO add product type for hot ([REDACTED_TASK_KEY] [Hot Wallet] Analytics)
if (userWallet is UserWallet.Cold) {
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse))
when (userWallet) {
is UserWallet.Cold -> {
removeParamsInterceptor(HotWalletContextInterceptor.id())
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse))
}
is UserWallet.Hot -> {
removeParamsInterceptor(LinkedCardContextInterceptor.id())
addParamsInterceptor(HotWalletContextInterceptor())
}
}
}
fun Analytics.setHotWalletContext() {
addParamsInterceptor(HotWalletContextInterceptor())
}
/**
* Erases the context
*/
fun Analytics.eraseContext() {
clearUserId()
removeParamsInterceptor(LinkedCardContextInterceptor.id())
removeParamsInterceptor(HotWalletContextInterceptor.id())
}
/**
* Adds a new context and keeps a previous context as the parent of the new one
*/
fun Analytics.addContext(userWallet: UserWallet) {
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
val newContext = when (userWallet) {
is UserWallet.Cold -> LinkedCardContextInterceptor(userWallet.scanResponse, parent = currentContext)
is UserWallet.Hot -> HotWalletContextInterceptor(parent = currentContext)
}
setUserId(userWalletId = userWallet.walletId.stringValue)
addParamsInterceptor(newContext)
}
/**
@ -48,18 +76,32 @@ fun Analytics.addContext(scanResponse: ScanResponse) {
setUserId(userWalletId.stringValue)
}
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext)
addParamsInterceptor(newContext)
}
fun Analytics.addHotWalletContext() {
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
val newContext = HotWalletContextInterceptor(currentContext)
addParamsInterceptor(newContext)
}
/**
* Removes the current context and restores the previous one if it was present.
*/
fun Analytics.removeContext() {
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
val previousContext = currentContext?.parent ?: return
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
val previousContext = when (currentContext) {
is LinkedCardContextInterceptor -> currentContext.parent
is HotWalletContextInterceptor -> currentContext.parent
else -> null
} ?: return
addParamsInterceptor(previousContext)
}

View file

@ -1,6 +1,7 @@
package com.tangem.core.analytics.utils
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
/**
[REDACTED_AUTHOR]
@ -9,9 +10,15 @@ interface AnalyticsContextProxy {
fun setContext(scanResponse: ScanResponse)
fun addContext(userWallet: UserWallet)
fun setHotWalletContext()
fun eraseContext()
fun addContext(scanResponse: ScanResponse)
fun addHotWalletContext()
fun removeContext()
}

View file

@ -2,6 +2,7 @@ package com.tangem.features.hotwallet.addexistingwallet.entry
import com.arkivanov.decompose.router.stack.*
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.utils.AnalyticsContextProxy
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -32,6 +33,7 @@ internal class AddExistingWalletModel @Inject constructor(
private val router: Router,
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
private val analyticsContextProxy: AnalyticsContextProxy,
) : Model() {
val hotWalletStepperComponentModelCallback = HotWalletStepperComponentModelCallback()
@ -45,6 +47,15 @@ internal class AddExistingWalletModel @Inject constructor(
val startRoute = AddExistingWalletRoute.Import
val currentRoute: MutableStateFlow<AddExistingWalletRoute> = MutableStateFlow(startRoute)
init {
analyticsContextProxy.addHotWalletContext()
}
override fun onDestroy() {
super.onDestroy()
analyticsContextProxy.removeContext()
}
fun onChildBack() {
when (currentRoute.value) {
is AddExistingWalletRoute.Import -> router.pop()

View file

@ -1,6 +1,7 @@
package com.tangem.features.hotwallet.createmobilewallet
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.utils.AnalyticsContextProxy
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
@ -25,6 +26,7 @@ internal class CreateMobileWalletModel @Inject constructor(
private val saveUserWalletUseCase: SaveWalletUseCase,
private val router: Router,
private val tangemHotSdk: TangemHotSdk,
private val analyticsContextProxy: AnalyticsContextProxy,
) : Model() {
internal val uiState: StateFlow<CreateMobileWalletUM>
@ -37,6 +39,15 @@ internal class CreateMobileWalletModel @Inject constructor(
),
)
init {
analyticsContextProxy.addHotWalletContext()
}
override fun onDestroy() {
super.onDestroy()
analyticsContextProxy.removeContext()
}
private fun onImportClick() {
router.push(AppRoute.AddExistingWallet)
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.hotwallet.createwalletbackup
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.push
import com.tangem.core.analytics.utils.AnalyticsContextProxy
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -23,6 +24,7 @@ internal class CreateWalletBackupModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val analyticsContextProxy: AnalyticsContextProxy,
) : Model() {
val params = paramsContainer.require<CreateWalletBackupComponent.Params>()
@ -36,6 +38,15 @@ internal class CreateWalletBackupModel @Inject constructor(
val startRoute = CreateWalletBackupRoute.RecoveryPhraseStart
val currentRoute: MutableStateFlow<CreateWalletBackupRoute> = MutableStateFlow(startRoute)
init {
analyticsContextProxy.addHotWalletContext()
}
override fun onDestroy() {
super.onDestroy()
analyticsContextProxy.removeContext()
}
fun onBack() {
when (currentRoute.value) {
is CreateWalletBackupRoute.RecoveryPhraseStart -> router.pop()

View file

@ -4,6 +4,7 @@ import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.push
import com.arkivanov.decompose.router.stack.replaceAll
import com.tangem.core.analytics.utils.AnalyticsContextProxy
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -39,6 +40,7 @@ internal class WalletActivationModel @Inject constructor(
private val router: Router,
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
private val analyticsContextProxy: AnalyticsContextProxy,
) : Model() {
val params = paramsContainer.require<WalletActivationComponent.Params>()
@ -56,6 +58,15 @@ internal class WalletActivationModel @Inject constructor(
val startRoute = WalletActivationRoute.ManualBackupStart
val currentRoute: MutableStateFlow<WalletActivationRoute> = MutableStateFlow(startRoute)
init {
analyticsContextProxy.addHotWalletContext()
}
override fun onDestroy() {
super.onDestroy()
analyticsContextProxy.removeContext()
}
fun onChildBack() {
when (currentRoute.value) {
is WalletActivationRoute.ManualBackupStart -> router.pop()

View file

@ -121,6 +121,10 @@ internal class WalletSettingsModel @Inject constructor(
}
init {
getUserWalletUseCase.invoke(params.userWalletId).onRight {
analyticsContextProxy.addContext(it)
}
fun combineUI(wallet: UserWallet) = combine(
getWalletNFTEnabledUseCase.invoke(params.userWalletId),
getWalletNotificationsEnabledUseCase(params.userWalletId),
@ -155,6 +159,11 @@ internal class WalletSettingsModel @Inject constructor(
.launchIn(modelScope)
}
override fun onDestroy() {
super.onDestroy()
analyticsContextProxy.removeContext()
}
private fun isNotificationsPermissionGranted(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissionsRepository.hasRuntimePermission(