Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-09 17:22:28 +05:00
parent aeb60e0cec
commit f2f10fe950
23 changed files with 182 additions and 125 deletions

View file

@ -4,6 +4,7 @@ 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.analytics.models.event.OnboardingAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
@ -98,6 +99,11 @@ internal class CreateWalletSelectionModel @Inject constructor(
}
private fun onMobileWalletClick() {
analyticsEventHandler.send(
event = OnboardingAnalyticsEvent.Onboarding.ButtonMobileWallet(
source = AnalyticsParam.ScreensSources.AddNewWallet.value,
),
)
if (!isHotWalletCreationSupported()) {
uiMessageSender.send(
hotWalletCreationNotSupportedDialog(isHotWalletCreationSupported.getLeastVersionName()),
@ -105,7 +111,7 @@ internal class CreateWalletSelectionModel @Inject constructor(
return
}
router.push(AppRoute.CreateMobileWallet)
router.push(AppRoute.CreateMobileWallet(AnalyticsParam.ScreensSources.AddNewWallet.value))
}
private fun onHardwareWalletClick() {

View file

@ -7,6 +7,7 @@ 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
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -134,6 +135,11 @@ internal class CreateWalletStartModel @Inject constructor(
}
private fun onStartWithMobileWalletClick() {
analyticsEventHandler.send(
event = OnboardingAnalyticsEvent.Onboarding.ButtonMobileWallet(
source = AnalyticsParam.ScreensSources.CreateNewWallet.value,
),
)
if (!isHotWalletCreationSupported()) {
uiMessageSender.send(
hotWalletCreationNotSupportedDialog(isHotWalletCreationSupported.getLeastVersionName()),
@ -141,7 +147,7 @@ internal class CreateWalletStartModel @Inject constructor(
return
}
router.push(AppRoute.CreateMobileWallet)
router.push(AppRoute.CreateMobileWallet(AnalyticsParam.ScreensSources.CreateNewWallet.value))
}
private fun onBuyClick() {

View file

@ -4,5 +4,9 @@ import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
interface CreateMobileWalletComponent : ComposableContentComponent {
interface Factory : ComponentFactory<Unit, CreateMobileWalletComponent>
data class Params(
val source: String,
)
interface Factory : ComponentFactory<Params, CreateMobileWalletComponent>
}

View file

@ -1,5 +1,8 @@
package com.tangem.features.hotwallet.addexistingwallet.im.port.model
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -37,6 +40,7 @@ internal class AddExistingWalletImportModel @Inject constructor(
private val hotUserWalletBuilderFactory: HotUserWalletBuilder.Factory,
private val saveUserWalletUseCase: SaveWalletUseCase,
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val params: AddExistingWalletImportComponent.Params = paramsContainer.require()
@ -60,6 +64,7 @@ internal class AddExistingWalletImportModel @Inject constructor(
}
init {
analyticsEventHandler.send(OnboardingAnalyticsEvent.SeedPhrase.ImportSeedPhraseScreenOpened)
importSeedPhraseUiStateBuilder = ImportSeedPhraseUiStateBuilder(
modelScope = modelScope,
mnemonicRepository = mnemonicRepository,
@ -72,6 +77,7 @@ internal class AddExistingWalletImportModel @Inject constructor(
)
},
onPassphraseInfoClick = ::onPassphraseInfoClick,
onImportClick = { analyticsEventHandler.send(OnboardingAnalyticsEvent.SeedPhrase.ButtonImport) },
)
}
@ -101,6 +107,23 @@ internal class AddExistingWalletImportModel @Inject constructor(
}
.onRight {
setImportProgress(false)
analyticsEventHandler.send(
event = OnboardingAnalyticsEvent.Onboarding.Finished(
source = AnalyticsParam.ScreensSources.ImportWallet.value,
),
)
analyticsEventHandler.send(
event = OnboardingAnalyticsEvent.CreateWallet.WalletCreatedSuccessfully(
source = AnalyticsParam.ScreensSources.ImportWallet.value,
creationType = OnboardingAnalyticsEvent.CreateWallet.WalletCreationType.SeedImport,
seedPhraseLength = mnemonic.mnemonicComponents.size,
passPhraseState = if (passphrase.isNullOrBlank()) {
AnalyticsParam.EmptyFull.Empty
} else {
AnalyticsParam.EmptyFull.Full
},
),
)
params.callbacks.onWalletImported(userWallet.walletId)
}
}.onFailure {

View file

@ -17,6 +17,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Suppress("LongParameterList")
internal class ImportSeedPhraseUiStateBuilder(
private val modelScope: CoroutineScope,
private val mnemonicRepository: MnemonicRepository,
@ -24,6 +25,7 @@ internal class ImportSeedPhraseUiStateBuilder(
private val updateUiState: ((AddExistingWalletImportUM) -> AddExistingWalletImportUM) -> Unit,
private val importWallet: (mnemonic: Mnemonic, passphrase: String?) -> Unit,
private val onPassphraseInfoClick: () -> Unit,
private val onImportClick: () -> Unit,
) {
private val wordsCheckJobHolder = JobHolder()
private var importedMnemonic: Mnemonic? = null
@ -57,6 +59,7 @@ internal class ImportSeedPhraseUiStateBuilder(
}
private fun onCreateWallet() {
onImportClick()
val mnemonic = importedMnemonic ?: return
val passphrase = passphrase?.takeIf { it.isNotEmpty() }
importWallet(mnemonic, passphrase)

View file

@ -1,9 +1,13 @@
package com.tangem.features.hotwallet.createmobilewallet
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.event.OnboardingAnalyticsEvent
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.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.message.dialog.Dialogs.hotWalletCreationNotSupportedDialog
@ -11,6 +15,7 @@ import com.tangem.domain.hotwallet.IsHotWalletCreationSupported
import com.tangem.domain.wallets.builder.HotUserWalletBuilder
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.domain.wallets.usecase.SyncWalletWithRemoteUseCase
import com.tangem.features.hotwallet.CreateMobileWalletComponent
import com.tangem.features.hotwallet.createmobilewallet.entity.CreateMobileWalletUM
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.hot.sdk.model.HotAuth
@ -28,6 +33,7 @@ import javax.inject.Inject
@Suppress("LongParameterList")
@ModelScoped
internal class CreateMobileWalletModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val hotUserWalletBuilderFactory: HotUserWalletBuilder.Factory,
private val saveUserWalletUseCase: SaveWalletUseCase,
@ -37,8 +43,11 @@ internal class CreateMobileWalletModel @Inject constructor(
private val trackingContextProxy: TrackingContextProxy,
private val isHotWalletCreationSupported: IsHotWalletCreationSupported,
private val uiMessageSender: UiMessageSender,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val params: CreateMobileWalletComponent.Params = paramsContainer.require()
internal val uiState: StateFlow<CreateMobileWalletUM>
field = MutableStateFlow(
CreateMobileWalletUM(
@ -51,6 +60,8 @@ internal class CreateMobileWalletModel @Inject constructor(
init {
trackingContextProxy.addHotWalletContext()
analyticsEventHandler.send(OnboardingAnalyticsEvent.Onboarding.Started(source = params.source))
analyticsEventHandler.send(OnboardingAnalyticsEvent.SeedPhrase.CreateMobileScreenOpened)
}
override fun onDestroy() {
@ -59,11 +70,13 @@ internal class CreateMobileWalletModel @Inject constructor(
}
private fun onImportClick() {
analyticsEventHandler.send(OnboardingAnalyticsEvent.SeedPhrase.ButtonImportWallet)
checkHotWalletCreationSupported(notSupported = { return })
router.push(AppRoute.AddExistingWallet)
}
private fun onCreateClick() {
analyticsEventHandler.send(OnboardingAnalyticsEvent.CreateWallet.ButtonCreateWallet)
checkHotWalletCreationSupported(notSupported = { return })
modelScope.launch {
@ -78,6 +91,16 @@ internal class CreateMobileWalletModel @Inject constructor(
saveUserWalletUseCase(userWallet)
analyticsEventHandler.send(OnboardingAnalyticsEvent.Onboarding.Finished(source = params.source))
analyticsEventHandler.send(
event = OnboardingAnalyticsEvent.CreateWallet.WalletCreatedSuccessfully(
source = params.source,
creationType = OnboardingAnalyticsEvent.CreateWallet.WalletCreationType.NewSeed,
seedPhraseLength = SEED_PHRASE_LENGTH,
passPhraseState = AnalyticsParam.EmptyFull.Empty,
),
)
launch(dispatchers.main + NonCancellable) {
syncWalletWithRemoteUseCase(userWalletId = userWallet.walletId)
}
@ -99,4 +122,8 @@ internal class CreateMobileWalletModel @Inject constructor(
notSupported()
}
}
companion object {
private const val SEED_PHRASE_LENGTH = 12
}
}

View file

@ -15,7 +15,7 @@ import dagger.assisted.AssistedInject
@Suppress("UnusedPrivateMember")
internal class DefaultCreateMobileWalletComponent @AssistedInject constructor(
@Assisted private val context: AppComponentContext,
@Assisted private val params: Unit,
@Assisted private val params: CreateMobileWalletComponent.Params,
) : CreateMobileWalletComponent, AppComponentContext by context {
private val model: CreateMobileWalletModel = getOrCreateModel(params)
@ -31,6 +31,9 @@ internal class DefaultCreateMobileWalletComponent @AssistedInject constructor(
@AssistedFactory
interface Factory : CreateMobileWalletComponent.Factory {
override fun create(context: AppComponentContext, params: Unit): DefaultCreateMobileWalletComponent
override fun create(
context: AppComponentContext,
params: CreateMobileWalletComponent.Params,
): DefaultCreateMobileWalletComponent
}
}

View file

@ -250,7 +250,7 @@ class MultiWalletBackupModel @Inject constructor(
private fun showCardVerificationFailedDialog(error: TangemSdkError.CardVerificationFailed) {
analyticsEventHandler.send(
event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed(AnalyticsParam.ScreensSources.Backup),
event = OnboardingAnalyticsEvent.Error.OfflineAttestationFailed(AnalyticsParam.ScreensSources.Backup),
)
val resource = error.localizedDescriptionRes()

View file

@ -24,7 +24,7 @@ internal sealed class SendAnalyticEvents(
val feeType: AnalyticsParam.FeeType,
val blockchain: String,
val nonceNotEmpty: Boolean,
private val ensStatus: AnalyticsParam.EnsStatus,
private val ensStatus: AnalyticsParam.EmptyFull,
) : SendAnalyticEvents(
event = "Transaction Sent Screen Opened",
params = mapOf(
@ -33,8 +33,8 @@ internal sealed class SendAnalyticEvents(
BLOCKCHAIN to blockchain,
NONCE to nonceNotEmpty.toString().capitalize(),
ENS_ADDRESS to when (ensStatus) {
AnalyticsParam.EnsStatus.EMPTY -> false.toString().capitalize()
AnalyticsParam.EnsStatus.FULL -> true.toString().capitalize()
AnalyticsParam.EmptyFull.Empty -> false.toString().capitalize()
AnalyticsParam.EmptyFull.Full -> true.toString().capitalize()
},
),
)

View file

@ -53,13 +53,13 @@ internal class SendAnalyticHelper @Inject constructor(
}
}
private fun getEnsStatus(sendUM: SendUM): AnalyticsParam.EnsStatus {
private fun getEnsStatus(sendUM: SendUM): AnalyticsParam.EmptyFull {
val blockchainAddressForEns =
(sendUM.destinationUM as? DestinationUM.Content)?.addressTextField?.isAddressEns
return if (blockchainAddressForEns == true) {
AnalyticsParam.EnsStatus.FULL
AnalyticsParam.EmptyFull.Full
} else {
AnalyticsParam.EnsStatus.EMPTY
AnalyticsParam.EmptyFull.Empty
}
}
}

View file

@ -56,12 +56,12 @@ internal class TokenReceiveAssetsModel @Inject constructor(
),
)
private fun configureEnsStatus(): AnalyticsParam.EnsStatus {
private fun configureEnsStatus(): AnalyticsParam.EmptyFull {
val hasEnsAddress = params.addresses.any { it.type == ReceiveAddress.Type.Ens }
return if (hasEnsAddress) {
AnalyticsParam.EnsStatus.FULL
AnalyticsParam.EmptyFull.Full
} else {
AnalyticsParam.EnsStatus.EMPTY
AnalyticsParam.EmptyFull.Empty
}
}
}

View file

@ -4,6 +4,7 @@ import arrow.core.getOrElse
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.tokens.TokenItemStateConverter.ApySource
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.models.account.Account
@ -354,7 +355,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
is WalletNFTItemUM.Content -> {
analyticsEventHandler.send(
NFTAnalyticsEvent.NFTListScreenOpened(
state = NFTAnalyticsEvent.NFTListScreenOpened.State.Full,
state = AnalyticsParam.EmptyFull.Full,
allAssetsCount = state.allAssetsCount,
collectionsCount = state.collectionsCount,
noCollectionAssetsCount = state.noCollectionAssetsCount,
@ -364,7 +365,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
is WalletNFTItemUM.Empty -> {
analyticsEventHandler.send(
NFTAnalyticsEvent.NFTListScreenOpened(
state = NFTAnalyticsEvent.NFTListScreenOpened.State.Empty,
state = AnalyticsParam.EmptyFull.Empty,
allAssetsCount = 0,
collectionsCount = 0,
noCollectionAssetsCount = 0,

View file

@ -37,7 +37,7 @@ sealed class WalletScreenAnalyticsEvent {
},
)
class TokenBalance(balance: AnalyticsParam.TokenBalanceState, token: String) : Basic(
class TokenBalance(balance: AnalyticsParam.EmptyFull, token: String) : Basic(
event = "Token Balance",
params = mapOf(
AnalyticsParam.STATE to balance.value,

View file

@ -156,9 +156,9 @@ internal class TokenListAnalyticsSender @Inject constructor(
-> {
if (balanceWasSentMap[blockchain.currency] != true) {
val tokenBalance = if (balanceStatus.amount.isZero()) {
AnalyticsParam.TokenBalanceState.Empty
AnalyticsParam.EmptyFull.Empty
} else {
AnalyticsParam.TokenBalanceState.Full
AnalyticsParam.EmptyFull.Full
}
analyticsEventHandler.send(
Basic.TokenBalance(