Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-30 17:36:14 +05:00
parent 9ebfdb5b31
commit a6f51bdad5
8 changed files with 79 additions and 128 deletions

View file

@ -34,8 +34,8 @@ internal sealed class SwapAmountUM {
override val primaryAmount: SwapAmountFieldUM,
override val secondaryAmount: SwapAmountFieldUM,
override val selectedAmountType: SwapAmountType,
val primaryCryptoCurrencyStatus: CryptoCurrencyStatus?,
val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus?,
val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
// selected swap route
val swapDirection: SwapDirection,
@ -61,7 +61,7 @@ sealed class SwapAmountFieldUM {
) : SwapAmountFieldUM() {
override val amountField: AmountState = AmountState.Empty(
isPrimaryButtonEnabled = false,
isRedesignEnabled = false,
isRedesignEnabled = true,
)
}
@ -70,7 +70,7 @@ sealed class SwapAmountFieldUM {
) : SwapAmountFieldUM() {
override val amountField: AmountState = AmountState.Empty(
isPrimaryButtonEnabled = false,
isRedesignEnabled = false,
isRedesignEnabled = true,
)
}

View file

@ -116,8 +116,6 @@ internal class SwapAmountModel @Inject constructor(
uiState.transformerUpdate(
SwapAmountSelectQuoteTransformer(
quoteUM = quoteUM,
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
),
@ -136,8 +134,6 @@ internal class SwapAmountModel @Inject constructor(
override fun onAmountValueChange(value: String) {
uiState.transformerUpdate(
SwapAmountValueChangeTransformer(
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
@ -155,8 +151,6 @@ internal class SwapAmountModel @Inject constructor(
override fun onMaxValueClick() {
uiState.transformerUpdate(
SwapAmountValueMaxTransformer(
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
@ -169,8 +163,6 @@ internal class SwapAmountModel @Inject constructor(
override fun onCurrencyChangeClick(isFiat: Boolean) {
uiState.transformerUpdate(
SwapAmountChangeCurrencyTransformer(
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
isFiatSelected = isFiat,
),
)
@ -378,8 +370,6 @@ internal class SwapAmountModel @Inject constructor(
uiState.transformerUpdate(
SwapAmountSetQuotesTransformer(
quotes = quotes,
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
),

View file

@ -0,0 +1,28 @@
package com.tangem.features.swap.v2.impl.amount.model.converter
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.swap.v2.impl.R
import com.tangem.utils.converter.Converter
internal class SwapAmountErrorConverter(
private val cryptoCurrency: CryptoCurrency,
) : Converter<ExpressError, TextReference?> {
override fun convert(value: ExpressError): TextReference? = when (value) {
is ExpressError.AmountError.TooSmallError -> resourceReference(
R.string.express_provider_min_amount,
wrappedList(value.amount.format { crypto(cryptoCurrency = cryptoCurrency) }),
)
is ExpressError.AmountError.TooBigError -> resourceReference(
R.string.express_provider_max_amount,
wrappedList(value.amount.format { crypto(cryptoCurrency = cryptoCurrency) }),
)
else -> null
}
}

View file

@ -1,22 +1,20 @@
package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.converters.AmountCurrencyTransformer
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.updateAmount
import com.tangem.utils.transformer.Transformer
internal class SwapAmountChangeCurrencyTransformer(
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val isFiatSelected: Boolean,
) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
return prevState.updateAmount(
onPrimaryAmount = {
copy(
amountField = AmountCurrencyTransformer(
cryptoCurrencyStatus = primaryCryptoCurrencyStatus,
cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
value = isFiatSelected,
).transform(prevState.primaryAmount.amountField),
)
@ -24,7 +22,7 @@ internal class SwapAmountChangeCurrencyTransformer(
onSecondaryAmount = {
copy(
amountField = AmountCurrencyTransformer(
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
value = isFiatSelected,
).transform(prevState.secondaryAmount.amountField),
)

View file

@ -3,33 +3,50 @@ package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.calculatePriceImpact
import com.tangem.features.swap.v2.impl.amount.model.converter.SwapAmountErrorConverter
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.utils.extensions.orZero
import com.tangem.utils.transformer.Transformer
internal class SwapAmountSelectQuoteTransformer(
private val quoteUM: SwapQuoteUM,
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary,
) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
val providerErrorConverter = SwapAmountErrorConverter(
cryptoCurrency = prevState.primaryCryptoCurrencyStatus.currency,
)
return prevState.copy(
isPrimaryButtonEnabled = true,
isPrimaryButtonEnabled = quoteUM is SwapQuoteUM.Content,
selectedQuote = quoteUM,
secondaryAmount = if (
prevState.selectedAmountType == SwapAmountType.From && prevState.swapDirection == SwapDirection.Direct
) {
primaryAmount = if (prevState.selectedAmountType == SwapAmountType.From) {
val swapAmountField = prevState.primaryAmount as? SwapAmountFieldUM.Content
val amountField = swapAmountField?.amountField as? AmountState.Data
val amountError = (quoteUM as? SwapQuoteUM.Error)?.expressError?.let(providerErrorConverter::convert)
swapAmountField?.copy(
amountField = amountField?.copy(
amountTextField = amountField.amountTextField.copy(
error = amountError ?: TextReference.EMPTY,
isError = amountError != null,
),
) ?: swapAmountField.amountField,
) ?: prevState.primaryAmount
} else {
prevState.primaryAmount
},
secondaryAmount = if (prevState.selectedAmountType == SwapAmountType.From) {
val secondaryAmountField = prevState.secondaryAmount as? SwapAmountFieldUM.Content
val fromAmount = (prevState.primaryAmount.amountField as? AmountState.Data)
?.amountTextField?.cryptoAmount?.value.orZero()
@ -38,17 +55,17 @@ internal class SwapAmountSelectQuoteTransformer(
swapDirection = prevState.swapDirection,
fromTokenAmount = fromAmount,
toTokenAmount = toAmount.orZero(),
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
primaryCryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
)
secondaryAmountField?.copy(
priceImpact = priceImpact,
amountField = AmountFieldChangeTransformer(
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
maxEnterAmount = secondaryMaximumAmountBoundary,
minimumTransactionAmount = secondaryMinimumAmountBoundary,
value = toAmount?.parseBigDecimal(secondaryCryptoCurrencyStatus.currency.decimals)
value = toAmount?.parseBigDecimal(prevState.secondaryCryptoCurrencyStatus.currency.decimals)
.orEmpty(),
).transform(secondaryAmountField.amountField),
) ?: prevState.secondaryAmount

View file

@ -1,29 +1,15 @@
package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.calculatePriceImpact
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM.Content.DifferencePercent
import com.tangem.utils.StringsSigns
import com.tangem.utils.extensions.isPositive
import com.tangem.utils.extensions.orZero
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
@ -31,91 +17,30 @@ import java.math.BigDecimal
internal class SwapAmountSetQuotesTransformer(
private val quotes: List<SwapQuoteUM>,
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary,
) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
val fromAmount = when (prevState.swapDirection) {
SwapDirection.Direct -> prevState.primaryAmount.amountField
SwapDirection.Reverse -> prevState.secondaryAmount.amountField
} as? AmountState.Data
val fromAmountValue = fromAmount?.amountTextField?.cryptoAmount?.value.orZero()
val sortedQuotes = quotes.sortedWith(SwapQuotesComparator)
val bestQuote = findBestQuote(quotes) ?: SwapQuoteUM.Empty
return prevState.copy(
isPrimaryButtonEnabled = bestQuote is SwapQuoteUM.Content,
val selectQuoteTransformer = SwapAmountSelectQuoteTransformer(
quoteUM = bestQuote,
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
)
val updatedState = selectQuoteTransformer.transform(prevState = prevState)
if (updatedState !is SwapAmountUM.Content) return prevState
return updatedState.copy(
isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled && quotes.isNotEmpty(),
swapQuotes = getQuotesWithDiff(sortedQuotes, bestQuote),
selectedQuote = bestQuote,
primaryAmount = if (
prevState.selectedAmountType == SwapAmountType.From && prevState.swapDirection == SwapDirection.Direct
) {
val swapAmountField = prevState.primaryAmount as? SwapAmountFieldUM.Content
val amountField = swapAmountField?.amountField as? AmountState.Data
val amountError = (bestQuote as? SwapQuoteUM.Error)?.expressError.getAmountError()
if (amountField?.amountTextField?.isError == true) {
prevState.primaryAmount
} else {
swapAmountField?.copy(
amountField = amountField?.copy(
amountTextField = amountField.amountTextField.copy(
error = amountError ?: TextReference.EMPTY,
isError = amountError != null,
),
) ?: swapAmountField.amountField,
) ?: prevState.primaryAmount
}
} else {
prevState.primaryAmount
},
secondaryAmount = if (
prevState.selectedAmountType == SwapAmountType.From && prevState.swapDirection == SwapDirection.Direct
) {
val amountField = prevState.secondaryAmount as? SwapAmountFieldUM.Content
val toAmount = (bestQuote as? SwapQuoteUM.Content)?.quoteAmount
val priceImpact = calculatePriceImpact(
swapDirection = prevState.swapDirection,
fromTokenAmount = fromAmountValue,
toTokenAmount = toAmount.orZero(),
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
)
amountField?.copy(
priceImpact = priceImpact,
amountField = AmountFieldChangeTransformer(
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
maxEnterAmount = secondaryMaximumAmountBoundary,
minimumTransactionAmount = secondaryMinimumAmountBoundary,
value = toAmount?.parseBigDecimal(secondaryCryptoCurrencyStatus.currency.decimals).orEmpty(),
).transform(amountField.amountField),
) ?: prevState.secondaryAmount
} else {
prevState.secondaryAmount
},
)
}
private fun ExpressError?.getAmountError(): TextReference? = when (this) {
is ExpressError.AmountError.TooSmallError -> resourceReference(
R.string.express_provider_min_amount,
wrappedList(amount.format { crypto(cryptoCurrency = primaryCryptoCurrencyStatus.currency) }),
)
is ExpressError.AmountError.TooBigError -> resourceReference(
R.string.express_provider_max_amount,
wrappedList(amount.format { crypto(cryptoCurrency = primaryCryptoCurrencyStatus.currency) }),
)
else -> null
}
private fun getQuotesWithDiff(sortedQuotes: List<SwapQuoteUM>, bestQuote: SwapQuoteUM): ImmutableList<SwapQuoteUM> {
return sortedQuotes.sortedWith(SwapQuotesComparator)
.map { quote ->

View file

@ -2,15 +2,11 @@ package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.updateAmount
import com.tangem.utils.transformer.Transformer
@Suppress("LongParameterList")
internal class SwapAmountValueChangeTransformer(
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val primaryMaximumAmountBoundary: EnterAmountBoundary,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary,
private val primaryMinimumAmountBoundary: EnterAmountBoundary,
@ -25,7 +21,7 @@ internal class SwapAmountValueChangeTransformer(
onPrimaryAmount = {
copy(
amountField = AmountFieldChangeTransformer(
cryptoCurrencyStatus = primaryCryptoCurrencyStatus,
cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
maxEnterAmount = primaryMaximumAmountBoundary,
minimumTransactionAmount = primaryMinimumAmountBoundary,
value = value,
@ -35,7 +31,7 @@ internal class SwapAmountValueChangeTransformer(
onSecondaryAmount = {
copy(
amountField = AmountFieldChangeTransformer(
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
maxEnterAmount = secondaryMaximumAmountBoundary,
minimumTransactionAmount = secondaryMinimumAmountBoundary,
value = value,

View file

@ -2,15 +2,12 @@ package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmountTransformer
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.updateAmount
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.utils.transformer.Transformer
internal class SwapAmountValueMaxTransformer(
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val primaryMaximumAmountBoundary: EnterAmountBoundary,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary,
private val primaryMinimumAmountBoundary: EnterAmountBoundary,
@ -25,7 +22,7 @@ internal class SwapAmountValueMaxTransformer(
onPrimaryAmount = {
copy(
amountField = AmountFieldSetMaxAmountTransformer(
cryptoCurrencyStatus = primaryCryptoCurrencyStatus,
cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
maxAmount = primaryMaximumAmountBoundary,
minAmount = primaryMinimumAmountBoundary,
).transform(prevState.primaryAmount.amountField),
@ -34,7 +31,7 @@ internal class SwapAmountValueMaxTransformer(
onSecondaryAmount = {
copy(
amountField = AmountFieldSetMaxAmountTransformer(
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
maxAmount = secondaryMaximumAmountBoundary,
minAmount = secondaryMinimumAmountBoundary,
).transform(prevState.secondaryAmount.amountField),