Updated on 2026-08-14
This commit is contained in:
commit
9e38389156
14 changed files with 331 additions and 188 deletions
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
},
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue