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

@ -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 }