Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-15 14:59:31 +07:00
parent b316ef4d87
commit 370690a359
42 changed files with 486 additions and 35 deletions

View file

@ -266,5 +266,6 @@ sealed class AnalyticsParam {
const val CHOSEN_TOKEN = "Token Chosen"
const val ENS = "ENS"
const val ENS_ADDRESS = "ENS Address"
const val ACCOUNT_DERIVATION_FROM = "Account Derivation (from)"
}
}

View file

@ -56,6 +56,14 @@ sealed class MainScreenAnalyticsEvent(
event = "Button - Explore",
)
class AccountShowTokens : MainScreenAnalyticsEvent(
event = "Button - Account Show Tokens",
)
class AccountHideTokens : MainScreenAnalyticsEvent(
event = "Button - Account Hide Tokens",
)
data class ButtonSwap(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
event = "Button - Swap",
params = mapOf(AnalyticsParam.STATUS to status.value),

View file

@ -166,7 +166,6 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
}
override fun approve(sessionForApprove: WcSessionApprove) {
analytics.send(WcAnalyticEvents.PairButtonConnect())
onCallTerminalAction.trySend(TerminalAction.Approve(sessionForApprove))
}

View file

@ -1,6 +1,7 @@
package com.tangem.domain.tokens.model.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACCOUNT_DERIVATION_FROM
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACTION
import com.tangem.core.analytics.models.AnalyticsParam.Key.BALANCE
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
@ -58,12 +59,14 @@ sealed class TokenScreenAnalyticsEvent(
token: String,
blockchain: String,
status: String?,
derivationIndex: Int? = null,
) : TokenScreenAnalyticsEvent(
event = event,
params = buildMap {
put(TOKEN_PARAM, token)
put(BLOCKCHAIN, blockchain)
status?.let { put(STATUS, it) }
derivationIndex?.let { put(ACCOUNT_DERIVATION_FROM, it.toString()) }
},
) {
@ -71,11 +74,13 @@ sealed class TokenScreenAnalyticsEvent(
token: String,
blockchain: String,
status: String?,
derivationIndex: Int? = null,
) : ButtonWithParams(
event = "Button - Buy",
token = token,
status = status,
blockchain = blockchain,
derivationIndex = derivationIndex,
)
class ButtonSell(
@ -93,22 +98,26 @@ sealed class TokenScreenAnalyticsEvent(
token: String,
status: String,
blockchain: String,
derivationIndex: Int? = null,
) : ButtonWithParams(
event = "Button - Exchange",
token = token,
status = status,
blockchain = blockchain,
derivationIndex = derivationIndex,
)
class ButtonSend(
token: String,
status: String,
blockchain: String,
derivationIndex: Int? = null,
) : ButtonWithParams(
event = "Button - Send",
token = token,
status = status,
blockchain = blockchain,
derivationIndex = derivationIndex,
)
class ButtonReceive(

View file

@ -277,4 +277,23 @@ fun CheckDAppResult.toAnalyticVerificationStatus(): String = when (this) {
SAFE -> DAppVerificationStatus.Verified
UNSAFE -> DAppVerificationStatus.Risky
FAILED_TO_VERIFY -> DAppVerificationStatus.Unknown
}.status
}.status
sealed class WcAnalyticAccountEvents(
event: String,
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category = WC_CATEGORY_ACCOUNT_NAME, event = event, params = params) {
data class PairButtonConnect(
private val accountDerivation: Int,
) : WcAnalyticAccountEvents(
event = "Button - Connect",
params = mapOf(
"Account Derivation" to accountDerivation.toString(),
),
)
companion object {
const val WC_CATEGORY_ACCOUNT_NAME = "WalletConnect - Account"
}
}

View file

@ -16,14 +16,35 @@ sealed class WalletSettingsAnalyticEvents(
params = mapOf(STATUS to enabled.value),
)
class WalletSettingsScreenOpened : WalletSettingsAnalyticEvents(
class WalletSettingsScreenOpened(
private val accountsCount: Int?,
) : WalletSettingsAnalyticEvents(
event = "Wallet Settings Screen Opened",
params = buildMap {
if (accountsCount != null) put("Accounts Count", accountsCount.toString())
},
)
class ButtonBackup : WalletSettingsAnalyticEvents(
event = "Button - Backup",
)
class ButtonAddAccount : WalletSettingsAnalyticEvents(
event = "Button - Add Account",
)
class ButtonOpenExistingAccount : WalletSettingsAnalyticEvents(
event = "Button - Open Existing Account",
)
class ButtonArchivedAccounts : WalletSettingsAnalyticEvents(
event = "Button - Archived Accounts",
)
class LongtapAccountsOrder : WalletSettingsAnalyticEvents(
event = "Longtap - Accounts Order",
)
data class ButtonAccessCode(
private val isCodeSet: Boolean,
) : WalletSettingsAnalyticEvents(

View file

@ -57,6 +57,7 @@ interface PortfolioSelectorController {
* combine and update with your Feature data and [PortfolioFetcher.data]
*/
val isEnabled: MutableStateFlow<(UserWallet, AccountStatus) -> Boolean>
suspend fun isAccountModeSync(): Boolean
fun selectAccount(accountId: AccountId?)
fun selectedAccountWithData(portfolioFetcher: PortfolioFetcher): Flow<Pair<UserWallet, AccountStatus>?>

View file

@ -0,0 +1,100 @@
package com.tangem.features.account.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.domain.models.account.AccountName
import com.tangem.domain.models.account.CryptoPortfolioIcon
import com.tangem.features.account.AccountCreateEditComponent
sealed class AccountSettingsAnalyticEvents(
category: String = "Settings / Account",
event: String,
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category, event, params) {
class AccountSettingsScreenOpened : AccountSettingsAnalyticEvents(
event = "Account Settings Screen Opened",
)
class ButtonManageTokens : AccountSettingsAnalyticEvents(
event = "Button - Manage Tokens",
)
class ButtonArchiveAccount : AccountSettingsAnalyticEvents(
event = "Button - Archive Account",
)
class ButtonArchiveAccountConfirmation : AccountSettingsAnalyticEvents(
event = "Button - Archive Account Confirmation",
)
class ButtonCancelAccountArchivation : AccountSettingsAnalyticEvents(
event = "Button - Cancel Account Archivation",
)
class AccountArchived : AccountSettingsAnalyticEvents(
event = "Account Archived",
)
class ButtonEdit : AccountSettingsAnalyticEvents(
event = "Button - Edit",
)
class AccountEditScreenOpened : AccountSettingsAnalyticEvents(
event = "Account Edit Screen Opened",
)
class ButtonSave(
val name: AccountName,
val icon: CryptoPortfolioIcon,
) : AccountSettingsAnalyticEvents(
event = "Button - Save",
params = buildMap {
val accountName = when (name) {
is AccountName.Custom -> name.value
AccountName.DefaultMain -> "DefaultMain"
}
put("Name", accountName)
put("Color", icon.color.name)
put("Icon", icon.value.name)
},
)
class ButtonAddNewAccount(
val name: AccountName,
val icon: CryptoPortfolioIcon,
val derivationIndex: Int,
) : AccountSettingsAnalyticEvents(
event = "Button - Add New Account",
params = buildMap {
val accountName = when (name) {
is AccountName.Custom -> name.value
AccountName.DefaultMain -> "DefaultMain"
}
put("Name", accountName)
put("Color", icon.color.name)
put("Icon", icon.value.name)
put("Derivation", derivationIndex.toString())
},
)
class AccountError(
val source: Source,
val error: String,
) : AccountSettingsAnalyticEvents(
event = "Account Error",
params = buildMap {
put("Error", error)
},
)
enum class Source(val value: String) {
NEW_ACCOUNT("New Account"), EDIT("Edit"), ARCHIVE("Archive")
}
companion object {
fun AccountCreateEditComponent.Params.toAnalyticSource() = when (this) {
is AccountCreateEditComponent.Params.Create -> Source.NEW_ACCOUNT
is AccountCreateEditComponent.Params.Edit -> Source.EDIT
}
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.features.account.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
sealed class WalletSettingsAccountAnalyticEvents(
category: String = "Settings / Wallet Settings",
event: String,
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category, event, params) {
class AccountCreated : WalletSettingsAccountAnalyticEvents(
event = "Account Created",
)
class AccountRecovered : WalletSettingsAccountAnalyticEvents(
event = "Account Recovered",
)
class ArchivedAccountsScreenOpened : WalletSettingsAccountAnalyticEvents(
event = "Archived Accounts Screen Opened",
)
class ButtonRecoverAccount : WalletSettingsAccountAnalyticEvents(
event = "Button - Recover Account",
)
}

View file

@ -1,5 +1,6 @@
package com.tangem.features.account.archived
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.core.decompose.model.Model
@ -18,6 +19,7 @@ import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase
import com.tangem.domain.account.usecase.GetArchivedAccountsUseCase
import com.tangem.domain.models.account.AccountId
import com.tangem.features.account.ArchivedAccountListComponent
import com.tangem.features.account.analytics.WalletSettingsAccountAnalyticEvents
import com.tangem.features.account.archived.entity.AccountArchivedUM
import com.tangem.features.account.archived.entity.AccountArchivedUMBuilder
import com.tangem.features.account.archived.entity.AccountArchivedUMBuilder.Companion.toggleProgress
@ -40,6 +42,7 @@ internal class ArchivedAccountListModel @Inject constructor(
private val recoverCryptoPortfolioUseCase: RecoverCryptoPortfolioUseCase,
private val getArchivedAccountsUseCase: GetArchivedAccountsUseCase,
private val umBuilder: AccountArchivedUMBuilder,
private val analyticsEventHandler: AnalyticsEventHandler,
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
) : Model() {
@ -52,6 +55,7 @@ internal class ArchivedAccountListModel @Inject constructor(
private val getArchivedAccountsJob = JobHolder()
init {
analyticsEventHandler.send(WalletSettingsAccountAnalyticEvents.ArchivedAccountsScreenOpened())
getArchivedAccounts()
}
@ -96,6 +100,7 @@ internal class ArchivedAccountListModel @Inject constructor(
}
private fun recoverCryptoPortfolio(accountId: AccountId) = modelScope.launch {
analyticsEventHandler.send(WalletSettingsAccountAnalyticEvents.ButtonRecoverAccount())
uiState.update { it.toggleProgress(accountId, isLoading = true) }
val result = withContext(dispatchers.default) {
recoverCryptoPortfolioUseCase(accountId)
@ -148,6 +153,7 @@ internal class ArchivedAccountListModel @Inject constructor(
}
private fun showSuccessRecoverMessage() {
analyticsEventHandler.send(WalletSettingsAccountAnalyticEvents.AccountRecovered())
val message = resourceReference(R.string.account_recover_success_message)
messageSender.send(ToastMessage(message = message))
}

View file

@ -4,6 +4,7 @@ import androidx.annotation.StringRes
import com.tangem.common.ui.account.AccountNameUM
import com.tangem.common.ui.account.toDomain
import com.tangem.common.ui.account.toUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
@ -25,6 +26,9 @@ import com.tangem.domain.models.account.CryptoPortfolioIcon
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.account.AccountCreateEditComponent
import com.tangem.features.account.analytics.AccountSettingsAnalyticEvents
import com.tangem.features.account.analytics.AccountSettingsAnalyticEvents.Companion.toAnalyticSource
import com.tangem.features.account.analytics.WalletSettingsAccountAnalyticEvents
import com.tangem.features.account.createedit.entity.AccountCreateEditUM
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.portfolioIcon
@ -54,6 +58,7 @@ internal class AccountCreateEditModel @Inject constructor(
private val addCryptoPortfolioUseCase: AddCryptoPortfolioUseCase,
private val getUnoccupiedAccountIndexUseCase: GetUnoccupiedAccountIndexUseCase,
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val params = paramsContainer.require<AccountCreateEditComponent.Params>()
@ -65,6 +70,8 @@ internal class AccountCreateEditModel @Inject constructor(
init {
if (params is AccountCreateEditComponent.Params.Create) {
updateDerivationInfo(userWalletId = params.userWalletId)
} else {
analyticsEventHandler.send(AccountSettingsAnalyticEvents.AccountEditScreenOpened())
}
}
@ -105,6 +112,12 @@ internal class AccountCreateEditModel @Inject constructor(
val icon = state.account.portfolioIcon.toDomain()
val index = state.account.derivationInfo.index ?: return
val derivationIndex = DerivationIndex(value = index).getOrNull() ?: return
val event = AccountSettingsAnalyticEvents.ButtonAddNewAccount(
name = name,
icon = icon,
derivationIndex = derivationIndex.value,
)
analyticsEventHandler.send(event)
uiState.value = uiState.value.toggleProgress(showProgress = true)
val result = addCryptoPortfolioUseCase(
@ -118,12 +131,22 @@ internal class AccountCreateEditModel @Inject constructor(
result
.onLeft(::handleAddAccountError)
.onRight {
analyticsEventHandler.send(WalletSettingsAccountAnalyticEvents.AccountCreated())
showMessage(R.string.account_create_success_message)
router.pop()
}
}
private fun handleAddAccountError(error: AddCryptoPortfolioUseCase.Error) {
val event = AccountSettingsAnalyticEvents.AccountError(
source = params.toAnalyticSource(),
error = when (error) {
is AddCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet -> error.cause.tag
is AddCryptoPortfolioUseCase.Error.DataOperationFailed -> error.cause.message.orEmpty()
},
)
analyticsEventHandler.send(event)
val isDuplicateAccountNamesError = (error as? AddCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet)
?.cause is AccountList.Error.DuplicateAccountNames
when {
@ -141,6 +164,7 @@ internal class AccountCreateEditModel @Inject constructor(
val icon = state.account.portfolioIcon.toDomain()
val isNewName = name != params.account.accountName
val isNewIcon = icon != params.account.portfolioIcon
analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonSave(name, icon))
uiState.value = uiState.value.toggleProgress(showProgress = true)
val result = updateCryptoPortfolioUseCase(
@ -159,6 +183,15 @@ internal class AccountCreateEditModel @Inject constructor(
}
private fun handleEditAccountError(error: UpdateCryptoPortfolioUseCase.Error) {
val event = AccountSettingsAnalyticEvents.AccountError(
source = params.toAnalyticSource(),
error = when (error) {
is UpdateCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet -> error.cause.tag
is UpdateCryptoPortfolioUseCase.Error.DataOperationFailed -> error.cause.message.orEmpty()
UpdateCryptoPortfolioUseCase.Error.NothingToUpdate -> error::class.simpleName.orEmpty()
},
)
analyticsEventHandler.send(event)
val isDuplicateAccountNamesError = (error as? UpdateCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet)
?.cause is AccountList.Error.DuplicateAccountNames
when {

View file

@ -2,6 +2,7 @@ package com.tangem.features.account.details
import com.tangem.common.routing.AppRoute
import com.tangem.common.ui.account.toUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -20,6 +21,7 @@ import com.tangem.domain.models.account.Account
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.account.AccountDetailsComponent
import com.tangem.features.account.analytics.AccountSettingsAnalyticEvents
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.portfolioIcon
import com.tangem.features.account.details.entity.AccountDetailsUM
import com.tangem.features.account.details.entity.AccountDetailsUM.ArchiveMode
@ -37,6 +39,7 @@ internal class AccountDetailsModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val archiveCryptoPortfolioUseCase: ArchiveCryptoPortfolioUseCase,
singleAccountSupplier: SingleAccountSupplier,
private val analyticsEventHandler: AnalyticsEventHandler,
private val getUserWalletUseCase: GetUserWalletUseCase,
) : Model() {
@ -48,12 +51,14 @@ internal class AccountDetailsModel @Inject constructor(
private val accountId = params.account.accountId
init {
analyticsEventHandler.send(AccountSettingsAnalyticEvents.AccountSettingsScreenOpened())
singleAccountSupplier(SingleAccountProducer.Params(accountId))
.onEach { account -> uiState.update { buildUI(account) } }
.launchIn(modelScope)
}
private fun onEditAccountClick(account: Account) {
analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonEdit())
router.push(AppRoute.EditAccount(account))
}
@ -62,17 +67,21 @@ internal class AccountDetailsModel @Inject constructor(
source = AppRoute.ManageTokens.Source.SETTINGS,
portfolioId = PortfolioId(account.accountId),
)
analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonManageTokens())
router.push(route)
}
private fun onArchiveAccountClick() {
analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonArchiveAccount())
confirmArchiveDialog()
}
private fun confirmArchiveDialog() {
val secondAction = EventMessageAction(
title = resourceReference(R.string.common_cancel),
onClick = {},
onClick = {
analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonCancelAccountArchivation())
},
)
val firstAction = EventMessageAction(
title = resourceReference(R.string.account_details_archive_action),
@ -90,6 +99,7 @@ internal class AccountDetailsModel @Inject constructor(
}
private fun archiveCryptoPortfolio() = modelScope.launch {
analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonArchiveAccountConfirmation())
uiState.update { it.toggleProgress(true) }
archiveCryptoPortfolioUseCase(accountId)
.onLeft { error ->
@ -97,6 +107,7 @@ internal class AccountDetailsModel @Inject constructor(
uiState.update { it.toggleProgress(false) }
}
.onRight {
analyticsEventHandler.send(AccountSettingsAnalyticEvents.AccountArchived())
val message = resourceReference(R.string.account_archive_success_message)
messageSender.send(ToastMessage(message = message))
router.pop()
@ -104,6 +115,11 @@ internal class AccountDetailsModel @Inject constructor(
}
private fun failedArchiveDialog(error: ArchiveCryptoPortfolioUseCase.Error) {
val event = AccountSettingsAnalyticEvents.AccountError(
source = AccountSettingsAnalyticEvents.Source.ARCHIVE,
error = error.tag,
)
analyticsEventHandler.send(event)
val titleRes = R.string.common_something_went_wrong
val messageRes = when (error) {
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountListRequirementsNotMet,

View file

@ -29,6 +29,8 @@ internal class DefaultPortfolioSelectorController @Inject constructor(
override val isEnabled: MutableStateFlow<(UserWallet, AccountStatus) -> Boolean> = MutableStateFlow { _, _ -> true }
override suspend fun isAccountModeSync(): Boolean = isAccountsModeEnabledUseCase.invokeSync()
override fun selectAccount(accountId: AccountId?) {
_selectedAccount.tryEmit(accountId)
}

View file

@ -31,6 +31,19 @@ internal sealed class CustomTokenAnalyticsEvent(
),
)
class AddTokenToAnotherAccount(
currencySymbol: String,
derivationPath: String,
source: ManageTokensSource,
) : CustomTokenAnalyticsEvent(
event = "Button - Add Token To Another Account",
params = mapOf(
AnalyticsParam.Key.TOKEN_PARAM to currencySymbol,
AnalyticsParam.Key.DERIVATION to derivationPath,
AnalyticsParam.Key.SOURCE to source.analyticsName,
),
)
class NetworkSelected(networkName: String, source: ManageTokensSource) : CustomTokenAnalyticsEvent(
event = "Custom Token Network Selected",
params = mapOf(

View file

@ -2,6 +2,7 @@ package com.tangem.features.managetokens.component
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormValues
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
import com.tangem.features.managetokens.entity.customtoken.SelectedNetwork
@ -16,7 +17,7 @@ internal interface CustomTokenFormComponent : ComposableContentComponent {
val source: ManageTokensSource,
val onSelectNetworkClick: (CustomTokenFormValues) -> Unit,
val onSelectDerivationPathClick: (CustomTokenFormValues) -> Unit,
val onCurrencyAdded: () -> Unit,
val onCurrencyAdded: (currency: CryptoCurrency) -> Unit,
)
interface Factory : ComponentFactory<Params, CustomTokenFormComponent>

View file

@ -15,6 +15,7 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
import com.tangem.features.managetokens.component.AddCustomTokenComponent
@ -232,7 +233,16 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
}
}
private fun dismissAndNotify() {
private fun dismissAndNotify(currency: CryptoCurrency) {
val account = addedToAccount
if (account is Account.CryptoPortfolio && !account.isMainAccount) {
val event = CustomTokenAnalyticsEvent.AddTokenToAnotherAccount(
currencySymbol = currency.symbol,
derivationPath = currency.network.derivationPath.value.orEmpty(),
source = params.source,
)
analyticsEventHandler.send(event)
}
params.onCurrencyAdded(addedToAccount)
dismiss()
}

View file

@ -364,7 +364,7 @@ internal class CustomTokenFormModel @Inject constructor(
return@resource
}
params.onCurrencyAdded()
params.onCurrencyAdded(currency)
}
private fun selectNetwork() {

View file

@ -4,6 +4,7 @@ import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.popToFirst
import com.arkivanov.decompose.router.stack.pushNew
import com.arkivanov.decompose.router.stack.replaceAll
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -47,6 +48,7 @@ internal class AddToPortfolioModel @Inject constructor(
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCaseV2,
private val getTokenMarketCryptoCurrency: GetTokenMarketCryptoCurrency,
private val messageSender: UiMessageSender,
private val analyticsEventHandler: AnalyticsEventHandler,
val portfolioSelectorController: PortfolioSelectorController,
) : Model(),
ChooseNetworkComponent.Callbacks by callbackDelegate,
@ -93,6 +95,7 @@ internal class AddToPortfolioModel @Inject constructor(
.map { it.availableToAddData }
.distinctUntilChanged()
.stateIn(this)
val isAccountMode = portfolioSelectorController.isAccountModeSync()
// use snapshot data, looks like we dont need to remap at runtime
val data = featureDataFlow.value
@ -117,6 +120,7 @@ internal class AddToPortfolioModel @Inject constructor(
// force select a portfolio, triggers [selectedPortfolio]
portfolioSelectorController.selectAccount(accountId)
} else {
logAccountSelector(isAccountMode)
navigation.replaceAll(AddToPortfolioRoutes.PortfolioSelector)
}
@ -165,6 +169,7 @@ internal class AddToPortfolioModel @Inject constructor(
.onEach {
middleNavigationJob?.cancel()
middleNavigationJob = changePortfolioNavigationFlow(data).launchIn(this)
logAccountSelector(isAccountMode)
navigation.pushNew(AddToPortfolioRoutes.PortfolioSelector)
}
.launchIn(this)
@ -194,6 +199,12 @@ internal class AddToPortfolioModel @Inject constructor(
.launchIn(modelScope)
}
private fun logAccountSelector(isAccountMode: Boolean) {
if (isAccountMode) {
analyticsEventHandler.send(eventBuilder.popupToChooseAccount())
}
}
private fun changeNetworkNavigationFlow(): Flow<SelectedNetwork> {
return setupNetworkFlow(selectedPortfolio)
.onEach { newNetwork ->
@ -260,6 +271,7 @@ internal class AddToPortfolioModel @Inject constructor(
data.availableToAddWallets[selectedAccountId.userWalletId] ?: return@combine null
val availableToAddAccount =
availableToAddWallets.availableToAddAccounts[selectedAccountId] ?: return@combine null
if (!isAccountMode) analyticsEventHandler.send(eventBuilder.addToPortfolioWalletChanged())
SelectedPortfolio(
isAccountMode = isAccountMode,
userWallet = availableToAddWallets.userWallet,

View file

@ -1,5 +1,6 @@
package com.tangem.features.markets.portfolio.add.impl.model
import com.tangem.common.ui.addtoken.AddTokenUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -10,13 +11,13 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.ToastMessage
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.models.account.Account
import com.tangem.domain.wallets.usecase.ColdWalletAndHasMissedDerivationsUseCase
import com.tangem.features.markets.impl.R
import com.tangem.features.markets.portfolio.add.api.SelectedNetwork
import com.tangem.features.markets.portfolio.add.api.SelectedPortfolio
import com.tangem.features.markets.portfolio.add.impl.AddTokenComponent
import com.tangem.features.markets.portfolio.add.impl.model.AddTokenUiBuilder.Companion.toggleProgress
import com.tangem.common.ui.addtoken.AddTokenUM
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
@ -74,7 +75,8 @@ internal class AddTokenModel @Inject constructor(
analyticsEventHandler.send(analyticsEventBuilder.addToPortfolioContinue(blockchainNames))
val cryptoCurrency = selectedNetwork.cryptoCurrency
val accountId = selectedPortfolio.account.account.account.accountId
val account = selectedPortfolio.account.account.account
val accountId = account.accountId
manageCryptoCurrenciesUseCase(accountId = accountId, add = cryptoCurrency)
.onLeft {
processError(error = it)
@ -90,6 +92,11 @@ internal class AddTokenModel @Inject constructor(
if (status == null) {
processError(error = null)
} else {
when (account) {
is Account.CryptoPortfolio -> if (!account.isMainAccount) {
analyticsEventHandler.send(analyticsEventBuilder.addToNotMainAccount())
}
}
params.callbacks.onTokenAdded(status.status)
}
uiState.value = um.toggleProgress(false)

View file

@ -62,10 +62,7 @@ internal class TokenActionsModel @Inject constructor(
)
private fun handledQuickAction(handledAction: HandledQuickAction) {
val event = analyticsEventBuilder.quickActionClick(
actionUM = handledAction.action,
blockchainName = handledAction.cryptoCurrencyData.status.currency.network.name,
)
val event = analyticsEventBuilder.getTokenActionClick(actionUM = handledAction.action)
analyticsEventHandler.send(event)
val isReceive = handledAction.action == TokenActionsBSContentUM.Action.Receive
if (!isReceive) return

View file

@ -1,5 +1,6 @@
package com.tangem.features.markets.portfolio.add.impl.model
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
@ -15,6 +16,7 @@ import javax.inject.Inject
@ModelScoped
internal class TokenActionsUiBuilder @Inject constructor(
paramsContainer: ParamsContainer,
private val analyticsEventHandler: AnalyticsEventHandler,
) {
private val params = paramsContainer.require<TokenActionsComponent.Params>()
@ -32,7 +34,10 @@ internal class TokenActionsUiBuilder @Inject constructor(
)
return TokenActionsUM(
token = tokenUM,
onLaterClick = { params.callbacks.onLaterClick() },
onLaterClick = {
analyticsEventHandler.send(params.eventBuilder.getTokenLater())
params.callbacks.onLaterClick()
},
quickActions = PortfolioTokenUMConverter.quickActions(data, tokenActionsHandler),
)
}

View file

@ -21,6 +21,14 @@ internal class PortfolioAnalyticsEvent(
),
)
fun popupToChooseAccount() = PortfolioAnalyticsEvent(
event = "Popup to choose account",
)
fun addToNotMainAccount() = PortfolioAnalyticsEvent(
event = "Button - Add (token not to main Account)",
)
fun addToPortfolioWalletChanged() = PortfolioAnalyticsEvent(event = "Wallet Selected")
fun addToPortfolioContinue(blockchainNames: List<String>) = PortfolioAnalyticsEvent(
@ -47,5 +55,19 @@ internal class PortfolioAnalyticsEvent(
put("blockchain", blockchainName)
},
)
fun getTokenActionClick(actionUM: TokenActionsBSContentUM.Action) = PortfolioAnalyticsEvent(
event = when (actionUM) {
TokenActionsBSContentUM.Action.Buy -> "Popup Get token - Button Buy"
TokenActionsBSContentUM.Action.Receive -> "Popup Get token - Button Receive"
TokenActionsBSContentUM.Action.Exchange -> "Popup Get token - Button Exchange"
TokenActionsBSContentUM.Action.Stake -> "Popup Get token - Button Stake"
else -> "error"
},
)
fun getTokenLater() = PortfolioAnalyticsEvent(
event = "Popup Get token - Button Later",
)
}
}

View file

@ -193,7 +193,10 @@ internal class MarketsPortfolioModel @Inject constructor(
NewAddToPortfolioManager.State.NothingToAdd -> AddButtonState.Unavailable
}
},
onAddClick = { bottomSheetNavigation.activate(MarketsPortfolioRoute.AddToPortfolio) },
onAddClick = {
analyticsEventHandler.send(analyticsEventBuilder.addToPortfolioClicked())
bottomSheetNavigation.activate(MarketsPortfolioRoute.AddToPortfolio)
},
)
newMarketsPortfolioDelegate.combineData()
.onEach { _state.value = it }

View file

@ -12,4 +12,10 @@ sealed class ReferralEvents(event: String) : AnalyticsEvent(REFERRAL_PROGRAM_CAT
class ParticipateSuccessful : ReferralEvents(event = "Participate Successful")
}
private const val REFERRAL_PROGRAM_CATEGORY = "Referral Program"
sealed class ReferralEventsAccounts(event: String) : AnalyticsEvent(REFERRAL_PROGRAM_ACCOUNT_CATEGORY, event) {
class ListChooseAccount : ReferralEventsAccounts(event = "List - choose account")
}
private const val REFERRAL_PROGRAM_CATEGORY = "Referral Program"
private const val REFERRAL_PROGRAM_ACCOUNT_CATEGORY = "Referral program - Account"

View file

@ -30,6 +30,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.referral.analytics.ReferralEvents
import com.tangem.feature.referral.analytics.ReferralEventsAccounts
import com.tangem.feature.referral.api.ReferralComponent
import com.tangem.feature.referral.domain.ReferralInteractor
import com.tangem.feature.referral.domain.errors.ReferralError
@ -116,6 +117,7 @@ internal class ReferralModel @Inject constructor(
private fun combineAccountUI(referralData: ReferralData): Flow<AccountAward?> = combine(
flow = portfolioSelectorController.selectedAccountWithData(portfolioFetcher)
.onEach { analyticsEventHandler.send(ReferralEventsAccounts.ListChooseAccount()) }
.onEach { bottomSheetNavigation.dismiss() },
flow2 = getBalanceHidingSettingsUseCase.isBalanceHidden(),
flow3 = getSelectedAppCurrencyUseCase.invokeOrDefault(),

View file

@ -51,16 +51,29 @@ sealed class CommonSendAnalyticEvents(
),
)
@Suppress("NullableToStringCall")
/** Confirmation screen opened */
data class ConfirmationScreenOpened(
val categoryName: String,
val source: CommonSendSource,
val fromDerivationIndex: Int?,
val toDerivationIndex: Int?,
val sendBlockchain: String,
val sendToken: String,
) : CommonSendAnalyticEvents(
category = categoryName,
event = "Confirm Screen Opened",
params = mapOf(
SOURCE to source.analyticsName,
),
params = buildMap {
put(SOURCE, source.analyticsName)
put("Token", sendToken)
put("Blockchain", sendBlockchain)
if (fromDerivationIndex != null || toDerivationIndex != null) {
put(
"Account Derivation From or To (optional)",
"$fromDerivationIndex, $toDerivationIndex",
)
}
},
)
/** If transaction delays notification is present */

View file

@ -88,10 +88,17 @@ internal class DefaultSendComponent @AssistedInject constructor(
componentScope.launch {
when (val activeComponent = stack.active.instance) {
is SendConfirmComponent -> {
val fromCurrency = params.currency
val fromDerivationIndex = model.accountFlow.value?.derivationIndex?.value
.takeIf { model.isAccountModeFlow.value }
analyticsEventHandler.send(
CommonSendAnalyticEvents.ConfirmationScreenOpened(
categoryName = model.analyticCategoryName,
source = model.analyticsSendSource,
sendBlockchain = fromCurrency.network.name,
sendToken = fromCurrency.symbol,
fromDerivationIndex = fromDerivationIndex,
toDerivationIndex = null,
),
)
if (model.currentRoute.value.isEditMode) {

View file

@ -2,6 +2,7 @@ package com.tangem.features.send.v2.send.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACCOUNT_DERIVATION_FROM
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
import com.tangem.core.analytics.models.AnalyticsParam.Key.ENS_ADDRESS
import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE
@ -25,18 +26,21 @@ internal sealed class SendAnalyticEvents(
val blockchain: String,
val isNonceNotEmpty: Boolean,
private val ensStatus: AnalyticsParam.EmptyFull,
val derivationIndex: Int?,
) : SendAnalyticEvents(
event = "Transaction Sent Screen Opened",
params = mapOf(
TOKEN_PARAM to token,
FEE_TYPE to feeType.value,
BLOCKCHAIN to blockchain,
NONCE to isNonceNotEmpty.toString().capitalize(),
ENS_ADDRESS to when (ensStatus) {
params = buildMap {
put(TOKEN_PARAM, token)
put(FEE_TYPE, feeType.value)
put(BLOCKCHAIN, blockchain)
if (derivationIndex != null) put(ACCOUNT_DERIVATION_FROM, derivationIndex.toString())
put(NONCE, isNonceNotEmpty.toString().capitalize())
val ensAddress = when (ensStatus) {
AnalyticsParam.EmptyFull.Empty -> false.toString().capitalize()
AnalyticsParam.EmptyFull.Full -> true.toString().capitalize()
},
),
}
put(ENS_ADDRESS, ensAddress)
},
)
data class ConvertTokenButtonClicked(

View file

@ -4,6 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.send.v2.api.entity.FeeNonce
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
@ -17,10 +18,12 @@ internal class SendAnalyticHelper @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
) {
fun sendSuccessAnalytics(cryptoCurrency: CryptoCurrency, sendUM: SendUM) {
fun sendSuccessAnalytics(cryptoCurrency: CryptoCurrency, sendUM: SendUM, account: Account.CryptoPortfolio?) {
val destinationUM = sendUM.destinationUM as? DestinationUM.Content
val feeSelectorUM = sendUM.feeSelectorUM as? FeeSelectorUM.Content ?: return
val feeType = feeSelectorUM.toAnalyticType()
val isNotMainAccount = account != null && !account.isMainAccount
val derivationIndex = if (isNotMainAccount) account.derivationIndex.value else null
analyticsEventHandler.send(
SendAnalyticEvents.TransactionScreenOpened(
token = cryptoCurrency.symbol,
@ -28,6 +31,7 @@ internal class SendAnalyticHelper @Inject constructor(
blockchain = cryptoCurrency.network.name,
isNonceNotEmpty = feeSelectorUM.feeNonce is FeeNonce.Nonce,
ensStatus = getEnsStatus(sendUM),
derivationIndex = derivationIndex,
),
)
analyticsEventHandler.send(

View file

@ -416,7 +416,11 @@ internal class SendConfirmModel @Inject constructor(
updateTransactionStatus(txData)
addTokenToWalletIfNeeded()
sendBalanceUpdater.scheduleUpdates()
sendAnalyticHelper.sendSuccessAnalytics(cryptoCurrency, uiState.value)
sendAnalyticHelper.sendSuccessAnalytics(
cryptoCurrency = cryptoCurrency,
sendUM = uiState.value,
account = params.accountFlow.value,
)
params.callback.onResult(uiState.value)
params.onSendTransaction()
},

View file

@ -79,10 +79,17 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
componentScope.launch {
when (val activeComponent = stack.active.instance) {
is NFTSendConfirmComponent -> {
val fromCurrency = model.cryptoCurrency
val fromDerivationIndex = model.account?.derivationIndex?.value
.takeIf { model.isAccountsMode }
analyticsEventHandler.send(
CommonSendAnalyticEvents.ConfirmationScreenOpened(
categoryName = analyticsCategoryName,
source = analyticsSendSource,
sendBlockchain = fromCurrency.network.name,
sendToken = fromCurrency.symbol,
fromDerivationIndex = fromDerivationIndex,
toDerivationIndex = null,
),
)
if (model.currentRouteFlow.value.isEditMode) {

View file

@ -109,10 +109,17 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
activeComponent.updateState(model.uiState.value.destinationUM)
}
is SendWithSwapConfirmComponent -> {
val fromCurrency = params.currency
val fromDerivationIndex = model.accountFlow.value?.derivationIndex?.value
.takeIf { model.isAccountModeFlow.value }
analyticsEventHandler.send(
CommonSendAnalyticEvents.ConfirmationScreenOpened(
categoryName = model.analyticCategoryName,
source = model.analyticsSendSource,
sendBlockchain = fromCurrency.network.name,
sendToken = fromCurrency.symbol,
fromDerivationIndex = fromDerivationIndex,
toDerivationIndex = null,
),
)
if (model.currentRoute.value.isEditMode) {

View file

@ -62,6 +62,7 @@ sealed class SwapEvents(
class ButtonSwipeClicked : SwapEvents(event = "Button - Swipe")
@Suppress("NullableToStringCall")
data class SwapInProgressScreen(
val provider: SwapProvider,
val commission: FeeType, // Market / Fast
@ -69,6 +70,8 @@ sealed class SwapEvents(
val receiveBlockchain: String,
val sendToken: String,
val receiveToken: String,
val fromDerivationIndex: Int?,
val toDerivationIndex: Int?,
) : SwapEvents(
event = "Swap in Progress Screen Opened",
params = mapOf(
@ -78,6 +81,7 @@ sealed class SwapEvents(
"Receive Token" to receiveToken,
"Send Blockchain" to sendBlockchain,
"Receive Blockchain" to receiveBlockchain,
"Account Derivation From or To (optional)" to "$fromDerivationIndex, $toDerivationIndex",
),
)

View file

@ -847,6 +847,8 @@ internal class SwapModel @Inject constructor(
val fee = dataState.selectedFee?.feeType ?: return
val fromCurrency = dataState.fromCryptoCurrency?.currency ?: return
val toCurrency = dataState.toCryptoCurrency?.currency ?: return
val fromDerivationIndex = dataState.fromAccount?.derivationIndex?.value
val toDerivationIndex = dataState.toAccount?.derivationIndex?.value
analyticsEventHandler.send(
SwapEvents.SwapInProgressScreen(
@ -856,6 +858,8 @@ internal class SwapModel @Inject constructor(
receiveBlockchain = toCurrency.network.name,
sendToken = fromCurrency.symbol,
receiveToken = toCurrency.symbol,
fromDerivationIndex = fromDerivationIndex,
toDerivationIndex = toDerivationIndex,
),
)
}

View file

@ -532,6 +532,7 @@ internal class TokenDetailsModel @Inject constructor(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
status = unavailabilityReason.toReasonAnalyticsText(),
derivationIndex = getAccountIndexOrNull(),
),
)
@ -557,6 +558,7 @@ internal class TokenDetailsModel @Inject constructor(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
status = null,
derivationIndex = getAccountIndexOrNull(),
),
)
router.openTokenDetails(userWalletId = userWalletId, currency = cryptoCurrency)
@ -578,6 +580,7 @@ internal class TokenDetailsModel @Inject constructor(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
status = unavailabilityReason.toReasonAnalyticsText(),
derivationIndex = getAccountIndexOrNull(),
),
)
@ -698,6 +701,7 @@ internal class TokenDetailsModel @Inject constructor(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
status = unavailabilityReason.toReasonAnalyticsText(),
derivationIndex = getAccountIndexOrNull(),
),
)
@ -766,6 +770,12 @@ internal class TokenDetailsModel @Inject constructor(
showErrorIfDemoModeOrElse(action = ::openExplorer)
}
private fun getAccountIndexOrNull(): Int? {
val account = account
val isNotMainAccount = account != null && !account.isMainAccount
return if (isNotMainAccount) account.derivationIndex.value else null
}
private fun openExplorer() {
val currencyStatus = cryptoCurrencyStatus ?: return

View file

@ -25,6 +25,8 @@ import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.message.bottomSheetMessage
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.models.PortfolioId
@ -88,6 +90,8 @@ internal class WalletSettingsModel @Inject constructor(
private val dismissUpgradeWalletNotificationUseCase: DismissUpgradeWalletNotificationUseCase,
private val unlockHotWalletContextualUseCase: UnlockHotWalletContextualUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val accountListSortingSaver: AccountListSortingSaver,
) : Model() {
@ -115,7 +119,18 @@ internal class WalletSettingsModel @Inject constructor(
init {
getUserWalletUseCase.invoke(params.userWalletId).onRight { wallet ->
trackingContextProxy.addContext(wallet)
analyticsEventHandler.send(WalletSettingsAnalyticEvents.WalletSettingsScreenOpened())
modelScope.launch {
val accountsCount = if (isAccountsModeEnabledUseCase.invokeSync()) {
singleAccountListSupplier(wallet.walletId)
.first()
.accounts
.size
} else {
null
}
val event = WalletSettingsAnalyticEvents.WalletSettingsScreenOpened(accountsCount)
analyticsEventHandler.send(event)
}
}
fun combineUI(wallet: UserWallet) = combine(

View file

@ -2,6 +2,7 @@ package com.tangem.feature.walletsettings.utils
import com.tangem.common.routing.AppRoute
import com.tangem.common.ui.account.AccountPortfolioItemUMConverter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
@ -24,6 +25,7 @@ import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.domain.wallets.extension.isAccountsSupported
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
import com.tangem.feature.walletsettings.entity.WalletSettingsAccountsUM
@ -46,6 +48,7 @@ internal class AccountItemsDelegate @Inject constructor(
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val accountListSortingSaver: AccountListSortingSaver,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val analyticsEventHandler: AnalyticsEventHandler,
) {
private val userWalletId = paramsContainer.require<WalletSettingsComponent.Params>().userWalletId
@ -72,7 +75,10 @@ internal class AccountItemsDelegate @Inject constructor(
): List<WalletSettingsAccountsUM> = buildList {
fun AccountStatus.CryptoPortfolio.mapCryptoPortfolio(): WalletSettingsAccountsUM {
val accountItemUM = AccountPortfolioItemUMConverter(
onClick = { openAccountDetails(this.account) },
onClick = {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonOpenExistingAccount())
openAccountDetails(this.account)
},
appCurrency = appCurrency,
accountBalance = this.tokenList.totalFiatBalance,
isBalanceHidden = isBalanceHidden,
@ -107,14 +113,22 @@ internal class AccountItemsDelegate @Inject constructor(
title = resourceReference(R.string.account_form_title_create),
isAddAccountEnabled = isAddAccountEnabled,
onAddAccountClick = {
if (isAddAccountEnabled) openAddAccount(userWalletId) else canNotAddAccountDialog()
if (isAddAccountEnabled) {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonAddAccount())
openAddAccount(userWalletId)
} else {
canNotAddAccountDialog()
}
},
),
archivedAccounts = if (isArchivedAccountsEnabled) {
BlockUM(
text = resourceReference(R.string.account_archived_accounts),
iconRes = R.drawable.ic_archive_24,
onClick = { openArchivedAccounts(userWalletId) },
onClick = {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonArchivedAccounts())
openArchivedAccounts(userWalletId)
},
)
} else {
null

View file

@ -1,7 +1,9 @@
package com.tangem.feature.walletsettings.utils
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.account.usecase.ApplyAccountListSortingUseCase
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
@ -24,6 +26,7 @@ import kotlin.time.Duration.Companion.seconds
@OptIn(FlowPreview::class)
internal class AccountListSortingSaver @Inject constructor(
private val applyAccountListSortingUseCase: ApplyAccountListSortingUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
dispatchers: CoroutineDispatcherProvider,
) {
@ -41,6 +44,9 @@ internal class AccountListSortingSaver @Inject constructor(
applyAccountListSortingUseCase.invoke(accountIds).onLeft {
Timber.e("Error while saving account list sorting: $it")
}
.onRight {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.LongtapAccountsOrder())
}
}
.launchIn(coroutineScope)
}

View file

@ -11,6 +11,8 @@ import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.UserWallet
@ -99,6 +101,8 @@ internal class WalletModel @Inject constructor(
private val accountsFeatureToggles: AccountsFeatureToggles,
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
private val trackingContextProxy: TrackingContextProxy,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val feedFeatureToggle: FeedFeatureToggle,
val screenLifecycleProvider: ScreenLifecycleProvider,
val innerWalletRouter: InnerWalletRouter,
@ -293,8 +297,19 @@ internal class WalletModel @Inject constructor(
modelScope.launch {
val hasMobileWallet = userWalletsListRepository.userWalletsSync()
.any { it is UserWallet.Hot }
val accountsCount = if (isAccountsModeEnabledUseCase.invokeSync()) {
singleAccountListSupplier(selectedWallet.walletId)
.first()
.accounts
.size
} else {
null
}
analyticsEventsHandler.send(
WalletScreenAnalyticsEvent.MainScreen.ScreenOpened(hasMobileWallet),
WalletScreenAnalyticsEvent.MainScreen.ScreenOpened(
hasMobileWallet = hasMobileWallet,
accountsCount = accountsCount,
),
)
}
}

View file

@ -196,11 +196,13 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
override fun onAccountExpandClick(account: Account) {
val userWalletId = stateHolder.getSelectedWalletId()
analyticsEventHandler.send(MainScreenAnalyticsEvent.AccountShowTokens())
accountDependencies.expandedAccountsHolder.expandAccount(userWalletId, account.accountId)
}
override fun onAccountCollapseClick(account: Account) {
val userWalletId = stateHolder.getSelectedWalletId()
analyticsEventHandler.send(MainScreenAnalyticsEvent.AccountHideTokens())
accountDependencies.expandedAccountsHolder.collapseAccount(userWalletId, account.accountId)
}

View file

@ -57,9 +57,13 @@ sealed class WalletScreenAnalyticsEvent {
data class ScreenOpened(
private val hasMobileWallet: Boolean,
private val accountsCount: Int?,
) : MainScreen(
event = "Screen opened",
params = mapOf("Mobile Wallet" to if (hasMobileWallet) "Yes" else "No"),
params = buildMap {
put("Mobile Wallet", if (hasMobileWallet) "Yes" else "No")
if (accountsCount != null) put("Accounts Count", accountsCount.toString())
},
)
data class NoticeFinishActivation(

View file

@ -31,6 +31,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.walletconnect.WcAnalyticAccountEvents
import com.tangem.domain.walletconnect.WcAnalyticEvents
import com.tangem.domain.walletconnect.model.WcPairError
import com.tangem.domain.walletconnect.model.WcPairError.Unknown
@ -319,6 +320,17 @@ internal class WcPairModel @Inject constructor(
val selectedPortfolio = selectedPortfolio.replayCache.firstOrNull()
val wallet = selectedPortfolio?.first ?: selectedUserWalletFlow.value
val account = selectedPortfolio?.second?.account
modelScope.launch {
if (selectorController.isAccountModeSync() && account != null) {
val derivationIndex = when (account) {
is Account.CryptoPortfolio -> account.derivationIndex.value
}
analytics.send(WcAnalyticAccountEvents.PairButtonConnect(derivationIndex))
} else {
analytics.send(WcAnalyticEvents.PairButtonConnect())
}
}
wcPairUseCase.approve(
WcSessionApprove(
wallet = wallet,