diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 8cfba8dd64..217260e00b 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -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" diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index f2d1e26685..8876d81ae9 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -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 { @@ -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 } } diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenActionsUtils.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenActionsUtils.kt index baf5001db7..7337dccc5d 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenActionsUtils.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenActionsUtils.kt @@ -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") } diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 38afb4a40f..31e79f809c 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -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" } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/ExchangeableState.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/ExchangeableState.kt new file mode 100644 index 0000000000..9a4f140cf1 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/ExchangeableState.kt @@ -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 +} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt index 3e6ad8a4e8..180a8b16b5 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt @@ -31,7 +31,7 @@ interface RampStateManager { status: CryptoCurrencyStatus, ): Either - suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean + suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): ExchangeableState suspend fun fetchBuyServiceData() diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt index adc1a8e4ca..02f95ce1e1 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt @@ -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() diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt index dcea766313..e88a9c31ee 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt @@ -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 } } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 680c0148e3..5e627d055b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -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() } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index 62fa577e9d..76809b1485 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -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 + } + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 9b4d62818d..a20a2babfc 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -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) { + // 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) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index b9647e9365..b5a1671df6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -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