Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-11 14:13:54 +05:00
commit 9e38389156
14 changed files with 331 additions and 188 deletions

View file

@ -165,12 +165,12 @@ private fun AmountInfo(amountUM: AmountState, onMaxAmountClick: () -> Unit, modi
.padding(end = 16.dp)
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.secondary)
.padding(horizontal = 12.dp, vertical = 4.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(),
onClick = onMaxAmountClick,
),
)
.padding(horizontal = 12.dp, vertical = 4.dp),
)
}
}

View file

@ -19,8 +19,11 @@ internal sealed class SwapAmountUM {
abstract val primaryAmount: SwapAmountFieldUM
abstract val secondaryAmount: SwapAmountFieldUM
abstract val selectedAmountType: SwapAmountType
abstract val swapDirection: SwapDirection
data object Empty : SwapAmountUM() {
data class Empty(
override val swapDirection: SwapDirection,
) : SwapAmountUM() {
override val isPrimaryButtonEnabled = false
override val selectedAmountType = SwapAmountType.From
override val primaryAmount = SwapAmountFieldUM.Empty(SwapAmountType.From)
@ -29,16 +32,16 @@ internal sealed class SwapAmountUM {
data class Content(
override val isPrimaryButtonEnabled: Boolean,
override val swapDirection: SwapDirection,
override val selectedAmountType: SwapAmountType,
// two amount fields
override val primaryAmount: SwapAmountFieldUM,
override val secondaryAmount: SwapAmountFieldUM,
override val selectedAmountType: SwapAmountType,
val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus?,
// selected swap route
val swapDirection: SwapDirection,
val swapRateType: ExpressRateType,
// swap models

View file

@ -13,13 +13,9 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.swap.models.SwapQuoteModel
@ -38,17 +34,17 @@ import com.tangem.features.swap.v2.impl.amount.SwapAmountUpdateListener
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.converter.SwapAmountReadyStateConverter
import com.tangem.features.swap.v2.impl.amount.model.converter.SwapQuoteUMConverter
import com.tangem.features.swap.v2.impl.amount.model.transformers.*
import com.tangem.features.swap.v2.impl.chooseprovider.SwapChooseProviderComponent
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.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.orZero
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.math.BigDecimal
import javax.inject.Inject
import kotlin.properties.Delegates
import com.tangem.utils.transformer.update as transformerUpdate
@ -77,18 +73,11 @@ internal class SwapAmountModel @Inject constructor(
private var userWallet = params.userWallet
private var primaryCryptoCurrency: CryptoCurrency = params.primaryCryptoCurrencyStatusFlow.value.currency
private var secondaryCryptoCurrency: CryptoCurrency? = params.secondaryCryptoCurrency
private var primaryCryptoCurrencyStatus: CryptoCurrencyStatus = params.primaryCryptoCurrencyStatusFlow.value
private var secondaryCryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus(
currency = primaryCryptoCurrencyStatus.currency,
value = CryptoCurrencyStatus.Loading,
)
private var primaryMaximumAmountBoundary: EnterAmountBoundary by Delegates.notNull()
private var secondaryMaximumAmountBoundary: EnterAmountBoundary by Delegates.notNull()
private var primaryMinimumAmountBoundary: EnterAmountBoundary by Delegates.notNull()
private var secondaryMinimumAmountBoundary: EnterAmountBoundary by Delegates.notNull()
private var secondaryMaximumAmountBoundary: EnterAmountBoundary? = null
private var secondaryMinimumAmountBoundary: EnterAmountBoundary? = null
val bottomSheetNavigation: SlotNavigation<SwapChooseProviderConfig> = SlotNavigation()
@ -103,7 +92,6 @@ internal class SwapAmountModel @Inject constructor(
subscribeOnCryptoCurrencyStatusFlow()
subscribeOnAmountUpdateTriggerUpdates()
observeChooseSelectToken()
// todo observe balance hiding flow
}
fun updateState(amountUM: SwapAmountUM) {
@ -132,11 +120,10 @@ internal class SwapAmountModel @Inject constructor(
override fun onInfoClick() {
val amountUM = uiState.value as? SwapAmountUM.Content ?: return
val selectedProvider = amountUM.selectedQuote.provider ?: return
val cryptoCurrency = secondaryCryptoCurrency ?: return
swapAlertFactory.priceImpactAlert(
hasPriceImpact = (amountUM.secondaryAmount as? SwapAmountFieldUM.Content)?.priceImpact != null,
currencySymbol = cryptoCurrency.symbol,
currencySymbol = amountUM.primaryCryptoCurrencyStatus.currency.symbol,
provider = selectedProvider,
)
}
@ -193,32 +180,58 @@ internal class SwapAmountModel @Inject constructor(
}
override fun onSeparatorClick() {
// todo send with swap make swap types
// todo handle reverse pair click with swap redesign
val amountFieldData = uiState.value.primaryAmount.amountField as? AmountState.Data
val callback = (params as? SwapAmountComponentParams.AmountParams)?.callback ?: return
val primaryCryptoCurrencyStatus = (uiState.value as? SwapAmountUM.Content)?.primaryCryptoCurrencyStatus
if (primaryCryptoCurrencyStatus != null) {
uiState.transformerUpdate(
SwapAmountPrimaryReadyStateTransformer(
userWallet = userWallet,
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
appCurrency = appCurrency,
swapDirection = swapDirection,
clickIntents = this,
isBalanceHidden = params.isBalanceHidingFlow.value,
),
)
}
callback.onSeparatorClick(lastAmount = amountFieldData?.amountTextField?.value.orEmpty())
callback.onAmountResult(uiState.value)
}
private fun subscribeOnCryptoCurrencyStatusFlow() {
params.primaryCryptoCurrencyStatusFlow.onEach { primaryCurrencyStatus ->
primaryCryptoCurrencyStatus = primaryCurrencyStatus
val state = uiState.value
if (state is SwapAmountUM.Content) {
secondaryCryptoCurrency = state.primaryCryptoCurrencyStatus.currency
secondaryCryptoCurrencyStatus = state.primaryCryptoCurrencyStatus
params.primaryCryptoCurrencyStatusFlow
.distinctUntilChanged { old, new -> old.value.amount == new.value.amount } // Check only balance changes
.onEach { primaryCurrencyStatus ->
val secondaryStatus = (uiState.value as? SwapAmountUM.Content)?.secondaryCryptoCurrencyStatus
initCurrencies(
primaryStatus = state.primaryCryptoCurrencyStatus,
secondaryStatus = state.secondaryCryptoCurrencyStatus,
primaryStatus = primaryCurrencyStatus,
secondaryStatus = secondaryStatus,
)
} else {
initPairs(
primaryCryptoCurrency = primaryCryptoCurrency,
secondaryCryptoCurrency = secondaryCryptoCurrency,
)
}
}.launchIn(modelScope)
if (secondaryStatus != null) {
uiState.transformerUpdate(
SwapAmountUpdateBalanceTransformer(
cryptoCurrencyStatus = primaryCurrencyStatus,
primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
),
)
} else {
uiState.transformerUpdate(
SwapAmountPrimaryReadyStateTransformer(
userWallet = userWallet,
primaryCryptoCurrencyStatus = primaryCurrencyStatus,
appCurrency = appCurrency,
swapDirection = swapDirection,
clickIntents = this,
isBalanceHidden = params.isBalanceHidingFlow.value,
),
)
}
}.launchIn(modelScope)
}
private fun subscribeOnAmountUpdateTriggerUpdates() {
@ -226,6 +239,7 @@ internal class SwapAmountModel @Inject constructor(
.onEach {
if (uiState.value is SwapAmountUM.Content) {
onAmountValueChange(it)
saveResult()
}
}
.launchIn(modelScope)
@ -246,12 +260,12 @@ internal class SwapAmountModel @Inject constructor(
amountUM
}
}
initPairs(primaryCryptoCurrency, currency)
initPairs(currency)
}
.launchIn(modelScope)
}
private fun initPairs(primaryCryptoCurrency: CryptoCurrency, secondaryCryptoCurrency: CryptoCurrency?) {
private fun initPairs(secondaryCryptoCurrency: CryptoCurrency?) {
modelScope.launch {
val cryptoCurrencyStatusList = getMultiCryptoCurrencyStatusUseCase
.invokeMultiWalletSync(userWallet.walletId)
@ -277,22 +291,22 @@ internal class SwapAmountModel @Inject constructor(
swapDirection = params.swapDirection,
)
if (secondaryStatus != null) {
initCurrencies(primaryCryptoCurrencyStatus, secondaryStatus)
this@SwapAmountModel.secondaryCryptoCurrency = secondaryCryptoCurrency
secondaryCryptoCurrencyStatus = secondaryStatus
uiState.update {
SwapAmountReadyStateConverter(
swapCurrencies = swapCurrencies,
val primaryStatus = (uiState.value as? SwapAmountUM.Content)?.primaryCryptoCurrencyStatus
if (secondaryStatus != null && primaryStatus != null) {
initCurrencies(primaryStatus, secondaryStatus)
uiState.transformerUpdate(
SwapAmountSecondaryReadyStateTransformer(
userWallet = userWallet,
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
swapCurrencies = swapCurrencies,
primaryCryptoCurrencyStatus = primaryStatus,
secondaryCryptoCurrencyStatus = secondaryStatus,
appCurrency = appCurrency,
swapDirection = swapDirection,
clickIntents = this@SwapAmountModel,
isBalanceHidden = params.isBalanceHidingFlow.value,
).convert(Unit)
}
),
)
loadQuotes()
} else {
// todo not available currency to swap
}
@ -304,7 +318,7 @@ internal class SwapAmountModel @Inject constructor(
}
}
private suspend fun initCurrencies(primaryStatus: CryptoCurrencyStatus, secondaryStatus: CryptoCurrencyStatus) {
private suspend fun initCurrencies(primaryStatus: CryptoCurrencyStatus, secondaryStatus: CryptoCurrencyStatus?) {
primaryMinimumAmountBoundary = EnterAmountBoundary(
amount = getMinimumTransactionAmountSyncUseCase
.invoke(
@ -314,21 +328,25 @@ internal class SwapAmountModel @Inject constructor(
fiatRate = primaryStatus.value.fiatRate,
fiatAmount = primaryStatus.value.fiatAmount,
)
secondaryMinimumAmountBoundary = EnterAmountBoundary(
amount = getMinimumTransactionAmountSyncUseCase
.invoke(
userWalletId = userWallet.walletId,
cryptoCurrencyStatus = secondaryStatus,
).getOrNull().orZero(),
fiatRate = secondaryStatus.value.fiatRate,
fiatAmount = secondaryStatus.value.fiatAmount,
)
primaryMaximumAmountBoundary = MaxEnterAmountConverter().convert(primaryStatus)
secondaryMaximumAmountBoundary = MaxEnterAmountConverter().convert(secondaryStatus)
if (secondaryStatus != null) {
secondaryMinimumAmountBoundary = EnterAmountBoundary(
amount = getMinimumTransactionAmountSyncUseCase
.invoke(
userWalletId = userWallet.walletId,
cryptoCurrencyStatus = secondaryStatus,
).getOrNull().orZero(),
fiatRate = secondaryStatus.value.fiatRate,
fiatAmount = secondaryStatus.value.fiatAmount,
)
secondaryMaximumAmountBoundary = MaxEnterAmountConverter().convert(secondaryStatus)
}
}
private fun loadQuotes() {
val state = uiState.value as? SwapAmountUM.Content ?: return
if (state.secondaryCryptoCurrencyStatus == null) return
val (fromCryptoCurrency, toCryptoCurrency) = when (state.swapDirection) {
SwapDirection.Direct -> {
@ -376,11 +394,17 @@ internal class SwapAmountModel @Inject constructor(
).takeIf { error is ExpressError.AmountError }
},
ifRight = { quote: SwapQuoteModel ->
convertToSwapProviderUM(
quote = quote,
provider = provider,
differencePercent = DifferencePercent.Empty,
SwapQuoteUMConverter(
primaryCurrency = fromCryptoCurrency,
secondaryCurrency = toCryptoCurrency,
swapDirection = swapDirection,
allowanceContract = quote.allowanceContract,
isApprovalNeeded = checkAllowance(state, quote),
).convert(
SwapQuoteUMConverter.Data(
quote = quote,
provider = provider,
),
)
},
)
@ -397,61 +421,19 @@ internal class SwapAmountModel @Inject constructor(
}
}
private suspend fun convertToSwapProviderUM(
quote: SwapQuoteModel,
provider: ExpressProvider,
differencePercent: DifferencePercent,
swapDirection: SwapDirection,
): SwapQuoteUM {
// todo swap allowance
private suspend fun checkAllowance(state: SwapAmountUM.Content, quote: SwapQuoteModel): Boolean {
val allowanceContract = quote.allowanceContract
return if (allowanceContract != null) {
val allowance = getAllowanceUseCase(
val allowance = if (allowanceContract != null) {
getAllowanceUseCase(
userWalletId = userWallet.walletId,
cryptoCurrency = primaryCryptoCurrencyStatus.currency,
cryptoCurrency = state.primaryCryptoCurrencyStatus.currency,
spenderAddress = allowanceContract,
).getOrNull().orZero()
val isApprovalNeeded = allowance < primaryCryptoCurrencyStatus.value.amount.orZero()
if (isApprovalNeeded) {
SwapQuoteUM.Allowance(
provider = provider,
allowanceContract = allowanceContract,
)
} else {
SwapQuoteUM.Content(
provider = provider,
quoteAmount = quote.toTokenAmount,
diffPercent = differencePercent,
quoteAmountValue = stringReference(
quote.toTokenAmount.format {
crypto(
when (swapDirection) {
SwapDirection.Direct -> secondaryCryptoCurrencyStatus.currency
SwapDirection.Reverse -> primaryCryptoCurrencyStatus.currency
},
)
},
),
)
}
).getOrNull()
} else {
SwapQuoteUM.Content(
provider = provider,
quoteAmount = quote.toTokenAmount,
diffPercent = differencePercent,
quoteAmountValue = stringReference(
quote.toTokenAmount.format {
crypto(
when (swapDirection) {
SwapDirection.Direct -> secondaryCryptoCurrencyStatus.currency
SwapDirection.Reverse -> primaryCryptoCurrencyStatus.currency
},
)
},
),
)
BigDecimal.ZERO
}
return allowance.orZero() < state.primaryCryptoCurrencyStatus.value.amount.orZero()
}
private fun saveResult() {
@ -475,20 +457,7 @@ internal class SwapAmountModel @Inject constructor(
} else {
R.drawable.ic_close_24
},
backIconClick = {
// if (!route.isEditMode) {
// todo analytics
// analyticsEventHandler.send(
// CommonSendAnalyticEvents.CloseButtonClicked(
// categoryName = params.analyticsCategoryName,
// source = SendScreenSource.Address,
// isFromSummary = false,
// isValid = state.isPrimaryButtonEnabled,
// ),
// )
// }
params.callback.onBackClick()
},
backIconClick = params.callback::onBackClick,
primaryButton = NavigationButton(
textReference = if (route.isEditMode) {
resourceReference(R.string.common_continue)

View file

@ -42,8 +42,8 @@ internal object SwapAmountQuoteUtils {
}
fun SwapAmountUM.updateAmount(
onPrimaryAmount: SwapAmountFieldUM.Content.() -> SwapAmountFieldUM,
onSecondaryAmount: SwapAmountFieldUM.Content.() -> SwapAmountFieldUM,
onPrimaryAmount: SwapAmountFieldUM.Content.(CryptoCurrencyStatus) -> SwapAmountFieldUM,
onSecondaryAmount: SwapAmountFieldUM.Content.(CryptoCurrencyStatus) -> SwapAmountFieldUM,
): SwapAmountUM {
if (this !is SwapAmountUM.Content) return this
@ -51,10 +51,11 @@ internal object SwapAmountQuoteUtils {
selectedAmountType == SwapAmountType.From && swapDirection == SwapDirection.Direct
) {
val amountFieldUM = primaryAmount as? SwapAmountFieldUM.Content ?: return this
copy(primaryAmount = amountFieldUM.onPrimaryAmount())
copy(primaryAmount = amountFieldUM.onPrimaryAmount(primaryCryptoCurrencyStatus))
} else {
if (secondaryCryptoCurrencyStatus == null) return this
val amountFieldUM = secondaryAmount as? SwapAmountFieldUM.Content ?: return this
copy(secondaryAmount = amountFieldUM.onSecondaryAmount())
copy(secondaryAmount = amountFieldUM.onSecondaryAmount(secondaryCryptoCurrencyStatus))
}
}
}

View file

@ -0,0 +1,66 @@
package com.tangem.features.swap.v2.impl.amount.model.converter
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.swap.models.SwapQuoteModel
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.converter.Converter
import java.math.BigDecimal
internal class SwapQuoteUMConverter(
private val swapDirection: SwapDirection,
private val allowanceContract: String?,
private val isApprovalNeeded: Boolean,
private val primaryCurrency: CryptoCurrency,
private val secondaryCurrency: CryptoCurrency,
) : Converter<SwapQuoteUMConverter.Data, SwapQuoteUM> {
override fun convert(value: Data): SwapQuoteUM {
val (quote, provider) = value
return if (allowanceContract != null) {
if (isApprovalNeeded) {
SwapQuoteUM.Allowance(
provider = provider,
allowanceContract = allowanceContract,
)
} else {
SwapQuoteUM.Content(
provider = provider,
quoteAmount = quote.toTokenAmount,
diffPercent = DifferencePercent.Empty,
quoteAmountValue = stringReference(
quote.toTokenAmount.toQuoteValue(),
),
)
}
} else {
SwapQuoteUM.Content(
provider = provider,
quoteAmount = quote.toTokenAmount,
diffPercent = DifferencePercent.Empty,
quoteAmountValue = stringReference(
quote.toTokenAmount.toQuoteValue(),
),
)
}
}
private fun BigDecimal.toQuoteValue() = format {
crypto(
when (swapDirection) {
SwapDirection.Direct -> secondaryCurrency
SwapDirection.Reverse -> primaryCurrency
},
)
}
data class Data(
val quote: SwapQuoteModel,
val provider: ExpressProvider,
)
}

View file

@ -11,18 +11,18 @@ internal class SwapAmountChangeCurrencyTransformer(
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
return prevState.updateAmount(
onPrimaryAmount = {
onPrimaryAmount = { primaryStatus ->
copy(
amountField = AmountCurrencyTransformer(
cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
cryptoCurrencyStatus = primaryStatus,
value = isFiatSelected,
).transform(prevState.primaryAmount.amountField),
)
},
onSecondaryAmount = {
onSecondaryAmount = { secondaryStatus ->
copy(
amountField = AmountCurrencyTransformer(
cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
cryptoCurrencyStatus = secondaryStatus,
value = isFiatSelected,
).transform(prevState.secondaryAmount.amountField),
)

View file

@ -0,0 +1,57 @@
package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.express.models.ExpressRateType
import com.tangem.domain.swap.models.SwapCurrencies
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
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.converter.SwapAmountFieldConverter
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
@Suppress("LongParameterList")
internal class SwapAmountPrimaryReadyStateTransformer(
private val userWallet: UserWallet,
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val appCurrency: AppCurrency,
private val clickIntents: AmountScreenClickIntents,
private val swapDirection: SwapDirection,
private val isBalanceHidden: Boolean,
) : Transformer<SwapAmountUM> {
private val amountFieldConverter = SwapAmountFieldConverter(
swapDirection = swapDirection,
isBalanceHidden = isBalanceHidden,
userWallet = userWallet,
appCurrency = appCurrency,
clickIntents = clickIntents,
)
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
return SwapAmountUM.Content(
isPrimaryButtonEnabled = false,
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
primaryAmount = amountFieldConverter.convert(
selectedType = SwapAmountType.From,
cryptoCurrencyStatus = primaryCryptoCurrencyStatus,
),
secondaryCryptoCurrencyStatus = null,
secondaryAmount = SwapAmountFieldUM.Empty(
SwapAmountType.To,
),
swapDirection = swapDirection,
swapCurrencies = SwapCurrencies.EMPTY,
selectedAmountType = prevState.selectedAmountType,
swapRateType = ExpressRateType.Float,
swapQuotes = persistentListOf(),
selectedQuote = SwapQuoteUM.Empty,
appCurrency = appCurrency,
)
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.features.swap.v2.impl.amount.model.converter
package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.domain.appcurrency.model.AppCurrency
@ -9,21 +9,22 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
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.converter.SwapAmountFieldConverter
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.utils.converter.Converter
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
@Suppress("LongParameterList")
internal class SwapAmountReadyStateConverter(
internal class SwapAmountSecondaryReadyStateTransformer(
private val userWallet: UserWallet,
private val swapCurrencies: SwapCurrencies,
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val appCurrency: AppCurrency,
private val swapCurrencies: SwapCurrencies,
private val clickIntents: AmountScreenClickIntents,
private val swapDirection: SwapDirection,
private val isBalanceHidden: Boolean,
) : Converter<Unit, SwapAmountUM> {
) : Transformer<SwapAmountUM> {
private val amountFieldConverter = SwapAmountFieldConverter(
swapDirection = swapDirection,
@ -33,26 +34,23 @@ internal class SwapAmountReadyStateConverter(
clickIntents = clickIntents,
)
override fun convert(value: Unit): SwapAmountUM {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
return SwapAmountUM.Content(
isPrimaryButtonEnabled = false,
primaryAmount = prevState.primaryAmount,
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
primaryAmount = amountFieldConverter.convert(
selectedType = SwapAmountType.From,
cryptoCurrencyStatus = primaryCryptoCurrencyStatus,
),
secondaryAmount = amountFieldConverter.convert(
selectedType = SwapAmountType.To,
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
),
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
swapCurrencies = swapCurrencies,
selectedAmountType = prevState.selectedAmountType,
swapDirection = swapDirection,
swapRateType = ExpressRateType.Float,
swapQuotes = persistentListOf(),
selectedQuote = SwapQuoteUM.Empty,
selectedAmountType = SwapAmountType.From,
appCurrency = appCurrency,
swapRateType = ExpressRateType.Float,
isPrimaryButtonEnabled = false,
)
}
}

View file

@ -16,8 +16,8 @@ import com.tangem.utils.transformer.Transformer
internal class SwapAmountSelectQuoteTransformer(
private val quoteUM: SwapQuoteUM,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary?,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary?,
) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
@ -46,7 +46,9 @@ internal class SwapAmountSelectQuoteTransformer(
} else {
prevState.primaryAmount
},
secondaryAmount = if (prevState.selectedAmountType == SwapAmountType.From) {
secondaryAmount = if (prevState.selectedAmountType == SwapAmountType.From &&
prevState.secondaryCryptoCurrencyStatus != null && secondaryMaximumAmountBoundary != null
) {
val secondaryAmountField = prevState.secondaryAmount as? SwapAmountFieldUM.Content
val fromAmount = (prevState.primaryAmount.amountField as? AmountState.Data)
?.amountTextField?.cryptoAmount?.value.orZero()

View file

@ -17,8 +17,8 @@ import java.math.BigDecimal
internal class SwapAmountSetQuotesTransformer(
private val quotes: List<SwapQuoteUM>,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary?,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary?,
) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState

View file

@ -0,0 +1,38 @@
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.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.utils.transformer.Transformer
internal class SwapAmountUpdateBalanceTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val primaryMaximumAmountBoundary: EnterAmountBoundary,
private val primaryMinimumAmountBoundary: EnterAmountBoundary,
) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
val state = prevState as? SwapAmountUM.Content ?: return prevState
val primaryAmountUM = state.primaryAmount as? SwapAmountFieldUM.Content ?: return prevState
val amountField = primaryAmountUM.amountField as? AmountState.Data ?: return prevState
val amountValue = if (amountField.amountTextField.isFiatValue) {
amountField.amountTextField.fiatValue
} else {
amountField.amountTextField.value
}
return state.copy(
primaryCryptoCurrencyStatus = cryptoCurrencyStatus,
primaryAmount = primaryAmountUM.copy(
amountField = AmountFieldChangeTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
maxEnterAmount = primaryMaximumAmountBoundary,
minimumTransactionAmount = primaryMinimumAmountBoundary,
value = amountValue,
).transform(prevState.primaryAmount.amountField),
),
)
}
}

View file

@ -4,39 +4,45 @@ import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTrans
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
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 SwapAmountValueChangeTransformer(
private val primaryMaximumAmountBoundary: EnterAmountBoundary,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary,
private val primaryMinimumAmountBoundary: EnterAmountBoundary,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary?,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary?,
private val value: String,
) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
return prevState
.copy(selectedQuote = SwapQuoteUM.Loading)
.updateAmount(
onPrimaryAmount = {
onPrimaryAmount = { primaryStatus ->
copy(
amountField = AmountFieldChangeTransformer(
cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
cryptoCurrencyStatus = primaryStatus,
maxEnterAmount = primaryMaximumAmountBoundary,
minimumTransactionAmount = primaryMinimumAmountBoundary,
value = value,
).transform(prevState.primaryAmount.amountField),
)
},
onSecondaryAmount = {
copy(
amountField = AmountFieldChangeTransformer(
cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
maxEnterAmount = secondaryMaximumAmountBoundary,
minimumTransactionAmount = secondaryMinimumAmountBoundary,
value = value,
).transform(prevState.secondaryAmount.amountField),
)
onSecondaryAmount = { secondaryStatus ->
if (secondaryMaximumAmountBoundary != null) {
copy(
amountField = AmountFieldChangeTransformer(
cryptoCurrencyStatus = secondaryStatus,
maxEnterAmount = secondaryMaximumAmountBoundary,
minimumTransactionAmount = secondaryMinimumAmountBoundary,
value = value,
).transform(prevState.secondaryAmount.amountField),
)
} else {
this
}
},
)
}

View file

@ -9,9 +9,9 @@ import com.tangem.utils.transformer.Transformer
internal class SwapAmountValueMaxTransformer(
private val primaryMaximumAmountBoundary: EnterAmountBoundary,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary,
private val primaryMinimumAmountBoundary: EnterAmountBoundary,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary?,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary?,
) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
@ -19,25 +19,28 @@ internal class SwapAmountValueMaxTransformer(
return prevState
.copy(selectedQuote = SwapQuoteUM.Loading)
.updateAmount(
onPrimaryAmount = {
onPrimaryAmount = { primaryStatus ->
copy(
amountField = AmountFieldSetMaxAmountTransformer(
cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
cryptoCurrencyStatus = primaryStatus,
maxAmount = primaryMaximumAmountBoundary,
minAmount = primaryMinimumAmountBoundary,
).transform(prevState.primaryAmount.amountField),
)
},
onSecondaryAmount = {
copy(
amountField = AmountFieldSetMaxAmountTransformer(
cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
maxAmount = secondaryMaximumAmountBoundary,
minAmount = secondaryMinimumAmountBoundary,
).transform(prevState.secondaryAmount.amountField),
)
onSecondaryAmount = { secondaryStatus ->
if (secondaryMaximumAmountBoundary != null) {
copy(
amountField = AmountFieldSetMaxAmountTransformer(
cryptoCurrencyStatus = secondaryStatus,
maxAmount = secondaryMaximumAmountBoundary,
minAmount = secondaryMinimumAmountBoundary,
).transform(prevState.secondaryAmount.amountField),
)
} else {
this
}
},
)
}
}

View file

@ -354,12 +354,12 @@ private fun AmountMaxButton(onMaxAmountClick: () -> Unit) {
.padding(end = 16.dp)
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.secondary)
.padding(horizontal = 12.dp, vertical = 4.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(),
onClick = onMaxAmountClick,
),
)
.padding(horizontal = 12.dp, vertical = 4.dp),
)
}