Updated on 2026-08-14
This commit is contained in:
parent
b7ea0b5d2c
commit
c6c7cf61ab
14 changed files with 241 additions and 90 deletions
|
|
@ -3,6 +3,7 @@ package com.tangem.common.ui.markets
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -73,6 +74,7 @@ fun MarketListItemContentV2(model: MarketsListItemUM, modifier: Modifier = Modif
|
|||
tangemIconUM = TangemIconUM.Url(model.iconUrl, fallbackRes = R.drawable.ic_custom_token_44),
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x10)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.layoutId(layoutId = TangemRowLayoutId.HEAD),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.domain.onramp.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_DESCRIPTION
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.PAYMENT_METHOD
|
||||
|
|
@ -208,4 +209,17 @@ sealed class OnrampAnalyticsEvent(
|
|||
event = "Button - All Offers",
|
||||
params = emptyMap(),
|
||||
)
|
||||
|
||||
data class NoticeBuyNotSupported(
|
||||
private val source: OnrampSource,
|
||||
private val tokenSymbol: String,
|
||||
private val blockchain: String,
|
||||
) : OnrampAnalyticsEvent(
|
||||
event = "Notice - Buy Not Supported",
|
||||
params = mapOf(
|
||||
SOURCE to source.analyticsName,
|
||||
TOKEN_PARAM to tokenSymbol,
|
||||
BLOCKCHAIN to blockchain,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -54,37 +54,6 @@ internal open class BaseActionsFactory(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the unavailability reason for the BUY action
|
||||
*
|
||||
* @param userWallet the user's cold wallet
|
||||
* @param currency the cryptocurrency to check
|
||||
* @param requirementsDeferred a deferred object containing the asset requirements condition
|
||||
*/
|
||||
protected suspend fun getOnrampUnavailabilityReason(
|
||||
userWallet: UserWallet,
|
||||
currency: CryptoCurrency,
|
||||
requirementsDeferred: Deferred<AssetRequirementsCondition?>?,
|
||||
): ScenarioUnavailabilityReason {
|
||||
// Start2Coin (S2C) are legacy single-currency cards that do not support buying crypto in-app
|
||||
// (historically only Receive/Send were offered for them).
|
||||
if (userWallet is UserWallet.Cold && userWallet.cardTypesResolver.isStart2Coin()) {
|
||||
return ScenarioUnavailabilityReason.BuyUnavailable(currency.name)
|
||||
}
|
||||
|
||||
val onrampUnavailabilityReason = rampStateManager.availableForBuy(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrency = currency,
|
||||
)
|
||||
val shouldCheckAssetRequirements =
|
||||
onrampUnavailabilityReason == ScenarioUnavailabilityReason.None && requirementsDeferred != null
|
||||
return if (shouldCheckAssetRequirements) {
|
||||
getReceiveScenario(requirementsDeferred.await())
|
||||
} else {
|
||||
onrampUnavailabilityReason
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the unavailability reason for the SEND action
|
||||
*
|
||||
|
|
|
|||
|
|
@ -50,14 +50,6 @@ internal class CommonActionsFactory(
|
|||
null
|
||||
}
|
||||
|
||||
val onrampUnavailabilityReasonDeferred = async {
|
||||
getOnrampUnavailabilityReason(
|
||||
userWallet = userWallet,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
requirementsDeferred = requirementsDeferred,
|
||||
)
|
||||
}
|
||||
|
||||
val sendUnavailabilityReasonDeferred = async {
|
||||
getSendUnavailabilityReason(userWalletId = userWallet.walletId, cryptoCurrencyStatus = cryptoCurrencyStatus)
|
||||
}
|
||||
|
|
@ -99,7 +91,7 @@ internal class CommonActionsFactory(
|
|||
// endregion
|
||||
|
||||
// region Buy
|
||||
addBuyAction(reason = onrampUnavailabilityReasonDeferred.await())
|
||||
addBuyAction(reason = ScenarioUnavailabilityReason.None)
|
||||
// endregion
|
||||
|
||||
// region Sell
|
||||
|
|
|
|||
|
|
@ -52,14 +52,6 @@ internal class OutdatedDataActionsFactory(
|
|||
null
|
||||
}
|
||||
|
||||
val onrampUnavailabilityReasonDeferred = async {
|
||||
getOnrampUnavailabilityReason(
|
||||
userWallet = userWallet,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
requirementsDeferred = requirementsDeferred,
|
||||
)
|
||||
}
|
||||
|
||||
val sendUnavailabilityReasonDeferred = if (sources.networkSource == StatusSource.ACTUAL) {
|
||||
async {
|
||||
getSendUnavailabilityReason(
|
||||
|
|
@ -87,7 +79,7 @@ internal class OutdatedDataActionsFactory(
|
|||
// endregion
|
||||
|
||||
// region Buy
|
||||
addBuyAction(reason = onrampUnavailabilityReasonDeferred.await())
|
||||
addBuyAction(reason = ScenarioUnavailabilityReason.None)
|
||||
// endregion
|
||||
|
||||
// region Stake
|
||||
|
|
|
|||
|
|
@ -35,14 +35,6 @@ internal class UnreachableActionsFactory(
|
|||
null
|
||||
}
|
||||
|
||||
val onrampUnavailabilityReasonDeferred = async {
|
||||
getOnrampUnavailabilityReason(
|
||||
userWallet = userWallet,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
requirementsDeferred = requirementsDeferred,
|
||||
)
|
||||
}
|
||||
|
||||
val hideTokenUnavailabilityReason = getTokenHideUnavailabilityReason(userWallet)
|
||||
// endregion
|
||||
|
||||
|
|
@ -52,7 +44,7 @@ internal class UnreachableActionsFactory(
|
|||
// endregion
|
||||
|
||||
// region Buy
|
||||
addBuyAction(reason = onrampUnavailabilityReasonDeferred.await())
|
||||
addBuyAction(reason = ScenarioUnavailabilityReason.None)
|
||||
// endregion
|
||||
|
||||
// region Receive
|
||||
|
|
|
|||
|
|
@ -82,4 +82,8 @@ dependencies {
|
|||
/** Other */
|
||||
implementation(deps.decompose.ext.compose)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
|
||||
/** Tests */
|
||||
testImplementation(projects.test.core)
|
||||
testImplementation(projects.common.test)
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.onramp.main.entity
|
|||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.ds.message.TangemMessageUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
@Immutable
|
||||
|
|
@ -11,9 +12,12 @@ internal sealed interface OnrampMainComponentUM {
|
|||
val topBarConfig: OnrampMainTopBarUM
|
||||
val errorNotification: NotificationUM?
|
||||
|
||||
val buyNotSupportedMessage: TangemMessageUM?
|
||||
|
||||
data class InitialLoading(
|
||||
override val topBarConfig: OnrampMainTopBarUM,
|
||||
override val errorNotification: NotificationUM?,
|
||||
override val buyNotSupportedMessage: TangemMessageUM? = null,
|
||||
) : OnrampMainComponentUM
|
||||
|
||||
data class Content(
|
||||
|
|
@ -22,6 +26,7 @@ internal sealed interface OnrampMainComponentUM {
|
|||
val amountBlockState: OnrampAmountBlockUM,
|
||||
val offersBlockState: OnrampOffersBlockUM,
|
||||
val onrampAmountButtonUMState: OnrampAmountButtonUMState,
|
||||
override val buyNotSupportedMessage: TangemMessageUM? = null,
|
||||
) : OnrampMainComponentUM
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.message.TangemMessageEffect
|
||||
import com.tangem.core.ui.ds.message.TangemMessageIconPosition
|
||||
import com.tangem.core.ui.ds.message.TangemMessageUM
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
|
|
@ -21,6 +23,7 @@ import com.tangem.features.onramp.impl.R
|
|||
import com.tangem.features.onramp.main.entity.*
|
||||
import com.tangem.utils.Provider
|
||||
import java.math.BigDecimal
|
||||
import com.tangem.core.ui.R as CoreUiR
|
||||
|
||||
internal class OnrampStateFactory(
|
||||
private val currentStateProvider: Provider<OnrampMainComponentUM>,
|
||||
|
|
@ -120,6 +123,42 @@ internal class OnrampStateFactory(
|
|||
}
|
||||
}
|
||||
|
||||
fun getBuyNotSupportedState(state: OnrampMainComponentUM = currentStateProvider()): OnrampMainComponentUM {
|
||||
val message = buildBuyNotSupportedMessage()
|
||||
|
||||
return when (state) {
|
||||
is OnrampMainComponentUM.Content -> state.copy(
|
||||
buyNotSupportedMessage = message,
|
||||
errorNotification = null,
|
||||
offersBlockState = OnrampOffersBlockUM.Empty,
|
||||
onrampAmountButtonUMState = OnrampAmountButtonUMState.None,
|
||||
amountBlockState = state.amountBlockState.copy(
|
||||
amountFieldModel = state.amountBlockState.amountFieldModel.copy(isError = true),
|
||||
secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty,
|
||||
),
|
||||
)
|
||||
is OnrampMainComponentUM.InitialLoading -> state.copy(
|
||||
buyNotSupportedMessage = message,
|
||||
errorNotification = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildBuyNotSupportedMessage(): TangemMessageUM = TangemMessageUM(
|
||||
id = "buy_not_supported",
|
||||
title = resourceReference(
|
||||
id = R.string.onramp_token_is_not_supported_banner_title,
|
||||
formatArgs = wrappedList(cryptoCurrency.name),
|
||||
),
|
||||
subtitle = resourceReference(R.string.onramp_token_is_not_supported_banner_subtitle),
|
||||
messageEffect = TangemMessageEffect.None,
|
||||
iconUM = TangemIconUM.Icon(
|
||||
iconRes = CoreUiR.drawable.ic_attention_default_24,
|
||||
tintReference = { TangemTheme.colors2.graphic.status.attention },
|
||||
),
|
||||
iconPosition = TangemMessageIconPosition.Leading,
|
||||
)
|
||||
|
||||
private fun getNoPairsErrorState(): OnrampMainComponentUM {
|
||||
val state = currentStateProvider()
|
||||
val contentState = state as? OnrampMainComponentUM.Content ?: return state
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.onramp.main.model
|
|||
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -9,12 +10,15 @@ import com.tangem.core.decompose.navigation.Router
|
|||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.components.fields.InputManager
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.onramp.*
|
||||
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
|
||||
import com.tangem.domain.onramp.model.OnrampAvailability
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.onramp.main.OnrampMainComponent
|
||||
import com.tangem.features.onramp.main.entity.*
|
||||
|
|
@ -30,9 +34,9 @@ import com.tangem.utils.coroutines.PeriodicTask
|
|||
import com.tangem.utils.coroutines.SingleTaskScheduler
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
|
|
@ -46,6 +50,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
private val fetchQuotesUseCase: OnrampFetchQuotesUseCase,
|
||||
private val getOnrampQuotesUseCase: GetOnrampQuotesUseCase,
|
||||
private val fetchPairsUseCase: OnrampFetchPairsUseCase,
|
||||
private val rampStateManager: RampStateManager,
|
||||
private val amountInputManager: InputManager,
|
||||
private val getOnrampOffersUseCase: GetOnrampOffersUseCase,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
|
|
@ -222,6 +227,8 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleOnrampAvailability(availability: OnrampAvailability) {
|
||||
// "Buy not supported" notification has priority over the residency flow.
|
||||
if (state.value.buyNotSupportedMessage != null) return
|
||||
when (availability) {
|
||||
is OnrampAvailability.Available -> Unit
|
||||
is OnrampAvailability.ConfirmResidency,
|
||||
|
|
@ -274,23 +281,30 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
ifLeft = ::handleOnrampError,
|
||||
ifRight = { country ->
|
||||
if (country == null) return@onEach
|
||||
state.update { prevState ->
|
||||
when (prevState) {
|
||||
is OnrampMainComponentUM.Content -> {
|
||||
amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency)
|
||||
}
|
||||
is OnrampMainComponentUM.InitialLoading -> {
|
||||
stateFactory.getReadyState(country.defaultCurrency, params.initialFiatAmount)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Resolve token-level buy support BEFORE emitting any Content state, so an
|
||||
// unsupported token never briefly shows an enabled amount field — otherwise it
|
||||
// would grab focus and flash the keyboard before being disabled.
|
||||
if (isTokenNotSupportedForBuy()) {
|
||||
showBuyNotSupported(country)
|
||||
} else {
|
||||
state.update { prevState -> getCountryUpdatedState(prevState, country) }
|
||||
updatePairsAndQuotes()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun getCountryUpdatedState(
|
||||
prevState: OnrampMainComponentUM,
|
||||
country: OnrampCountry,
|
||||
): OnrampMainComponentUM = when (prevState) {
|
||||
is OnrampMainComponentUM.Content -> amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency)
|
||||
is OnrampMainComponentUM.InitialLoading ->
|
||||
stateFactory.getReadyState(country.defaultCurrency, params.initialFiatAmount)
|
||||
}
|
||||
|
||||
private fun subscribeToQuotesUpdate() {
|
||||
getOnrampQuotesUseCase.invoke()
|
||||
.conflate()
|
||||
|
|
@ -356,11 +370,43 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun isTokenNotSupportedForBuy(): Boolean {
|
||||
// Token-level "cannot be bought", independent of country: the asset is either flagged as
|
||||
// not onrampable (BuyUnavailable) or absent from the express asset list (AssetNotFound).
|
||||
// Transient express states (loading/unreachable) are NOT treated as "not supported".
|
||||
val reason = rampStateManager.availableForBuy(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
)
|
||||
return reason is ScenarioUnavailabilityReason.BuyUnavailable ||
|
||||
reason is ScenarioUnavailabilityReason.AssetNotFound
|
||||
}
|
||||
|
||||
private fun handleOnrampError(onrampError: OnrampError) {
|
||||
TangemLogger.e(onrampError.toString())
|
||||
state.update { stateFactory.getOnrampErrorState(onrampError) }
|
||||
}
|
||||
|
||||
private fun showBuyNotSupported(country: OnrampCountry) {
|
||||
if (state.value.buyNotSupportedMessage != null) return
|
||||
|
||||
analyticsEventHandler.send(
|
||||
OnrampAnalyticsEvent.NoticeBuyNotSupported(
|
||||
source = params.source,
|
||||
tokenSymbol = params.cryptoCurrency.symbol,
|
||||
blockchain = params.cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
quotesTaskScheduler.cancelTask()
|
||||
// "Not supported" has priority: hide the residency bottom sheet if it was already shown.
|
||||
bottomSheetNavigation.dismiss()
|
||||
// Emit the not-supported state in a single update built from the ready state, so the amount
|
||||
// field never appears enabled first (no focus/keyboard flash).
|
||||
state.update { prevState ->
|
||||
stateFactory.getBuyNotSupportedState(getCountryUpdatedState(prevState, country))
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendOnrampQuotesErrorAnalytic(quotes: List<OnrampQuote>) {
|
||||
quotes.forEach { errorState ->
|
||||
when (errorState) {
|
||||
|
|
|
|||
|
|
@ -125,9 +125,11 @@ private fun OnrampAmountField(amountField: AmountFieldModel, currencyCode: Strin
|
|||
)
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
if (!amountField.isError) {
|
||||
requester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OnrampAmountSecondary(state: OnrampSecondaryFieldErrorUM.Error) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp
|
|||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.ds.message.TangemMessage
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
|
|
@ -75,7 +76,7 @@ private fun InitialLoading(state: OnrampMainComponentUM.InitialLoading, modifier
|
|||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
OnrampAmountContentLoading()
|
||||
if (state.errorNotification != null) Notification(config = state.errorNotification.config)
|
||||
OnrampNotifications(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +135,16 @@ private fun Content(state: OnrampMainComponentUM.Content, modifier: Modifier = M
|
|||
|
||||
OnrampOffersContent(state = state.offersBlockState)
|
||||
|
||||
if (state.errorNotification != null) Notification(config = state.errorNotification.config)
|
||||
OnrampNotifications(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OnrampNotifications(state: OnrampMainComponentUM, modifier: Modifier = Modifier) {
|
||||
val buyNotSupportedMessage = state.buyNotSupportedMessage
|
||||
val errorNotification = state.errorNotification
|
||||
when {
|
||||
buyNotSupportedMessage != null -> TangemMessage(messageUM = buyNotSupportedMessage, modifier = modifier)
|
||||
errorNotification != null -> Notification(config = errorNotification.config, modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
|||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.tokens.GetAssetRequirementsUseCase
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM
|
||||
|
|
@ -270,9 +269,7 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
val isNotUnreachable = status.value !is CryptoCurrencyStatus.Unreachable
|
||||
|
||||
val isAvailable = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> {
|
||||
isAvailableForBuy
|
||||
} // unreachable state is available for Buy operation
|
||||
OnrampOperation.BUY -> true
|
||||
OnrampOperation.SELL -> isNotUnreachable
|
||||
OnrampOperation.SWAP -> {
|
||||
isNotUnreachable && isAvailableForBuy
|
||||
|
|
@ -295,12 +292,7 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
|
||||
private suspend fun checkAvailabilityByOperation(status: CryptoCurrencyStatus): Boolean {
|
||||
return when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> {
|
||||
rampStateManager.availableForBuy(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrency = status.currency,
|
||||
).isAvailable()
|
||||
}
|
||||
OnrampOperation.BUY -> true
|
||||
OnrampOperation.SELL -> {
|
||||
rampStateManager.availableForSell(
|
||||
userWalletId = userWallet.walletId,
|
||||
|
|
@ -318,8 +310,4 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ScenarioUnavailabilityReason.isAvailable(): Boolean {
|
||||
return this == ScenarioUnavailabilityReason.None
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package com.tangem.features.onramp.main.entity.factory
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.core.ui.ds.message.TangemMessageEffect
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.main.entity.OnrampAmountButtonUMState
|
||||
import com.tangem.features.onramp.main.entity.OnrampMainComponentUM
|
||||
import com.tangem.features.onramp.main.entity.OnrampOffersBlockUM
|
||||
import com.tangem.features.onramp.main.entity.OnrampSecondaryFieldErrorUM
|
||||
import com.tangem.utils.Provider
|
||||
import io.mockk.mockk
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class OnrampStateFactoryTest {
|
||||
|
||||
private lateinit var currentState: OnrampMainComponentUM
|
||||
|
||||
private val cryptoCurrency = MockCryptoCurrencyFactory().ethereum
|
||||
|
||||
private val factory = OnrampStateFactory(
|
||||
currentStateProvider = Provider { currentState },
|
||||
onrampAmountButtonUMStateFactory = OnrampAmountButtonUMStateFactory(),
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
onrampIntents = mockk(relaxed = true),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN initial loading with error WHEN getBuyNotSupportedState THEN shows None message and clears error`() {
|
||||
// Arrange
|
||||
currentState = factory.getInitialState(currency = "BTC", onClose = {}, openSettings = {})
|
||||
.copy(errorNotification = mockk())
|
||||
|
||||
// Act
|
||||
val result = factory.getBuyNotSupportedState()
|
||||
|
||||
// Assert
|
||||
val message = result.buyNotSupportedMessage
|
||||
assertThat(message).isNotNull()
|
||||
assertThat(message!!.messageEffect).isEqualTo(TangemMessageEffect.None)
|
||||
assertThat(message.title).isEqualTo(
|
||||
resourceReference(
|
||||
id = R.string.onramp_token_is_not_supported_banner_title,
|
||||
formatArgs = wrappedList(cryptoCurrency.name),
|
||||
),
|
||||
)
|
||||
assertThat(message.subtitle).isEqualTo(
|
||||
resourceReference(R.string.onramp_token_is_not_supported_banner_subtitle),
|
||||
)
|
||||
assertThat(result.errorNotification).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN content with errors WHEN getBuyNotSupportedState THEN message has priority and other errors hidden`() {
|
||||
// Arrange
|
||||
currentState = factory.getInitialState(currency = "BTC", onClose = {}, openSettings = {})
|
||||
val content = factory.getReadyState(currency = USD_CURRENCY) as OnrampMainComponentUM.Content
|
||||
currentState = content.copy(
|
||||
errorNotification = mockk(),
|
||||
offersBlockState = OnrampOffersBlockUM.Loading,
|
||||
onrampAmountButtonUMState = OnrampAmountButtonUMState.Loaded(persistentListOf()),
|
||||
amountBlockState = content.amountBlockState.copy(
|
||||
amountFieldModel = content.amountBlockState.amountFieldModel.copy(isError = true),
|
||||
secondaryFieldModel = OnrampSecondaryFieldErrorUM.Error(stringReference("error")),
|
||||
),
|
||||
)
|
||||
|
||||
// Act
|
||||
val result = factory.getBuyNotSupportedState() as OnrampMainComponentUM.Content
|
||||
|
||||
// Assert
|
||||
assertThat(result.buyNotSupportedMessage).isNotNull()
|
||||
assertThat(result.errorNotification).isNull()
|
||||
assertThat(result.offersBlockState).isEqualTo(OnrampOffersBlockUM.Empty)
|
||||
assertThat(result.onrampAmountButtonUMState).isEqualTo(OnrampAmountButtonUMState.None)
|
||||
assertThat(result.amountBlockState.secondaryFieldModel).isEqualTo(OnrampSecondaryFieldErrorUM.Empty)
|
||||
// Amount input is locked (disabled via isError) — like the unsupported-country case.
|
||||
assertThat(result.amountBlockState.amountFieldModel.isError).isTrue()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val USD_CURRENCY = OnrampCurrency(
|
||||
name = "US Dollar",
|
||||
code = "USD",
|
||||
image = null,
|
||||
precision = 2,
|
||||
unit = "$",
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue