Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-27 19:43:34 +03:00
parent 76e17fc21f
commit f9cc3decfb
12 changed files with 353 additions and 144 deletions

View file

@ -95,7 +95,7 @@ sealed class AnalyticsParam {
}
companion object Key {
const val BLOCKCHAIN = "blockchain"
const val BLOCKCHAIN = "Blockchain"
const val TOKEN = "Token"
const val SOURCE = "Source"
const val BALANCE = "Balance"

View file

@ -10,6 +10,8 @@ import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRA
import com.tangem.datasource.api.express.models.response.Asset
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.exchange.ExchangeableState
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase
@ -97,10 +99,11 @@ internal class DefaultRampManager(
}
}
override suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean {
return runCatching { getExchangeableFlag(userWalletId, cryptoCurrency) && !cryptoCurrency.isCustom }
.getOrNull()
?: false
override suspend fun availableForSwap(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): ExchangeableState {
return runCatching { getExchangeableState(userWalletId, cryptoCurrency) }.getOrNull() ?: ExchangeableState.Error
}
override fun getBuyInitializationStatus(): Flow<ExchangeServiceInitializationStatus> {
@ -127,14 +130,27 @@ internal class DefaultRampManager(
return expressServiceLoader.getInitializationStatus(userWalletId)
}
private suspend fun getExchangeableFlag(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean {
private suspend fun getExchangeableState(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): ExchangeableState {
return withContext(dispatchers.io) {
val asset = expressServiceLoader.getInitializationStatus(userWalletId)
.value
.getOrNull()
?.find { cryptoCurrency.findAssetPredicate(it) }
when (val asset = expressServiceLoader.getInitializationStatus(userWalletId).value) {
is Lce.Error -> ExchangeableState.Error
is Lce.Loading -> ExchangeableState.Loading
is Lce.Content -> {
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) }
foundAsset?.exchangeAvailable?.toExchangeableState() ?: ExchangeableState.AssetNotFound
}
}
}
}
asset?.exchangeAvailable ?: false
private fun Boolean.toExchangeableState(): ExchangeableState {
return if (this) {
ExchangeableState.Exchangeable
} else {
ExchangeableState.NotExchangeable
}
}

View file

@ -34,7 +34,11 @@ fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference {
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
)
}
ScenarioUnavailabilityReason.Unreachable -> {
is ScenarioUnavailabilityReason.TokenNoQuotes,
ScenarioUnavailabilityReason.SingleWallet,
is ScenarioUnavailabilityReason.ExpressUnreachable,
ScenarioUnavailabilityReason.Unreachable,
-> {
resourceReference(
id = R.string.token_button_unavailability_generic_description,
)
@ -45,6 +49,21 @@ fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference {
ScenarioUnavailabilityReason.UsedOutdatedData -> {
resourceReference(id = R.string.token_button_unavailability_reason_out_of_date_balance)
}
is ScenarioUnavailabilityReason.AssetNotFound -> resourceReference(
id = R.string.token_button_unavailability_reason_not_exchangeable,
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
)
is ScenarioUnavailabilityReason.CustomToken -> {
resourceReference(
id = R.string
.token_button_unavailability_reason_custom_token,
)
}
is ScenarioUnavailabilityReason.DataLoading,
is ScenarioUnavailabilityReason.ExpressLoading,
-> {
resourceReference(id = R.string.token_button_unavailability_reason_loading)
}
ScenarioUnavailabilityReason.None -> {
throw IllegalArgumentException("The unavailability reason must be other than None")
}

View file

@ -175,7 +175,7 @@ sealed class AnalyticsParam {
}
companion object Key {
const val BLOCKCHAIN = "blockchain"
const val BLOCKCHAIN = "Blockchain"
const val TOKEN_PARAM = "Token"
const val SOURCE = "Source"
const val BALANCE = "Balance"
@ -210,5 +210,6 @@ sealed class AnalyticsParam {
const val RESIDENCE = "Residence"
const val PAYMENT_METHOD = "Payment Method"
const val WATCHED = "Watched"
const val ACTION = "Action"
}
}

View file

@ -0,0 +1,9 @@
package com.tangem.domain.exchange
sealed interface ExchangeableState {
data object Exchangeable : ExchangeableState
data object NotExchangeable : ExchangeableState
data object Loading : ExchangeableState
data object Error : ExchangeableState
data object AssetNotFound : ExchangeableState
}

View file

@ -31,7 +31,7 @@ interface RampStateManager {
status: CryptoCurrencyStatus,
): Either<ScenarioUnavailabilityReason, Unit>
suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean
suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): ExchangeableState
suspend fun fetchBuyServiceData()

View file

@ -5,6 +5,8 @@ sealed class ScenarioUnavailabilityReason {
data object UsedOutdatedData : ScenarioUnavailabilityReason()
data object DataLoading : ScenarioUnavailabilityReason()
// staking-specific
data class StakingUnavailable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
@ -19,8 +21,32 @@ sealed class ScenarioUnavailabilityReason {
data class BuyUnavailable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
// swap-specific
/**
* False for this token in asset request
*/
data class NotExchangeable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
data class CustomToken(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
data class TokenNoQuotes(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
data object SingleWallet : ScenarioUnavailabilityReason()
/**
* Express request failed
*/
data class ExpressUnreachable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
/**
* Assets still loading
*/
data class ExpressLoading(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
/**
* No token in asset list returned from express
*/
data class AssetNotFound(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
// sell-specific
data class NotSupportedBySellService(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()

View file

@ -1,6 +1,10 @@
package com.tangem.domain.tokens.model.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACTION
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
/**
@ -34,29 +38,89 @@ sealed class TokenScreenAnalyticsEvent(
params = mapOf("Token" to token),
)
class ButtonBuy(token: String) : TokenScreenAnalyticsEvent(
event = "Button - Buy",
params = mapOf("Token" to token),
)
sealed class ButtonWithParams(
event: String,
token: String,
blockchain: String,
status: String?,
) : TokenScreenAnalyticsEvent(
event = event,
params = buildMap {
put(TOKEN_PARAM, token)
put(BLOCKCHAIN, blockchain)
status?.let { put(STATUS, it) }
},
) {
class ButtonSell(token: String) : TokenScreenAnalyticsEvent(
event = "Button - Sell",
params = mapOf("Token" to token),
)
class ButtonBuy(
token: String,
blockchain: String,
status: String?,
) : ButtonWithParams(
event = "Button - Buy",
token = token,
status = status,
blockchain = blockchain,
)
class ButtonExchange(token: String) : TokenScreenAnalyticsEvent(
event = "Button - Exchange",
params = mapOf("Token" to token),
)
class ButtonSell(
token: String,
status: String,
blockchain: String,
) : ButtonWithParams(
event = "Button - Sell",
token = token,
status = status,
blockchain = blockchain,
)
class ButtonSend(token: String) : TokenScreenAnalyticsEvent(
event = "Button - Send",
params = mapOf("Token" to token),
)
class ButtonExchange(
token: String,
status: String,
blockchain: String,
) : ButtonWithParams(
event = "Button - Exchange",
token = token,
status = status,
blockchain = blockchain,
)
class ButtonReceive(token: String) : TokenScreenAnalyticsEvent(
event = "Button - Receive",
params = mapOf("Token" to token),
class ButtonSend(
token: String,
status: String,
blockchain: String,
) : ButtonWithParams(
event = "Button - Send",
token = token,
status = status,
blockchain = blockchain,
)
class ButtonReceive(
token: String,
status: String,
blockchain: String,
) : ButtonWithParams(
event = "Button - Receive",
token = token,
status = status,
blockchain = blockchain,
)
}
class ActionButtonDisabled(
token: String,
status: String,
blockchain: String,
action: String,
) : TokenScreenAnalyticsEvent(
event = "Action Button Disabled",
params = mapOf(
TOKEN_PARAM to token,
STATUS to status,
BLOCKCHAIN to blockchain,
ACTION to action,
),
)
class ButtonCopyAddress(token: String) : TokenScreenAnalyticsEvent(
@ -89,30 +153,20 @@ sealed class TokenScreenAnalyticsEvent(
params = mapOf("Token" to token),
)
class NoticeActionInactive(token: String, tokenAction: TokenAction, reason: String) : TokenScreenAnalyticsEvent(
event = "Notice - Action Inactive",
params = buildMap {
put("Token", token)
put("Action", tokenAction.action)
if (reason.isNotEmpty()) {
put("Reason", reason)
}
},
) {
sealed class TokenAction(val action: String) {
data object BuyAction : TokenAction("Buy")
data object SellAction : TokenAction("Sell")
data object SwapAction : TokenAction("Swap")
data object SendAction : TokenAction("Send")
data object ReceiveAction : TokenAction("Receive")
}
}
companion object {
const val AVAILABLE = "Available"
private const val UNAVAILABLE = "Unavailable"
private const val EMPTY = "Empty"
private const val PENDING = "Pending"
private const val CACHING = "Caching"
private const val CUSTOM_TOKEN = "Custom Token"
private const val BLOCKCHAIN_UNREACHABLE = "Blockchain Unreachable"
private const val ASSET_NOT_FOUND = "Asset NotFound"
private const val ASSET_LOADING = "Assets Loading"
private const val ASSETS_ERROR = "Assets Error"
private const val NO_QUOTE = "Quotes Unavailable"
private const val SINGLE_WALLET = "Single Wallet"
private const val LOADING = "Loading"
fun ScenarioUnavailabilityReason.toReasonAnalyticsText(): String {
return when (this) {
@ -125,9 +179,15 @@ sealed class TokenScreenAnalyticsEvent(
is ScenarioUnavailabilityReason.EmptyBalance -> EMPTY
is ScenarioUnavailabilityReason.PendingTransaction -> PENDING
ScenarioUnavailabilityReason.UsedOutdatedData -> CACHING
ScenarioUnavailabilityReason.None,
ScenarioUnavailabilityReason.Unreachable,
-> ""
ScenarioUnavailabilityReason.None -> AVAILABLE
ScenarioUnavailabilityReason.Unreachable -> BLOCKCHAIN_UNREACHABLE
is ScenarioUnavailabilityReason.AssetNotFound -> ASSET_NOT_FOUND
is ScenarioUnavailabilityReason.CustomToken -> CUSTOM_TOKEN
is ScenarioUnavailabilityReason.ExpressLoading -> ASSET_LOADING
is ScenarioUnavailabilityReason.ExpressUnreachable -> ASSETS_ERROR
ScenarioUnavailabilityReason.SingleWallet -> SINGLE_WALLET
is ScenarioUnavailabilityReason.TokenNoQuotes -> NO_QUOTE
ScenarioUnavailabilityReason.DataLoading -> LOADING
}
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.domain.tokens
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.exchange.ExchangeableState
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.StatusSource
import com.tangem.domain.promo.PromoRepository
@ -24,7 +25,7 @@ import kotlinx.coroutines.withTimeoutOrNull
*
* @property rampManager Ramp manager to check ramp availability
*/
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LargeClass")
class GetCryptoCurrencyActionsUseCase(
private val rampManager: RampStateManager,
private val walletManagersFacade: WalletManagersFacade,
@ -181,26 +182,11 @@ class GetCryptoCurrencyActionsUseCase(
}
// swap
if (userWallet.isMultiCurrency) {
val isExchangeable = withTimeoutOrNull(REQUEST_EXCHANGE_DATA_TIMEOUT) {
rampManager.availableForSwap(userWallet.walletId, cryptoCurrency)
} ?: false
if (isExchangeable && cryptoCurrencyStatus.value !is CryptoCurrencyStatus.NoQuote) {
val swapStoriesEnabled = swapFeatureToggles.isPromoStoriesEnabled
activeList.add(
TokenActionsState.ActionState.Swap(
unavailabilityReason = ScenarioUnavailabilityReason.None,
showBadge = shouldShowSwapStories && swapStoriesEnabled,
),
)
} else {
disabledList.add(
TokenActionsState.ActionState.Swap(
unavailabilityReason = ScenarioUnavailabilityReason.NotExchangeable(cryptoCurrency.name),
showBadge = false,
),
)
}
val swapActionState = getSwapUnavailabilityReason(userWallet, cryptoCurrencyStatus, shouldShowSwapStories)
if (swapActionState.unavailabilityReason == ScenarioUnavailabilityReason.None) {
activeList.add(swapActionState)
} else {
disabledList.add(swapActionState)
}
// buy
@ -289,7 +275,8 @@ class GetCryptoCurrencyActionsUseCase(
}
// swap
val isSwapAvailable = with(cryptoCurrencyStatus.value.sources) {
val sources = cryptoCurrencyStatus.value.sources
val isSwapAvailable = with(sources) {
quoteSource.isActual() && networkSource.isActual()
}
@ -297,8 +284,11 @@ class GetCryptoCurrencyActionsUseCase(
TokenActionsState.ActionState.Swap(
unavailabilityReason = if (isSwapAvailable) {
ScenarioUnavailabilityReason.None
} else {
} else if (sources.networkSource == StatusSource.ONLY_CACHE) {
ScenarioUnavailabilityReason.UsedOutdatedData
} else {
// CACHE source always when loading
ScenarioUnavailabilityReason.DataLoading
},
showBadge = false,
),
@ -376,6 +366,54 @@ class GetCryptoCurrencyActionsUseCase(
}
}
private suspend fun getSwapUnavailabilityReason(
userWallet: UserWallet,
cryptoCurrencyStatus: CryptoCurrencyStatus,
shouldShowSwapStories: Boolean,
): TokenActionsState.ActionState {
val cryptoCurrency = cryptoCurrencyStatus.currency
return if (userWallet.isMultiCurrency) {
if (cryptoCurrency.isCustom) {
return TokenActionsState.ActionState.Swap(
unavailabilityReason = ScenarioUnavailabilityReason.CustomToken(cryptoCurrency.name),
showBadge = false,
)
}
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.NoQuote) {
return TokenActionsState.ActionState.Swap(
unavailabilityReason = ScenarioUnavailabilityReason.TokenNoQuotes(cryptoCurrency.name),
showBadge = false,
)
}
val exchangeableState = withTimeoutOrNull(REQUEST_EXCHANGE_DATA_TIMEOUT) {
rampManager.availableForSwap(userWallet.walletId, cryptoCurrency)
} ?: ExchangeableState.Loading
val swapStoriesEnabled = swapFeatureToggles.isPromoStoriesEnabled
val currencyName = cryptoCurrency.name
val reason = when (exchangeableState) {
ExchangeableState.Exchangeable -> {
ScenarioUnavailabilityReason.None
}
ExchangeableState.AssetNotFound -> ScenarioUnavailabilityReason.AssetNotFound(currencyName)
ExchangeableState.Error -> ScenarioUnavailabilityReason.ExpressUnreachable(currencyName)
ExchangeableState.Loading -> ScenarioUnavailabilityReason.ExpressLoading(currencyName)
ExchangeableState.NotExchangeable -> ScenarioUnavailabilityReason.NotExchangeable(currencyName)
}
val isShowBadge = reason == ScenarioUnavailabilityReason.None && shouldShowSwapStories && swapStoriesEnabled
TokenActionsState.ActionState.Swap(
unavailabilityReason = reason,
showBadge = isShowBadge,
)
} else {
TokenActionsState.ActionState.Swap(
unavailabilityReason = ScenarioUnavailabilityReason.SingleWallet,
showBadge = false,
)
}
}
private fun isAddressAvailable(networkAddress: NetworkAddress?): Boolean {
return networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty()
}

View file

@ -10,6 +10,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.exchange.ExchangeableState
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
@ -241,10 +242,21 @@ internal class OnrampTokenListModel @Inject constructor(
val isAvailable = rampStateManager.availableForSwap(
userWalletId = params.userWalletId,
cryptoCurrency = status.currency,
)
).isSwapAvailable() && !status.currency.isCustom
isAvailable && status.value !is CryptoCurrencyStatus.NoQuote
}
}
}
private fun ExchangeableState.isSwapAvailable(): Boolean {
return when (this) {
ExchangeableState.AssetNotFound,
ExchangeableState.Error,
ExchangeableState.Loading,
ExchangeableState.NotExchangeable,
-> false
ExchangeableState.Exchangeable -> true
}
}
}

View file

@ -45,10 +45,7 @@ import com.tangem.domain.staking.GetYieldUseCase
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.NetworkAddress
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText
@ -281,6 +278,7 @@ internal class TokenDetailsViewModel @Inject constructor(
.conflate()
.distinctUntilChanged()
.onEach {
sendButtonsEvents(it.states)
internalUiState.value = stateFactory.getManageButtonsState(actions = it.states)
}
.flowOn(dispatchers.main)
@ -288,6 +286,21 @@ internal class TokenDetailsViewModel @Inject constructor(
.saveIn(buttonsJobHolder)
}
private fun sendButtonsEvents(states: List<TokenActionsState.ActionState>) {
// for now send only for swap
val swapState = states.firstOrNull { it is TokenActionsState.ActionState.Swap } ?: return
if (swapState.unavailabilityReason != ScenarioUnavailabilityReason.None) {
analyticsEventsHandler.send(
TokenScreenAnalyticsEvent.ActionButtonDisabled(
token = cryptoCurrency.symbol,
status = swapState.unavailabilityReason.toReasonAnalyticsText(),
blockchain = cryptoCurrency.network.name,
action = "Swap",
),
)
}
}
private fun updateWarnings(cryptoCurrencyStatus: CryptoCurrencyStatus) {
viewModelScope.launch(dispatchers.main) {
getCurrencyWarningsUseCase.invoke(
@ -472,14 +485,15 @@ internal class TokenDetailsViewModel @Inject constructor(
}
override fun onBuyClick(unavailabilityReason: ScenarioUnavailabilityReason) {
analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonBuy(cryptoCurrency.symbol))
analyticsEventsHandler.send(
TokenScreenAnalyticsEvent.ButtonWithParams.ButtonBuy(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
status = unavailabilityReason.toReasonAnalyticsText(),
),
)
if (handleUnavailabilityReason(
unavailabilityReason = unavailabilityReason,
tokenSymbol = cryptoCurrency.symbol,
tokenAction = TokenScreenAnalyticsEvent.NoticeActionInactive.TokenAction.BuyAction,
)
) {
if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) {
return
}
@ -512,7 +526,13 @@ internal class TokenDetailsViewModel @Inject constructor(
}
override fun onBuyCoinClick(cryptoCurrency: CryptoCurrency) {
analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonBuy(cryptoCurrency.symbol))
analyticsEventsHandler.send(
TokenScreenAnalyticsEvent.ButtonWithParams.ButtonBuy(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
status = null,
),
)
router.openTokenDetails(userWalletId = userWalletId, currency = cryptoCurrency)
}
@ -528,14 +548,15 @@ internal class TokenDetailsViewModel @Inject constructor(
}
override fun onSendClick(unavailabilityReason: ScenarioUnavailabilityReason) {
analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonSend(cryptoCurrency.symbol))
analyticsEventsHandler.send(
TokenScreenAnalyticsEvent.ButtonWithParams.ButtonSend(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
status = unavailabilityReason.toReasonAnalyticsText(),
),
)
if (handleUnavailabilityReason(
unavailabilityReason = unavailabilityReason,
tokenSymbol = cryptoCurrency.symbol,
tokenAction = TokenScreenAnalyticsEvent.NoticeActionInactive.TokenAction.SendAction,
)
) {
if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) {
return
}
@ -554,17 +575,18 @@ internal class TokenDetailsViewModel @Inject constructor(
override fun onReceiveClick(unavailabilityReason: ScenarioUnavailabilityReason) {
val networkAddress = cryptoCurrencyStatus?.value?.networkAddress ?: return
if (handleUnavailabilityReason(
unavailabilityReason = unavailabilityReason,
tokenSymbol = cryptoCurrency.symbol,
tokenAction = TokenScreenAnalyticsEvent.NoticeActionInactive.TokenAction.ReceiveAction,
)
) {
if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) {
return
}
viewModelScope.launch(dispatchers.main) {
analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrency.symbol))
analyticsEventsHandler.send(
TokenScreenAnalyticsEvent.ButtonWithParams.ButtonReceive(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
status = unavailabilityReason.toReasonAnalyticsText(),
),
)
analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrency.symbol))
internalUiState.value = stateFactory.getStateWithReceiveBottomSheet(
@ -583,12 +605,7 @@ internal class TokenDetailsViewModel @Inject constructor(
}
override fun onStakeClick(unavailabilityReason: ScenarioUnavailabilityReason) {
if (handleUnavailabilityReason(
unavailabilityReason = unavailabilityReason,
tokenSymbol = cryptoCurrency.symbol,
tokenAction = null,
)
) {
if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) {
return
}
@ -619,14 +636,15 @@ internal class TokenDetailsViewModel @Inject constructor(
}
override fun onSellClick(unavailabilityReason: ScenarioUnavailabilityReason) {
analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonSell(cryptoCurrency.symbol))
analyticsEventsHandler.send(
TokenScreenAnalyticsEvent.ButtonWithParams.ButtonSell(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
status = unavailabilityReason.toReasonAnalyticsText(),
),
)
if (handleUnavailabilityReason(
unavailabilityReason = unavailabilityReason,
tokenSymbol = cryptoCurrency.symbol,
tokenAction = TokenScreenAnalyticsEvent.NoticeActionInactive.TokenAction.SellAction,
)
) {
if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) {
return
}
@ -643,14 +661,15 @@ internal class TokenDetailsViewModel @Inject constructor(
}
override fun onSwapClick(unavailabilityReason: ScenarioUnavailabilityReason) {
analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonExchange(cryptoCurrency.symbol))
analyticsEventsHandler.send(
TokenScreenAnalyticsEvent.ButtonWithParams.ButtonExchange(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
status = unavailabilityReason.toReasonAnalyticsText(),
),
)
if (handleUnavailabilityReason(
unavailabilityReason = unavailabilityReason,
tokenSymbol = cryptoCurrency.symbol,
tokenAction = TokenScreenAnalyticsEvent.NoticeActionInactive.TokenAction.SwapAction,
)
) {
if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) {
return
}
@ -972,21 +991,8 @@ internal class TokenDetailsViewModel @Inject constructor(
internalUiState.value = stateFactory.getStateWithClosedBottomSheet()
}
private fun handleUnavailabilityReason(
unavailabilityReason: ScenarioUnavailabilityReason,
tokenSymbol: String,
tokenAction: TokenScreenAnalyticsEvent.NoticeActionInactive.TokenAction?,
): Boolean {
private fun handleUnavailabilityReason(unavailabilityReason: ScenarioUnavailabilityReason): Boolean {
if (unavailabilityReason == ScenarioUnavailabilityReason.None) return false
if (tokenAction != null) {
analyticsEventsHandler.send(
TokenScreenAnalyticsEvent.NoticeActionInactive(
token = tokenSymbol,
tokenAction = tokenAction,
reason = unavailabilityReason.toReasonAnalyticsText(),
),
)
}
internalUiState.value = stateFactory.getStateWithActionButtonErrorDialog(unavailabilityReason)

View file

@ -39,6 +39,8 @@ import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.AVAILABLE
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
@ -133,7 +135,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
analyticsEventHandler.send(
event = TokenScreenAnalyticsEvent.ButtonSend(cryptoCurrencyStatus.currency.symbol),
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonSend(
token = cryptoCurrencyStatus.currency.symbol,
status = unavailabilityReason.toReasonAnalyticsText(),
blockchain = cryptoCurrencyStatus.currency.network.name,
),
)
if (handleUnavailabilityReason(unavailabilityReason)) return
@ -151,7 +157,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
val userWalletId = stateHolder.getSelectedWalletId()
analyticsEventHandler.send(
event = TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrencyStatus.currency.symbol),
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonReceive(
token = cryptoCurrencyStatus.currency.symbol,
status = AVAILABLE,
blockchain = cryptoCurrencyStatus.currency.network.name,
),
)
analyticsEventHandler.send(
@ -290,7 +300,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
unavailabilityReason: ScenarioUnavailabilityReason,
) {
analyticsEventHandler.send(
event = TokenScreenAnalyticsEvent.ButtonSell(cryptoCurrencyStatus.currency.symbol),
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonSell(
token = cryptoCurrencyStatus.currency.symbol,
status = unavailabilityReason.toReasonAnalyticsText(),
blockchain = cryptoCurrencyStatus.currency.network.name,
),
)
if (handleUnavailabilityReason(unavailabilityReason)) return
@ -314,7 +328,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
analyticsEventHandler.send(
event = TokenScreenAnalyticsEvent.ButtonBuy(cryptoCurrencyStatus.currency.symbol),
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonBuy(
token = cryptoCurrencyStatus.currency.symbol,
status = unavailabilityReason.toReasonAnalyticsText(),
blockchain = cryptoCurrencyStatus.currency.network.name,
),
)
if (handleUnavailabilityReason(unavailabilityReason)) return
@ -349,7 +367,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
unavailabilityReason: ScenarioUnavailabilityReason,
) {
analyticsEventHandler.send(
event = TokenScreenAnalyticsEvent.ButtonExchange(cryptoCurrencyStatus.currency.symbol),
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonExchange(
token = cryptoCurrencyStatus.currency.symbol,
status = unavailabilityReason.toReasonAnalyticsText(),
blockchain = cryptoCurrencyStatus.currency.network.name,
),
)
if (handleUnavailabilityReason(unavailabilityReason)) return