Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-27 17:44:05 +05:00
parent b4e9c23f6d
commit 4fb12ce1ff
23 changed files with 415 additions and 84 deletions

View file

@ -14,6 +14,7 @@ import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.extensions.setHotWalletContext import com.tangem.tap.common.extensions.setHotWalletContext
import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.toHexString import com.tangem.common.extensions.toHexString
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
/** /**
@ -91,7 +92,7 @@ internal class DefaultTrackingContextProxy(private val abTestsManager: ABTestsMa
abTestsManager.setUserProperties( abTestsManager.setUserProperties(
userId = calculateUserIdHash(userWallet.walletId), userId = calculateUserIdHash(userWallet.walletId),
batch = null, batch = null,
productType = "Mobile Wallet", productType = AnalyticsParam.ProductType.MobileWallet.value,
firmware = null, firmware = null,
) )
} }

View file

@ -2,13 +2,13 @@ package com.tangem.tap.common.analytics.paramsInterceptor
import com.tangem.core.analytics.api.ParamsInterceptor import com.tangem.core.analytics.api.ParamsInterceptor
import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.card.analytics.IntroductionProcess import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.inject
import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.proxy.redux.DaggerGraphState
@ -50,17 +50,17 @@ class CardContextInterceptor(
if (scanResponse.productType != ProductType.Ring && userWalletId != null) { if (scanResponse.productType != ProductType.Ring && userWalletId != null) {
val isWalletWithRing = runBlocking { walletsRepository.isWalletWithRing(userWalletId) } val isWalletWithRing = runBlocking { walletsRepository.isWalletWithRing(userWalletId) }
if (isWalletWithRing) return "Ring" if (isWalletWithRing) return AnalyticsParam.ProductType.Ring.value
} }
return when (scanResponse.productType) { return when (scanResponse.productType) {
ProductType.Note -> "Note" ProductType.Note -> AnalyticsParam.ProductType.Note.value
ProductType.Twins -> "Twin" ProductType.Twins -> AnalyticsParam.ProductType.Twins.value
ProductType.Wallet -> "Wallet" ProductType.Wallet -> AnalyticsParam.ProductType.Wallet.value
ProductType.Wallet2 -> "Wallet 2.0" ProductType.Wallet2 -> AnalyticsParam.ProductType.Wallet2.value
ProductType.Ring -> "Ring" ProductType.Ring -> AnalyticsParam.ProductType.Ring.value
ProductType.Start2Coin -> "Start2Coin" ProductType.Start2Coin -> AnalyticsParam.ProductType.Start2Coin.value
ProductType.Visa -> "VISA" ProductType.Visa -> AnalyticsParam.ProductType.Visa.value
else -> if (DemoHelper.isDemoCard(scanResponse)) getDemoCardProductType() else "Other" else -> if (DemoHelper.isDemoCard(scanResponse)) getDemoCardProductType() else "Other"
} }
} }

View file

@ -13,7 +13,7 @@ class HotWalletContextInterceptor(
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true
override fun intercept(params: MutableMap<String, String>) { override fun intercept(params: MutableMap<String, String>) {
params[AnalyticsParam.PRODUCT_TYPE] = "Mobile Wallet" params[AnalyticsParam.PRODUCT_TYPE] = AnalyticsParam.ProductType.MobileWallet.value
} }
companion object { companion object {

View file

@ -563,6 +563,8 @@ internal class ChildFactory @Inject constructor(
userWalletId = route.userWalletId, userWalletId = route.userWalletId,
isUpgradeFlow = route.isUpgradeFlow, isUpgradeFlow = route.isUpgradeFlow,
shouldSetAccessCode = route.setAccessCode, shouldSetAccessCode = route.setAccessCode,
analyticsSource = route.analyticsSource,
analyticsAction = route.analyticsAction,
), ),
componentFactory = createWalletBackupComponentFactory, componentFactory = createWalletBackupComponentFactory,
) )
@ -572,6 +574,7 @@ internal class ChildFactory @Inject constructor(
context = context, context = context,
params = UpdateAccessCodeComponent.Params( params = UpdateAccessCodeComponent.Params(
userWalletId = route.userWalletId, userWalletId = route.userWalletId,
source = route.source,
), ),
componentFactory = updateAccessCodeComponentFactory, componentFactory = updateAccessCodeComponentFactory,
) )

View file

@ -364,6 +364,8 @@ sealed class AppRoute(val path: String) : Route {
@Serializable @Serializable
data class CreateWalletBackup( data class CreateWalletBackup(
val userWalletId: UserWalletId, val userWalletId: UserWalletId,
val analyticsSource: String,
val analyticsAction: String,
val isUpgradeFlow: Boolean = false, val isUpgradeFlow: Boolean = false,
val setAccessCode: Boolean = false, val setAccessCode: Boolean = false,
) : AppRoute(path = "/create_wallet_backup/${userWalletId.stringValue}") ) : AppRoute(path = "/create_wallet_backup/${userWalletId.stringValue}")
@ -371,6 +373,7 @@ sealed class AppRoute(val path: String) : Route {
@Serializable @Serializable
data class UpdateAccessCode( data class UpdateAccessCode(
val userWalletId: UserWalletId, val userWalletId: UserWalletId,
val source: String,
) : AppRoute(path = "/update_access_code/${userWalletId.stringValue}") ) : AppRoute(path = "/update_access_code/${userWalletId.stringValue}")
@Serializable @Serializable

View file

@ -85,6 +85,10 @@ sealed class AnalyticsParam {
data object Markets : ScreensSources("Markets") data object Markets : ScreensSources("Markets")
data object HotWallet : ScreensSources("Hot Wallet") data object HotWallet : ScreensSources("Hot Wallet")
data object TangemPay : ScreensSources("Tangem Pay") 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")
} }
sealed class TxSentFrom(val value: String) { sealed class TxSentFrom(val value: String) {
@ -203,6 +207,17 @@ sealed class AnalyticsParam {
EMPTY("Empty"), FULL("Full") EMPTY("Empty"), FULL("Full")
} }
enum class ProductType(val value: String) {
Note("Note"),
Twins("Twins"),
Wallet("Wallet"),
Start2Coin("Start2Coin"),
Wallet2("Wallet 2.0"),
Ring("Ring"),
Visa("VISA"),
MobileWallet("Mobile Wallet"),
}
companion object Key { companion object Key {
const val BLOCKCHAIN = "Blockchain" const val BLOCKCHAIN = "Blockchain"
const val TOKEN_PARAM = "Token" const val TOKEN_PARAM = "Token"

View file

@ -15,6 +15,7 @@ dependencies {
// region Core modules // region Core modules
implementation(projects.core.res) implementation(projects.core.res)
implementation(projects.core.utils) implementation(projects.core.utils)
implementation(projects.core.analytics)
// endregion // endregion
// region Domain modules // region Domain modules

View file

@ -1,8 +1,8 @@
package com.tangem.feature.walletsettings.analytics package com.tangem.domain.wallets.analytics
import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsEvent
internal sealed class Settings( sealed class Settings(
category: String = "Settings", category: String = "Settings",
event: String, event: String,
params: Map<String, String> = emptyMap(), params: Map<String, String> = emptyMap(),

View file

@ -0,0 +1,154 @@
package com.tangem.domain.wallets.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACTION
import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS
sealed class WalletSettingsAnalyticEvents(
category: String = "Settings / Wallet Settings",
event: String,
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category, event, params) {
data class NftToggleSwitch(val enabled: AnalyticsParam.OnOffState) : WalletSettingsAnalyticEvents(
event = "NFT toggle switch",
params = mapOf(STATUS to enabled.value),
)
object WalletSettingsScreenOpened : WalletSettingsAnalyticEvents(
event = "Wallet Settings Screen Opened",
)
object ButtonBackup : WalletSettingsAnalyticEvents(
event = "Button - Backup",
)
data class ButtonAccessCode(
private val isCodeSet: Boolean,
) : WalletSettingsAnalyticEvents(
event = "Button - Access Code",
params = mapOf(ACTION to if (isCodeSet) "Changing" else "Set"),
)
data class BackupScreenOpened(
val isManualBackupEnabled: Boolean,
) : WalletSettingsAnalyticEvents(
event = "Backup Screen Opened",
params = mapOf("Manual Backup" to if (isManualBackupEnabled) "Enabled" else "Disabled"),
)
object ButtonRecoveryPhrase : WalletSettingsAnalyticEvents(
event = "Button - Recovery phrase",
)
data class NoticeBackupFirst(
val source: String,
val action: Action,
) : WalletSettingsAnalyticEvents(
event = "Notice - Backup First",
params = mapOf(
AnalyticsParam.Key.SOURCE to source,
AnalyticsParam.Key.ACTION to action.value,
),
) {
enum class Action(val value: String) {
AccessCode("Access Code"),
Upgrade("Upgrade"),
Remove("Remove"),
}
}
object ButtonHardwareUpdate : WalletSettingsAnalyticEvents(
event = "Button - Hardware Update",
)
object HardwareUpgradeScreenOpened : WalletSettingsAnalyticEvents(
event = "Hardware Upgrade Screen Opened",
)
object ButtonCreateNewWallet : WalletSettingsAnalyticEvents(
event = "Button - Create New Wallet",
)
object ButtonUpgradeCurrent : WalletSettingsAnalyticEvents(
event = "Button - Upgrade Current",
)
object CreateWalletScreenOpened : WalletSettingsAnalyticEvents(
event = "Create Wallet Screen Opened",
)
object HardwareBackupScreenOpened : WalletSettingsAnalyticEvents(
event = "Hardware Backup Screen Opened",
)
data class RecoveryPhraseScreenInfo(
val source: String,
val action: String,
) : WalletSettingsAnalyticEvents(
event = "Recovery Phrase Screen Info",
params = mapOf(
AnalyticsParam.Key.SOURCE to source,
AnalyticsParam.Key.ACTION to action,
),
)
data class RecoveryPhraseScreen(
val source: String,
val action: String,
) : WalletSettingsAnalyticEvents(
event = "Recovery Phrase Screen",
params = mapOf(
AnalyticsParam.Key.SOURCE to source,
AnalyticsParam.Key.ACTION to action,
),
)
data class RecoveryPhraseCheck(
val source: String,
val action: String,
) : WalletSettingsAnalyticEvents(
event = "Recovery Phrase Check",
params = mapOf(
AnalyticsParam.Key.SOURCE to source,
AnalyticsParam.Key.ACTION to action,
),
)
data class BackupCompleteScreen(
val source: String,
val action: String,
) : WalletSettingsAnalyticEvents(
event = "Backup Complete Screen",
params = mapOf(
AnalyticsParam.Key.SOURCE to source,
AnalyticsParam.Key.ACTION to action,
),
)
data class AccessCodeScreenOpened(
val source: String,
) : WalletSettingsAnalyticEvents(
event = "Access Code Screen Opened",
params = mapOf(AnalyticsParam.Key.SOURCE to source),
)
data class ReEnterAccessCodeScreen(
val source: String,
) : WalletSettingsAnalyticEvents(
event = "Re-enter Access Code Screen",
params = mapOf(AnalyticsParam.Key.SOURCE to source),
)
object ButtonStartUpgrade : WalletSettingsAnalyticEvents(
event = "Button - Start Upgrade",
)
enum class RecoveryPhraseScreenAction(val value: String) {
Upgrade("Upgrade"),
Backup("Backup"),
AccessCode("Access Code"),
Remove("Remove"),
}
}

View file

@ -10,6 +10,8 @@ interface CreateWalletBackupComponent : ComposableContentComponent {
val userWalletId: UserWalletId, val userWalletId: UserWalletId,
val isUpgradeFlow: Boolean, val isUpgradeFlow: Boolean,
val shouldSetAccessCode: Boolean, val shouldSetAccessCode: Boolean,
val analyticsSource: String,
val analyticsAction: String,
) )
interface Factory : ComponentFactory<Params, CreateWalletBackupComponent> interface Factory : ComponentFactory<Params, CreateWalletBackupComponent>

View file

@ -7,6 +7,7 @@ import com.tangem.domain.models.wallet.UserWalletId
interface UpdateAccessCodeComponent : ComposableContentComponent { interface UpdateAccessCodeComponent : ComposableContentComponent {
data class Params( data class Params(
val userWalletId: UserWalletId, val userWalletId: UserWalletId,
val source: String,
) )
interface Factory : ComponentFactory<Params, UpdateAccessCodeComponent> interface Factory : ComponentFactory<Params, UpdateAccessCodeComponent>
} }

View file

@ -2,6 +2,8 @@ package com.tangem.features.hotwallet.addexistingwallet.entry
import com.arkivanov.decompose.router.stack.* import com.arkivanov.decompose.router.stack.*
import com.tangem.common.routing.AppRoute 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.utils.TrackingContextProxy import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.core.decompose.di.GlobalUiMessageSender import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
@ -15,6 +17,7 @@ import com.tangem.core.ui.message.EventMessageAction
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.settings.ShouldAskPermissionUseCase import com.tangem.domain.settings.ShouldAskPermissionUseCase
import com.tangem.domain.hotwallet.SetAccessCodeSkippedUseCase import com.tangem.domain.hotwallet.SetAccessCodeSkippedUseCase
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.features.hotwallet.addexistingwallet.entry.routing.AddExistingWalletRoute import com.tangem.features.hotwallet.addexistingwallet.entry.routing.AddExistingWalletRoute
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent
import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent
@ -28,6 +31,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@Suppress("LongParameterList")
@ModelScoped @ModelScoped
internal class AddExistingWalletModel @Inject constructor( internal class AddExistingWalletModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
@ -36,6 +40,7 @@ internal class AddExistingWalletModel @Inject constructor(
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender, @GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
private val trackingContextProxy: TrackingContextProxy, private val trackingContextProxy: TrackingContextProxy,
private val setAccessCodeSkippedUseCase: SetAccessCodeSkippedUseCase, private val setAccessCodeSkippedUseCase: SetAccessCodeSkippedUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() { ) : Model() {
val hotWalletStepperComponentModelCallback = HotWalletStepperComponentModelCallback() val hotWalletStepperComponentModelCallback = HotWalletStepperComponentModelCallback()
@ -49,6 +54,8 @@ internal class AddExistingWalletModel @Inject constructor(
val startRoute = AddExistingWalletRoute.Import val startRoute = AddExistingWalletRoute.Import
val currentRoute: MutableStateFlow<AddExistingWalletRoute> = MutableStateFlow(startRoute) val currentRoute: MutableStateFlow<AddExistingWalletRoute> = MutableStateFlow(startRoute)
private val analyticsSource = AnalyticsParam.ScreensSources.ImportWallet
init { init {
trackingContextProxy.addHotWalletContext() trackingContextProxy.addHotWalletContext()
} }
@ -133,6 +140,9 @@ internal class AddExistingWalletModel @Inject constructor(
inner class ManualBackupCompletedModelCallbacks : ManualBackupCompletedComponent.ModelCallbacks { inner class ManualBackupCompletedModelCallbacks : ManualBackupCompletedComponent.ModelCallbacks {
override fun onContinueClick(userWalletId: UserWalletId) { override fun onContinueClick(userWalletId: UserWalletId) {
analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.AccessCodeScreenOpened(source = analyticsSource.value),
)
stackNavigation.replaceAll(AddExistingWalletRoute.SetAccessCode(userWalletId)) stackNavigation.replaceAll(AddExistingWalletRoute.SetAccessCode(userWalletId))
} }
@ -141,6 +151,9 @@ internal class AddExistingWalletModel @Inject constructor(
inner class AccessCodeModelCallbacks : AccessCodeComponent.ModelCallbacks { inner class AccessCodeModelCallbacks : AccessCodeComponent.ModelCallbacks {
override fun onNewAccessCodeInput(userWalletId: UserWalletId, accessCode: String) { override fun onNewAccessCodeInput(userWalletId: UserWalletId, accessCode: String) {
analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.ReEnterAccessCodeScreen(source = analyticsSource.value),
)
stackNavigation.push(AddExistingWalletRoute.ConfirmAccessCode(userWalletId, accessCode)) stackNavigation.push(AddExistingWalletRoute.ConfirmAccessCode(userWalletId, accessCode))
} }

View file

@ -23,6 +23,7 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.SaveWalletError import com.tangem.domain.common.wallets.error.SaveWalletError
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase import com.tangem.domain.wallets.usecase.SaveWalletUseCase
@ -64,6 +65,10 @@ internal class CreateHardwareWalletModel @Inject constructor(
), ),
) )
init {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.CreateWalletScreenOpened)
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
} }

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.pop
import com.arkivanov.decompose.router.stack.push import com.arkivanov.decompose.router.stack.push
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.utils.TrackingContextProxy import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
@ -11,6 +12,7 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.navigation.popTo import com.tangem.core.decompose.navigation.popTo
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.features.hotwallet.CreateWalletBackupComponent import com.tangem.features.hotwallet.CreateWalletBackupComponent
import com.tangem.features.hotwallet.createwalletbackup.routing.CreateWalletBackupRoute import com.tangem.features.hotwallet.createwalletbackup.routing.CreateWalletBackupRoute
import com.tangem.features.hotwallet.manualbackup.check.ManualBackupCheckComponent import com.tangem.features.hotwallet.manualbackup.check.ManualBackupCheckComponent
@ -27,6 +29,7 @@ internal class CreateWalletBackupModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val router: Router, private val router: Router,
private val trackingContextProxy: TrackingContextProxy, private val trackingContextProxy: TrackingContextProxy,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() { ) : Model() {
val params = paramsContainer.require<CreateWalletBackupComponent.Params>() val params = paramsContainer.require<CreateWalletBackupComponent.Params>()
@ -42,6 +45,12 @@ internal class CreateWalletBackupModel @Inject constructor(
init { init {
trackingContextProxy.addHotWalletContext() trackingContextProxy.addHotWalletContext()
analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.RecoveryPhraseScreenInfo(
source = params.analyticsSource,
action = params.analyticsAction,
),
)
} }
override fun onDestroy() { override fun onDestroy() {
@ -59,14 +68,32 @@ internal class CreateWalletBackupModel @Inject constructor(
} }
fun onManualBackupStarted() { fun onManualBackupStarted() {
analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.RecoveryPhraseScreen(
source = params.analyticsSource,
action = params.analyticsAction,
),
)
stackNavigation.push(CreateWalletBackupRoute.RecoveryPhrase) stackNavigation.push(CreateWalletBackupRoute.RecoveryPhrase)
} }
fun onManualBackupPhraseShown() { fun onManualBackupPhraseShown() {
analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.RecoveryPhraseCheck(
source = params.analyticsSource,
action = params.analyticsAction,
),
)
stackNavigation.push(CreateWalletBackupRoute.ConfirmBackup) stackNavigation.push(CreateWalletBackupRoute.ConfirmBackup)
} }
fun onManualBackupChecked() { fun onManualBackupChecked() {
analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.BackupCompleteScreen(
source = params.analyticsSource,
action = params.analyticsAction,
),
)
stackNavigation.push( stackNavigation.push(
configuration = CreateWalletBackupRoute.BackupCompleted( configuration = CreateWalletBackupRoute.BackupCompleted(
isUpgradeFlow = params.isUpgradeFlow, isUpgradeFlow = params.isUpgradeFlow,
@ -80,6 +107,7 @@ internal class CreateWalletBackupModel @Inject constructor(
router.replaceCurrent( router.replaceCurrent(
route = AppRoute.UpdateAccessCode( route = AppRoute.UpdateAccessCode(
userWalletId = params.userWalletId, userWalletId = params.userWalletId,
source = params.analyticsSource,
), ),
) )
} else { } else {

View file

@ -11,6 +11,8 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.hotwallet.UpdateAccessCodeComponent import com.tangem.features.hotwallet.UpdateAccessCodeComponent
import com.tangem.features.hotwallet.updateaccesscode.routing.UpdateAccessCodeRoute import com.tangem.features.hotwallet.updateaccesscode.routing.UpdateAccessCodeRoute
import com.tangem.features.hotwallet.accesscode.AccessCodeComponent import com.tangem.features.hotwallet.accesscode.AccessCodeComponent
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -21,6 +23,7 @@ internal class UpdateAccessCodeModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val router: Router, private val router: Router,
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model(), AccessCodeComponent.ModelCallbacks { ) : Model(), AccessCodeComponent.ModelCallbacks {
private val params = paramsContainer.require<UpdateAccessCodeComponent.Params>() private val params = paramsContainer.require<UpdateAccessCodeComponent.Params>()
@ -30,6 +33,10 @@ internal class UpdateAccessCodeModel @Inject constructor(
val currentRoute: MutableStateFlow<UpdateAccessCodeRoute> = MutableStateFlow(startRoute) val currentRoute: MutableStateFlow<UpdateAccessCodeRoute> = MutableStateFlow(startRoute)
val mobileWalletSetupFinishedComponentModelCallbacks = MobileWalletSetupFinishedComponentModelCallbacks() val mobileWalletSetupFinishedComponentModelCallbacks = MobileWalletSetupFinishedComponentModelCallbacks()
init {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.AccessCodeScreenOpened(source = params.source))
}
fun onChildBack() { fun onChildBack() {
when (currentRoute.value) { when (currentRoute.value) {
is UpdateAccessCodeRoute.SetAccessCode -> router.pop() is UpdateAccessCodeRoute.SetAccessCode -> router.pop()
@ -39,6 +46,7 @@ internal class UpdateAccessCodeModel @Inject constructor(
} }
override fun onNewAccessCodeInput(userWalletId: UserWalletId, accessCode: String) { override fun onNewAccessCodeInput(userWalletId: UserWalletId, accessCode: String) {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ReEnterAccessCodeScreen(source = params.source))
stackNavigation.push(UpdateAccessCodeRoute.ConfirmAccessCode(userWalletId, accessCode)) stackNavigation.push(UpdateAccessCodeRoute.ConfirmAccessCode(userWalletId, accessCode))
} }

View file

@ -6,6 +6,7 @@ import com.tangem.common.doOnFailure
import com.tangem.common.doOnResult import com.tangem.common.doOnResult
import com.tangem.common.doOnSuccess import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
@ -25,6 +26,7 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.usecase.ClearHotWalletContextualUnlockUseCase import com.tangem.domain.wallets.usecase.ClearHotWalletContextualUnlockUseCase
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
@ -58,6 +60,7 @@ internal class UpgradeWalletModel @Inject constructor(
private val tangemSdkManager: TangemSdkManager, private val tangemSdkManager: TangemSdkManager,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory, private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() { ) : Model() {
private val params = paramsContainer.require<UpgradeWalletComponent.Params>() private val params = paramsContainer.require<UpgradeWalletComponent.Params>()
@ -73,6 +76,10 @@ internal class UpgradeWalletModel @Inject constructor(
), ),
) )
init {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.HardwareUpgradeScreenOpened)
}
override fun onDestroy() { override fun onDestroy() {
clearHotWalletContextualUnlockUseCase.invoke(params.userWalletId) clearHotWalletContextualUnlockUseCase.invoke(params.userWalletId)
super.onDestroy() super.onDestroy()
@ -85,6 +92,7 @@ internal class UpgradeWalletModel @Inject constructor(
} }
private fun onContinueClick() { private fun onContinueClick() {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonStartUpgrade)
scanCard() scanCard()
} }
@ -151,8 +159,6 @@ internal class UpgradeWalletModel @Inject constructor(
private fun showCardVerificationFailedDialog(error: TangemError) { private fun showCardVerificationFailedDialog(error: TangemError) {
if (error !is TangemSdkError.CardVerificationFailed) return if (error !is TangemSdkError.CardVerificationFailed) return
// TODO [REDACTED_TASK_KEY] track error
val resource = error.localizedDescriptionRes() val resource = error.localizedDescriptionRes()
val resId = resource.resId ?: R.string.common_unknown_error val resId = resource.resId ?: R.string.common_unknown_error
val resArgs = resource.args.map { it.value } val resArgs = resource.args.map { it.value }

View file

@ -4,6 +4,8 @@ import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.push import com.arkivanov.decompose.router.stack.push
import com.arkivanov.decompose.router.stack.replaceAll import com.arkivanov.decompose.router.stack.replaceAll
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.utils.TrackingContextProxy import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.core.decompose.di.GlobalUiMessageSender import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
@ -18,6 +20,7 @@ import com.tangem.core.ui.message.EventMessageAction
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.settings.ShouldAskPermissionUseCase import com.tangem.domain.settings.ShouldAskPermissionUseCase
import com.tangem.domain.hotwallet.SetAccessCodeSkippedUseCase import com.tangem.domain.hotwallet.SetAccessCodeSkippedUseCase
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.features.hotwallet.manualbackup.check.ManualBackupCheckComponent import com.tangem.features.hotwallet.manualbackup.check.ManualBackupCheckComponent
import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent
import com.tangem.features.hotwallet.manualbackup.phrase.ManualBackupPhraseComponent import com.tangem.features.hotwallet.manualbackup.phrase.ManualBackupPhraseComponent
@ -41,9 +44,10 @@ internal class WalletActivationModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val router: Router, private val router: Router,
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
private val setAccessCodeSkippedUseCase: SetAccessCodeSkippedUseCase,
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender, @GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
private val trackingContextProxy: TrackingContextProxy, private val trackingContextProxy: TrackingContextProxy,
private val setAccessCodeSkippedUseCase: SetAccessCodeSkippedUseCase, private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() { ) : Model() {
val params = paramsContainer.require<WalletActivationComponent.Params>() val params = paramsContainer.require<WalletActivationComponent.Params>()
@ -66,8 +70,19 @@ internal class WalletActivationModel @Inject constructor(
} }
val currentRoute: MutableStateFlow<WalletActivationRoute> = MutableStateFlow(startRoute) val currentRoute: MutableStateFlow<WalletActivationRoute> = MutableStateFlow(startRoute)
private val source = AnalyticsParam.ScreensSources.Main
private val action = WalletSettingsAnalyticEvents.RecoveryPhraseScreenAction.Backup
init { init {
trackingContextProxy.addHotWalletContext() trackingContextProxy.addHotWalletContext()
if (startRoute is WalletActivationRoute.ManualBackupStart) {
analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.RecoveryPhraseScreenInfo(
source = source.value,
action = action.value,
),
)
}
} }
override fun onDestroy() { override fun onDestroy() {
@ -143,13 +158,23 @@ internal class WalletActivationModel @Inject constructor(
inner class ManualBackupStartModelCallbacks : ManualBackupStartComponent.ModelCallbacks { inner class ManualBackupStartModelCallbacks : ManualBackupStartComponent.ModelCallbacks {
override fun onContinueClick() { override fun onContinueClick() {
stackNavigation.push(WalletActivationRoute.ManualBackupPhrase) stackNavigation.push(WalletActivationRoute.ManualBackupPhrase)
analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.RecoveryPhraseScreen(
source = source.value,
action = action.value,
),
)
} }
} }
inner class ManualBackupPhraseModelCallbacks : ManualBackupPhraseComponent.ModelCallbacks { inner class ManualBackupPhraseModelCallbacks : ManualBackupPhraseComponent.ModelCallbacks {
override fun onContinueClick() { override fun onContinueClick() {
stackNavigation.push( stackNavigation.push(WalletActivationRoute.ManualBackupCheck)
WalletActivationRoute.ManualBackupCheck, analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.RecoveryPhraseCheck(
source = source.value,
action = action.value,
),
) )
} }
} }
@ -157,6 +182,12 @@ internal class WalletActivationModel @Inject constructor(
inner class ManualBackupCheckModelCallbacks : ManualBackupCheckComponent.ModelCallbacks { inner class ManualBackupCheckModelCallbacks : ManualBackupCheckComponent.ModelCallbacks {
override fun onCompleteClick() { override fun onCompleteClick() {
stackNavigation.push(WalletActivationRoute.ManualBackupCompleted) stackNavigation.push(WalletActivationRoute.ManualBackupCompleted)
analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.BackupCompleteScreen(
source = source.value,
action = action.value,
),
)
} }
} }

View file

@ -1,6 +1,8 @@
package com.tangem.features.hotwallet.walletbackup.model package com.tangem.features.hotwallet.walletbackup.model
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
@ -11,6 +13,7 @@ import com.tangem.core.ui.components.label.entity.LabelStyle
import com.tangem.core.ui.components.label.entity.LabelUM import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.UnlockHotWalletContextualUseCase import com.tangem.domain.wallets.usecase.UnlockHotWalletContextualUseCase
@ -27,12 +30,13 @@ import javax.inject.Inject
@ModelScoped @ModelScoped
internal class WalletBackupModel @Inject constructor( internal class WalletBackupModel @Inject constructor(
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val getUserWalletUseCase: GetUserWalletUseCase, private val getUserWalletUseCase: GetUserWalletUseCase,
private val unlockHotWalletContextualUseCase: UnlockHotWalletContextualUseCase, private val unlockHotWalletContextualUseCase: UnlockHotWalletContextualUseCase,
override val dispatchers: CoroutineDispatcherProvider,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase, private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val urlOpener: UrlOpener, private val urlOpener: UrlOpener,
private val router: Router, private val router: Router,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() { ) : Model() {
private val params: WalletBackupComponent.Params = paramsContainer.require() private val params: WalletBackupComponent.Params = paramsContainer.require()
@ -63,6 +67,7 @@ internal class WalletBackupModel @Inject constructor(
) )
init { init {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.BackupScreenOpened(isManualBackupEnabled = true))
getUserWalletUseCase.invokeFlow(params.userWalletId) getUserWalletUseCase.invokeFlow(params.userWalletId)
.onEach { either -> .onEach { either ->
either.fold( either.fold(
@ -112,6 +117,7 @@ internal class WalletBackupModel @Inject constructor(
} }
private fun onRecoveryPhraseClick() { private fun onRecoveryPhraseClick() {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonRecoveryPhrase)
if (uiState.value.backedUp) { if (uiState.value.backedUp) {
getUserWalletUseCase.invoke(params.userWalletId) getUserWalletUseCase.invoke(params.userWalletId)
.fold( .fold(
@ -132,6 +138,8 @@ internal class WalletBackupModel @Inject constructor(
router.push( router.push(
AppRoute.CreateWalletBackup( AppRoute.CreateWalletBackup(
userWalletId = params.userWalletId, userWalletId = params.userWalletId,
analyticsSource = AnalyticsParam.ScreensSources.Backup.value,
analyticsAction = WalletSettingsAnalyticEvents.RecoveryPhraseScreenAction.Backup.value,
), ),
) )
} }
@ -152,6 +160,7 @@ internal class WalletBackupModel @Inject constructor(
} }
private fun onHardwareWalletClick() { private fun onHardwareWalletClick() {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonHardwareUpdate)
router.push(AppRoute.WalletHardwareBackup(params.userWalletId)) router.push(AppRoute.WalletHardwareBackup(params.userWalletId))
} }
} }

View file

@ -2,6 +2,8 @@ package com.tangem.features.hotwallet.wallethardwarebackup.model
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
@ -18,6 +20,7 @@ import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.bottomSheetMessage import com.tangem.core.ui.message.bottomSheetMessage
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.UnlockHotWalletContextualUseCase import com.tangem.domain.wallets.usecase.UnlockHotWalletContextualUseCase
@ -46,30 +49,41 @@ internal class WalletHardwareBackupModel @Inject constructor(
private val urlOpener: UrlOpener, private val urlOpener: UrlOpener,
private val getUserWalletUseCase: GetUserWalletUseCase, private val getUserWalletUseCase: GetUserWalletUseCase,
private val messageSender: UiMessageSender, private val messageSender: UiMessageSender,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() { ) : Model() {
private val params = paramsContainer.require<WalletHardwareBackupComponent.Params>() private val params = paramsContainer.require<WalletHardwareBackupComponent.Params>()
private val makeBackupAtFirstAlertBS private val makeBackupAtFirstAlertBS
get() = bottomSheetMessage { get() = run {
infoBlock { analyticsEventHandler.send(
icon(R.drawable.ic_passcode_lock_32) { WalletSettingsAnalyticEvents.NoticeBackupFirst(
type = MessageBottomSheetUMV2.Icon.Type.Accent source = AnalyticsParam.ScreensSources.HardwareWallet.value,
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint action = WalletSettingsAnalyticEvents.NoticeBackupFirst.Action.Upgrade,
),
)
bottomSheetMessage {
infoBlock {
icon(R.drawable.ic_passcode_lock_32) {
type = MessageBottomSheetUMV2.Icon.Type.Accent
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
}
title = resourceReference(R.string.hw_backup_need_finish_first)
body = resourceReference(R.string.hw_backup_to_upgrade_description)
} }
title = resourceReference(R.string.hw_backup_need_finish_first) primaryButton {
body = resourceReference(R.string.hw_backup_to_upgrade_description) text = resourceReference(R.string.hw_backup_need_action)
} onClick {
primaryButton { router.push(
text = resourceReference(R.string.hw_backup_need_action) AppRoute.CreateWalletBackup(
onClick { userWalletId = params.userWalletId,
router.push( isUpgradeFlow = true,
AppRoute.CreateWalletBackup( analyticsSource = AnalyticsParam.ScreensSources.HardwareWallet.value,
userWalletId = params.userWalletId, analyticsAction = WalletSettingsAnalyticEvents.RecoveryPhraseScreenAction.Upgrade.value,
isUpgradeFlow = true, ),
), )
) closeBs()
closeBs() }
} }
} }
} }
@ -100,6 +114,7 @@ internal class WalletHardwareBackupModel @Inject constructor(
) )
init { init {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.HardwareBackupScreenOpened)
showPurchaseBlockWithDelay() showPurchaseBlockWithDelay()
} }
@ -111,6 +126,7 @@ internal class WalletHardwareBackupModel @Inject constructor(
} }
private fun onCreateNewWalletClick() { private fun onCreateNewWalletClick() {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonCreateNewWallet)
router.push(AppRoute.CreateHardwareWallet) router.push(AppRoute.CreateHardwareWallet)
} }
@ -118,6 +134,7 @@ internal class WalletHardwareBackupModel @Inject constructor(
val userWallet = getUserWalletUseCase.invoke(params.userWalletId) val userWallet = getUserWalletUseCase.invoke(params.userWalletId)
.getOrElse { error("Cannot find user wallet with id: ${params.userWalletId.stringValue}") } .getOrElse { error("Cannot find user wallet with id: ${params.userWalletId.stringValue}") }
if (userWallet is UserWallet.Hot) { if (userWallet is UserWallet.Hot) {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonUpgradeCurrent)
if (!userWallet.backedUp) { if (!userWallet.backedUp) {
messageSender.send(makeBackupAtFirstAlertBS) messageSender.send(makeBackupAtFirstAlertBS)
} else { } else {

View file

@ -1,17 +0,0 @@
package com.tangem.feature.walletsettings.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS
internal sealed class WalletSettingsAnalyticEvents(
category: String = "Settings / Wallet",
event: String,
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category, event, params) {
data class NftToggleSwitch(val enabled: AnalyticsParam.OnOffState) : WalletSettingsAnalyticEvents(
event = "NFT toggle switch",
params = mapOf(STATUS to enabled.value),
)
}

View file

@ -5,7 +5,6 @@ import androidx.compose.ui.Modifier
import com.tangem.common.ui.account.AccountIconPreviewData import com.tangem.common.ui.account.AccountIconPreviewData
import com.tangem.common.ui.userwallet.state.UserWalletItemUM import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.common.ui.userwallet.state.UserWalletItemUM.ImageState import com.tangem.common.ui.userwallet.state.UserWalletItemUM.ImageState
import com.tangem.core.decompose.navigation.DummyRouter
import com.tangem.core.ui.components.block.model.BlockUM import com.tangem.core.ui.components.block.model.BlockUM
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
@ -43,9 +42,7 @@ internal class PreviewWalletSettingsComponent : WalletSettingsComponent {
private val previewState = WalletSettingsUM( private val previewState = WalletSettingsUM(
popBack = {}, popBack = {},
items = ItemsBuilder( items = ItemsBuilder().buildItems(
router = DummyRouter(),
).buildItems(
userWallet = UserWallet.Hot( userWallet = UserWallet.Hot(
walletId = UserWalletId("011"), walletId = UserWalletId("011"),
name = "My Wallet", name = "My Wallet",
@ -71,6 +68,8 @@ internal class PreviewWalletSettingsComponent : WalletSettingsComponent {
walletUpgradeDismissed = false, walletUpgradeDismissed = false,
onUpgradeWalletClick = {}, onUpgradeWalletClick = {},
onDismissUpgradeWalletClick = {}, onDismissUpgradeWalletClick = {},
onBackupClick = {},
onCardSettingsClick = {},
accountsUM = previewAccounts(), accountsUM = previewAccounts(),
cardItem = previewCardBlock(), cardItem = previewCardBlock(),
), ),

View file

@ -8,6 +8,7 @@ import com.arkivanov.decompose.router.slot.activate
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRoute.ManageTokens.Source import com.tangem.common.routing.AppRoute.ManageTokens.Source
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.OnOffState.Off import com.tangem.core.analytics.models.AnalyticsParam.OnOffState.Off
import com.tangem.core.analytics.models.AnalyticsParam.OnOffState.On import com.tangem.core.analytics.models.AnalyticsParam.OnOffState.On
import com.tangem.core.analytics.utils.TrackingContextProxy import com.tangem.core.analytics.utils.TrackingContextProxy
@ -36,9 +37,10 @@ import com.tangem.domain.nft.EnableWalletNFTUseCase
import com.tangem.domain.nft.GetWalletNFTEnabledUseCase import com.tangem.domain.nft.GetWalletNFTEnabledUseCase
import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.settings.repositories.PermissionRepository import com.tangem.domain.settings.repositories.PermissionRepository
import com.tangem.domain.wallets.analytics.Settings
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents.RecoveryPhraseScreenAction
import com.tangem.domain.wallets.usecase.* import com.tangem.domain.wallets.usecase.*
import com.tangem.feature.walletsettings.analytics.Settings
import com.tangem.feature.walletsettings.analytics.WalletSettingsAnalyticEvents
import com.tangem.feature.walletsettings.component.WalletSettingsComponent import com.tangem.feature.walletsettings.component.WalletSettingsComponent
import com.tangem.feature.walletsettings.entity.* import com.tangem.feature.walletsettings.entity.*
import com.tangem.feature.walletsettings.impl.R import com.tangem.feature.walletsettings.impl.R
@ -111,8 +113,9 @@ internal class WalletSettingsModel @Inject constructor(
) )
init { init {
getUserWalletUseCase.invoke(params.userWalletId).onRight { getUserWalletUseCase.invoke(params.userWalletId).onRight { wallet ->
trackingContextProxy.addContext(it) trackingContextProxy.addContext(wallet)
analyticsEventHandler.send(WalletSettingsAnalyticEvents.WalletSettingsScreenOpened)
} }
fun combineUI(wallet: UserWallet) = combine( fun combineUI(wallet: UserWallet) = combine(
@ -235,10 +238,12 @@ internal class WalletSettingsModel @Inject constructor(
isNotificationsPermissionGranted = isNotificationsPermissionGranted, isNotificationsPermissionGranted = isNotificationsPermissionGranted,
onCheckedNotificationsChanged = ::onCheckedNotificationsChange, onCheckedNotificationsChanged = ::onCheckedNotificationsChange,
onNotificationsDescriptionClick = ::onNotificationsDescriptionClick, onNotificationsDescriptionClick = ::onNotificationsDescriptionClick,
onAccessCodeClick = ::onAccessCodeClick, onAccessCodeClick = { onAccessCodeClick(userWallet) },
walletUpgradeDismissed = isUpgradeNotificationEnabled, walletUpgradeDismissed = isUpgradeNotificationEnabled,
onUpgradeWalletClick = ::onUpgradeWalletClick, onUpgradeWalletClick = { onUpgradeWalletClick() },
onDismissUpgradeWalletClick = ::onDismissUpgradeWalletClick, onDismissUpgradeWalletClick = ::onDismissUpgradeWalletClick,
onBackupClick = ::onBackupClick,
onCardSettingsClick = ::onCardSettingsClick,
accountsUM = accountList, accountsUM = accountList,
) )
} }
@ -262,7 +267,6 @@ internal class WalletSettingsModel @Inject constructor(
} }
private fun onLinkMoreCardsClick(scanResponse: ScanResponse) { private fun onLinkMoreCardsClick(scanResponse: ScanResponse) {
analyticsEventHandler.send(Settings.ButtonCreateBackup)
trackingContextProxy.addContext(scanResponse) trackingContextProxy.addContext(scanResponse)
router.push( router.push(
@ -357,23 +361,35 @@ internal class WalletSettingsModel @Inject constructor(
} }
} }
private fun onAccessCodeClick() { private fun onAccessCodeClick(userWallet: UserWallet) {
if (!state.value.isWalletBackedUp) { if (userWallet is UserWallet.Hot) {
showMakeBackupAtFirstAlertBS(isUpgradeFlow = false) val isCodeSet = userWallet.hotWalletId.authType != HotWalletId.AuthType.NoPassword
} else { analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonAccessCode(isCodeSet))
unlockWalletIfNeedAndProceed { authorizationRequired -> if (!state.value.isWalletBackedUp) {
router.push( showMakeBackupAtFirstAlertBS(
route = AppRoute.UpdateAccessCode( isUpgradeFlow = false,
userWalletId = params.userWalletId, action = WalletSettingsAnalyticEvents.NoticeBackupFirst.Action.AccessCode,
),
) )
} else {
unlockWalletIfNeedAndProceed { authorizationRequired ->
router.push(
route = AppRoute.UpdateAccessCode(
userWalletId = params.userWalletId,
source = AnalyticsParam.ScreensSources.WalletSettings.value,
),
)
}
} }
} }
} }
private fun onUpgradeWalletClick() { private fun onUpgradeWalletClick() {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonHardwareUpdate)
if (!state.value.isWalletBackedUp) { if (!state.value.isWalletBackedUp) {
showMakeBackupAtFirstAlertBS(isUpgradeFlow = true) showMakeBackupAtFirstAlertBS(
isUpgradeFlow = true,
action = WalletSettingsAnalyticEvents.NoticeBackupFirst.Action.Upgrade,
)
} else { } else {
unlockWalletIfNeedAndProceed { unlockWalletIfNeedAndProceed {
router.push(AppRoute.UpgradeWallet(userWalletId = params.userWalletId)) router.push(AppRoute.UpgradeWallet(userWalletId = params.userWalletId))
@ -387,7 +403,25 @@ internal class WalletSettingsModel @Inject constructor(
} }
} }
private fun showMakeBackupAtFirstAlertBS(isUpgradeFlow: Boolean) { private fun onBackupClick() {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonBackup)
router.push(AppRoute.WalletBackup(params.userWalletId))
}
private fun onCardSettingsClick() {
router.push(AppRoute.CardSettings(params.userWalletId))
}
private fun showMakeBackupAtFirstAlertBS(
action: WalletSettingsAnalyticEvents.NoticeBackupFirst.Action,
isUpgradeFlow: Boolean,
) {
analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.NoticeBackupFirst(
source = AnalyticsParam.ScreensSources.WalletSettings.value,
action = action,
),
)
val message = bottomSheetMessage { val message = bottomSheetMessage {
infoBlock { infoBlock {
icon(R.drawable.ic_passcode_lock_32) { icon(R.drawable.ic_passcode_lock_32) {
@ -405,6 +439,12 @@ internal class WalletSettingsModel @Inject constructor(
userWalletId = params.userWalletId, userWalletId = params.userWalletId,
isUpgradeFlow = isUpgradeFlow, isUpgradeFlow = isUpgradeFlow,
setAccessCode = true, setAccessCode = true,
analyticsSource = AnalyticsParam.ScreensSources.WalletSettings.value,
analyticsAction = if (isUpgradeFlow) {
RecoveryPhraseScreenAction.Backup.value
} else {
RecoveryPhraseScreenAction.AccessCode.value
},
), ),
) )
closeBs() closeBs()
@ -478,6 +518,15 @@ internal class WalletSettingsModel @Inject constructor(
) )
} }
is UserWallet.Hot -> { is UserWallet.Hot -> {
if (!userWallet.backedUp) {
analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.NoticeBackupFirst(
source = AnalyticsParam.ScreensSources.WalletSettings.value,
action = WalletSettingsAnalyticEvents.NoticeBackupFirst.Action.Remove,
),
)
}
bottomSheetMessage { bottomSheetMessage {
infoBlock { infoBlock {
icon(R.drawable.ic_alert_circle_24) { icon(R.drawable.ic_alert_circle_24) {
@ -525,6 +574,8 @@ internal class WalletSettingsModel @Inject constructor(
router.push( router.push(
AppRoute.CreateWalletBackup( AppRoute.CreateWalletBackup(
userWalletId = userWallet.walletId, userWalletId = userWallet.walletId,
analyticsSource = AnalyticsParam.ScreensSources.WalletSettings.value,
analyticsAction = RecoveryPhraseScreenAction.Remove.value,
), ),
) )
closeBs() closeBs()

View file

@ -1,8 +1,6 @@
package com.tangem.feature.walletsettings.utils package com.tangem.feature.walletsettings.utils
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.components.block.model.BlockUM import com.tangem.core.ui.components.block.model.BlockUM
import com.tangem.core.ui.components.label.entity.LabelStyle import com.tangem.core.ui.components.label.entity.LabelStyle
import com.tangem.core.ui.components.label.entity.LabelUM import com.tangem.core.ui.components.label.entity.LabelUM
@ -18,9 +16,7 @@ import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject import javax.inject.Inject
@ModelScoped @ModelScoped
internal class ItemsBuilder @Inject constructor( internal class ItemsBuilder @Inject constructor() {
private val router: Router,
) {
@Suppress("LongParameterList") @Suppress("LongParameterList")
fun buildItems( fun buildItems(
@ -45,6 +41,8 @@ internal class ItemsBuilder @Inject constructor(
walletUpgradeDismissed: Boolean, walletUpgradeDismissed: Boolean,
onUpgradeWalletClick: () -> Unit, onUpgradeWalletClick: () -> Unit,
onDismissUpgradeWalletClick: () -> Unit, onDismissUpgradeWalletClick: () -> Unit,
onBackupClick: () -> Unit,
onCardSettingsClick: () -> Unit,
): PersistentList<WalletSettingsItemUM> = persistentListOf<WalletSettingsItemUM>() ): PersistentList<WalletSettingsItemUM> = persistentListOf<WalletSettingsItemUM>()
.add(cardItem) .add(cardItem)
.addAll( .addAll(
@ -66,6 +64,8 @@ internal class ItemsBuilder @Inject constructor(
onLinkMoreCardsClick = onLinkMoreCardsClick, onLinkMoreCardsClick = onLinkMoreCardsClick,
onReferralClick = onReferralClick, onReferralClick = onReferralClick,
onManageTokensClick = onManageTokensClick, onManageTokensClick = onManageTokensClick,
onBackupClick = onBackupClick,
onCardSettingsClick = onCardSettingsClick,
), ),
) )
.addAll( .addAll(
@ -173,18 +173,19 @@ internal class ItemsBuilder @Inject constructor(
onLinkMoreCardsClick: () -> Unit, onLinkMoreCardsClick: () -> Unit,
onReferralClick: () -> Unit, onReferralClick: () -> Unit,
onManageTokensClick: () -> Unit, onManageTokensClick: () -> Unit,
onBackupClick: () -> Unit,
onCardSettingsClick: () -> Unit,
) = WalletSettingsItemUM.WithItems( ) = WalletSettingsItemUM.WithItems(
id = "card", id = "card",
description = resourceReference(R.string.settings_card_settings_footer), description = resourceReference(R.string.settings_card_settings_footer),
blocks = buildList { blocks = buildList {
val userWalletId = userWallet.walletId
val isHotWallet = userWallet is UserWallet.Hot val isHotWallet = userWallet is UserWallet.Hot
if (isHotWallet) { if (isHotWallet) {
val hasBackup = userWallet.backedUp val hasBackup = userWallet.backedUp
val backupBlock = BlockUM( val backupBlock = BlockUM(
text = resourceReference(R.string.common_backup), text = resourceReference(R.string.common_backup),
iconRes = R.drawable.ic_more_cards_24, iconRes = R.drawable.ic_more_cards_24,
onClick = { router.push(AppRoute.WalletBackup(userWalletId)) }, onClick = { onBackupClick() },
endContent = if (hasBackup) { endContent = if (hasBackup) {
BlockUM.EndContent.None BlockUM.EndContent.None
} else { } else {
@ -224,7 +225,7 @@ internal class ItemsBuilder @Inject constructor(
val deviceSettingsBlock = BlockUM( val deviceSettingsBlock = BlockUM(
text = resourceReference(R.string.card_settings_title), text = resourceReference(R.string.card_settings_title),
iconRes = R.drawable.ic_card_settings_24, iconRes = R.drawable.ic_card_settings_24,
onClick = { router.push(AppRoute.CardSettings(userWalletId)) }, onClick = { onCardSettingsClick() },
) )
add(deviceSettingsBlock) add(deviceSettingsBlock)