diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldV2.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldV2.kt
index dbc955ab1e..9fdb77d943 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldV2.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldV2.kt
@@ -62,6 +62,7 @@ fun AmountFieldV2(
onValuePastedTriggerDismiss: () -> Unit,
onCurrencyChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
+ reserveSpaceForError: Boolean = true,
) {
val decimalFormat = rememberDecimalFormat()
@@ -120,12 +121,13 @@ fun AmountFieldV2(
AmountSecondary(
amountUM = amountUM,
onCurrencyChange = onCurrencyChange,
+ reserveSpaceForError = reserveSpaceForError,
)
}
}
@Composable
-private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -> Unit) {
+private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -> Unit, reserveSpaceForError: Boolean) {
Box(
modifier = Modifier
.fillMaxWidth()
@@ -144,29 +146,33 @@ private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -
AmountFieldCurrencyInfo(
amountUM = amountUM,
onCurrencyChange = onCurrencyChange,
+ modifier = if (reserveSpaceForError) Modifier.padding(bottom = 16.dp) else Modifier,
)
AmountFieldError(
isError = amountUM.amountTextField.isError,
isWarning = amountUM.amountTextField.isWarning,
error = amountUM.amountTextField.error,
+ reserveSpaceForError = reserveSpaceForError,
modifier = Modifier
- .align(BottomCenter)
- .padding(top = 24.dp),
+ .align(BottomCenter),
)
}
}
}
@Composable
-private fun BoxScope.AmountFieldCurrencyInfo(amountUM: AmountState.Data, onCurrencyChange: (Boolean) -> Unit) {
+private fun BoxScope.AmountFieldCurrencyInfo(
+ amountUM: AmountState.Data,
+ onCurrencyChange: (Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+) {
val isFiatAvailable = amountUM.amountTextField.fiatAmount.value != null
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
- modifier = Modifier
+ modifier = modifier
.align(TopCenter)
- .padding(bottom = 16.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
@@ -255,13 +261,14 @@ private fun AmountFieldError(
isError: Boolean,
isWarning: Boolean,
error: TextReference,
+ reserveSpaceForError: Boolean,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = isError || isWarning,
enter = fadeIn(),
exit = fadeOut(),
- modifier = modifier,
+ modifier = if (reserveSpaceForError) modifier.padding(top = 24.dp) else modifier,
label = "Error field appearance animation",
) {
val errorText = remember(this, error) { error }
@@ -271,7 +278,13 @@ private fun AmountFieldError(
style = TangemTheme.typography.caption2,
color = color,
textAlign = TextAlign.Center,
- modifier = Modifier.testTag(SendScreenTestTags.AMOUNT_ERROR_TEXT),
+ modifier = if (reserveSpaceForError) {
+ Modifier.testTag(SendScreenTestTags.AMOUNT_ERROR_TEXT)
+ } else {
+ Modifier
+ .padding(top = 24.dp)
+ .testTag(SendScreenTestTags.AMOUNT_ERROR_TEXT)
+ },
)
}
}
@@ -324,6 +337,7 @@ private fun AmountFieldV2_Preview(@PreviewParameter(AmountFieldV2PreviewProvider
onValueChange = {},
onValuePastedTriggerDismiss = { },
onCurrencyChange = {},
+ reserveSpaceForError = false,
modifier = Modifier.background(TangemTheme.colors.background.action),
)
}
diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt
index fe59dbf317..990e4bc9c1 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt
@@ -40,13 +40,13 @@ internal fun String.checkExceedBalance(
maxEnterAmount: EnterAmountBoundary,
amountTextField: AmountFieldModel,
): Boolean {
- val currencyCryptoAmount = maxEnterAmount.amount ?: BigDecimal.ZERO
- val currencyFiatAmount = maxEnterAmount.fiatAmount ?: BigDecimal.ZERO
val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals)
val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals)
return if (amountTextField.isFiatValue) {
+ val currencyFiatAmount = maxEnterAmount.fiatAmount ?: return false
fiatDecimal > currencyFiatAmount
} else {
+ val currencyCryptoAmount = maxEnterAmount.amount ?: return false
cryptoDecimal > currencyCryptoAmount
}
}
diff --git a/core/ui/src/main/res/drawable/ic_fixed.xml b/core/ui/src/main/res/drawable/ic_fixed.xml
new file mode 100644
index 0000000000..585d3632c3
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_fixed.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_floating.xml b/core/ui/src/main/res/drawable/ic_floating.xml
new file mode 100644
index 0000000000..0b94fd88fa
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_floating.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt
index 0f4ef33398..f5d7042b43 100644
--- a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt
+++ b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt
@@ -15,6 +15,8 @@ import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.express.models.request.ExchangeSentRequestBody
import com.tangem.datasource.api.express.models.request.PairsRequestBody
import com.tangem.datasource.api.express.models.response.ExchangeDataResponseWithTxDetails
+import com.tangem.datasource.api.express.models.response.RateType
+import com.tangem.datasource.api.express.models.response.SwapPairProvider
import com.tangem.datasource.api.express.models.response.TxDetails
import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.datasource.di.NetworkMoshi
@@ -98,8 +100,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
}
val mappedProviders = pair.providers
- .filterNot { it.hasOnlyFixedRateType() }
- .mapNotNull { expressProviders[it.providerId] }
+ .mapNotNull { it.withExpressProvider(expressProviders) }
.filterYieldSupplyProvider(statusFrom)
if (statusFrom != null && statusTo != null && mappedProviders.isNotEmpty()) {
@@ -157,8 +158,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
val currencyStatusTo = createSendWithSwapCryptoCurrencyStatus(statusToDeferred.await())
val mappedProvider = pair.providers
- .filterNot { it.hasOnlyFixedRateType() }
- .mapNotNull { mappedProviders[it.providerId] }
+ .mapNotNull { it.withExpressProvider(mappedProviders) }
.filterYieldSupplyProvider(currencyStatusFrom)
if (currencyStatusFrom != null && currencyStatusTo != null && mappedProvider.isNotEmpty()) {
@@ -443,6 +443,18 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
}
}
+ private fun SwapPairProvider.withExpressProvider(
+ expressProviders: Map,
+ ): ExpressProvider? {
+ val provider = expressProviders[providerId] ?: return null
+ return provider.copy(rateTypes = rateTypes.map { it.toExpressRateType() })
+ }
+
+ private fun RateType.toExpressRateType(): ExpressRateType = when (this) {
+ RateType.FLOAT -> ExpressRateType.Float
+ RateType.FIXED -> ExpressRateType.Fixed
+ }
+
private fun CryptoCurrency.getContractAddress(): String {
return when (this) {
is CryptoCurrency.Token -> this.contractAddress
diff --git a/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapQuoteModel.kt b/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapQuoteModel.kt
index 3a65c92b9c..45df45fc76 100644
--- a/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapQuoteModel.kt
+++ b/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapQuoteModel.kt
@@ -8,7 +8,7 @@ import java.math.BigDecimal
*
* @property provider swap provider
* @property toTokenAmount amount of token you want to receive
- * @property fromTokenAmount amount of from-token required (for fixed rate quotes)
+ * @property fromTokenAmount amount of from-token required (only set for fixed rate quotes)
* @property allowanceContract whether swap occurs via third token
*/
data class SwapQuoteModel(
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/entity/SwapAmountUM.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/entity/SwapAmountUM.kt
index c749636dc6..b5cdb3898e 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/entity/SwapAmountUM.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/entity/SwapAmountUM.kt
@@ -10,6 +10,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.domain.swap.models.SwapCurrencies
import com.tangem.domain.swap.models.SwapDirection
+import com.tangem.domain.swap.models.SwapRateMode
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import kotlinx.collections.immutable.ImmutableList
@@ -44,6 +45,7 @@ internal sealed class SwapAmountUM {
// selected swap route
val swapRateType: ExpressRateType,
+ val swapRateMode: SwapRateMode,
// swap models
val swapCurrencies: SwapCurrencies,
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountClickIntents.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountClickIntents.kt
index 0e9ba829e8..38716f07d8 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountClickIntents.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountClickIntents.kt
@@ -8,6 +8,7 @@ internal interface SwapAmountClickIntents : AmountScreenClickIntents {
fun onExpandEditField(selectedAmountType: SwapAmountType)
fun onInfoClick()
fun onSelectTokenClick()
+ fun onRateClick()
fun onSeparatorClick()
fun onProviderClick()
}
\ No newline at end of file
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt
index 45499a10b7..ac2fe177b4 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt
@@ -16,17 +16,18 @@ 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.utils.parseBigDecimal
import com.tangem.datasource.local.swap.SwapBestRateAnimationStore
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.ExpressRateType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.notifications.ShouldShowNotificationUseCase
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
+import com.tangem.domain.express.models.ExpressRateType
import com.tangem.domain.swap.models.*
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase
@@ -172,17 +173,43 @@ internal class SwapAmountModel @Inject constructor(
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
isNeedApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
+ isBalanceHidden = params.isBalanceHidingFlow.value,
+ primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
+ primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
),
)
}
override fun onExpandEditField(selectedAmountType: SwapAmountType) {
+ val content = uiState.value as? SwapAmountUM.Content ?: return
+
+ val newSwapRateType = when (selectedAmountType) {
+ SwapAmountType.To -> {
+ if (content.swapRateMode != SwapRateMode.FLOAT_ONLY) {
+ ExpressRateType.Fixed
+ } else {
+ return
+ }
+ }
+ SwapAmountType.From -> {
+ if (content.swapRateMode == SwapRateMode.FIXED_ONLY) {
+ ExpressRateType.Fixed
+ } else if (content.swapRateMode == SwapRateMode.FLOAT_AND_FIXED) {
+ ExpressRateType.Float
+ } else {
+ return
+ }
+ }
+ }
+
uiState.update { amountUM ->
- if (amountUM !is SwapAmountUM.Content) return
+ if (amountUM !is SwapAmountUM.Content) return@update amountUM
amountUM.copy(
selectedAmountType = selectedAmountType,
+ swapRateType = newSwapRateType,
)
}
+ startLoadingQuotesTask(isSilentReload = false)
}
override fun onInfoClick() {
@@ -290,6 +317,10 @@ internal class SwapAmountModel @Inject constructor(
}
}
+ override fun onRateClick() {
+ // TODO [REDACTED_TASK_KEY]
+ }
+
override fun onSeparatorClick() {
val amountParams = params as? SwapAmountComponentParams.AmountParams ?: return
@@ -335,17 +366,9 @@ internal class SwapAmountModel @Inject constructor(
private fun subscribeOnBalanceHiddenUpdates() {
params.isBalanceHidingFlow.onEach { isHidden ->
- val isOnlyOneWallet = getWalletsUseCase.invokeSync().size == 1
uiState.transformerUpdate(
SwapAmountBalanceHiddenTransformer(
isBalanceHidden = isHidden,
- isSingleWallet = isOnlyOneWallet,
- userWallet = userWallet,
- appCurrency = appCurrency,
- swapDirection = swapDirection,
- clickIntents = this,
- isAccountsMode = params.isAccountModeFlow.value,
- account = params.accountFlow.value,
),
)
}.launchIn(modelScope)
@@ -525,16 +548,14 @@ internal class SwapAmountModel @Inject constructor(
private fun initPairs(swapCurrencies: SwapCurrencies, secondaryCryptoCurrency: CryptoCurrency?) {
modelScope.launch {
- val swapCryptoCurrency = selectInitialPairUseCase(
+ val secondaryCurrency = selectInitialPairUseCase(
primaryCryptoCurrency = primaryCryptoCurrency,
secondaryCryptoCurrency = secondaryCryptoCurrency,
userWallet = userWallet,
swapCurrencies = swapCurrencies,
swapDirection = params.swapDirection,
)
-
- val secondaryStatus = swapCryptoCurrency?.currencyStatus
-
+ val secondaryStatus = secondaryCurrency?.currencyStatus
val primaryStatus = (uiState.value as? SwapAmountUM.Content)?.primaryCryptoCurrencyStatus
if (secondaryStatus != null && primaryStatus != null) {
initCurrencies(primaryStatus, secondaryStatus)
@@ -553,8 +574,14 @@ internal class SwapAmountModel @Inject constructor(
isSingleWallet = isOnlyOneWallet,
isAccountsMode = params.isAccountModeFlow.value,
account = params.accountFlow.value,
+ providers = secondaryCurrency.providers,
),
)
+
+ val currentState = uiState.value as? SwapAmountUM.Content
+ if (currentState != null && currentState.swapRateMode != SwapRateMode.FLOAT_ONLY) {
+ computeAndSetSecondaryAmount(currentState)
+ }
startLoadingQuotesTask(isSilentReload = false)
} else {
@Suppress("NullableToStringCall")
@@ -592,7 +619,11 @@ internal class SwapAmountModel @Inject constructor(
fiatRate = secondaryStatus.value.fiatRate,
fiatAmount = secondaryStatus.value.fiatAmount,
)
- secondaryMaximumAmountBoundary = MaxEnterAmountConverter().convert(secondaryStatus)
+ secondaryMaximumAmountBoundary = EnterAmountBoundary(
+ amount = null,
+ fiatRate = secondaryStatus.value.fiatRate,
+ fiatAmount = null,
+ )
}
}
@@ -606,18 +637,39 @@ internal class SwapAmountModel @Inject constructor(
onReverse = { state.secondaryCryptoCurrencyStatus.currency to state.primaryCryptoCurrencyStatus.currency },
)
- val fromAmount = when (state.swapDirection) {
- SwapDirection.Direct -> state.primaryAmount.amountField
- SwapDirection.Reverse -> state.secondaryAmount.amountField
- } as? AmountState.Data
+ val amountField = state.swapDirection.withSwapDirection(
+ onDirect = {
+ if (state.selectedAmountType == SwapAmountType.From) {
+ state.primaryAmount.amountField
+ } else {
+ state.secondaryAmount.amountField
+ }
+ },
+ onReverse = {
+ if (state.selectedAmountType == SwapAmountType.From) {
+ state.secondaryAmount.amountField
+ } else {
+ state.primaryAmount.amountField
+ }
+ },
+ ) as? AmountState.Data
- val fromAmountValue = fromAmount?.amountTextField?.cryptoAmount?.value.orZero()
+ val amountValue = amountField?.amountTextField?.cryptoAmount?.value.orZero()
val isAmountScreen = params is SwapAmountComponentParams.AmountParams
- val isAmountError = fromAmount?.amountTextField?.isError == true || fromAmountValue.isNullOrZero()
+ val isAmountError = amountField?.amountTextField?.isError == true || amountValue.isNullOrZero()
if (isAmountScreen && isAmountError) {
uiState.transformerUpdate(SwapQuoteEmptyStateTransformer); return
}
+ val rateType = when (state.selectedAmountType) {
+ SwapAmountType.To -> ExpressRateType.Fixed
+ SwapAmountType.From -> if (state.swapRateMode == SwapRateMode.FIXED_ONLY) {
+ ExpressRateType.Fixed
+ } else {
+ ExpressRateType.Float
+ }
+ }
+
if (!isSilentReload) uiState.transformerUpdate(SwapQuoteLoadingStateTransformer)
modelScope.launch {
@@ -625,6 +677,7 @@ internal class SwapAmountModel @Inject constructor(
.asSequence()
.filter { swapCurrencyStatus -> swapCurrencyStatus.currencyStatus.currency.id == toCryptoCurrency.id }
.flatMap(SwapCryptoCurrency::providers)
+ .filter { provider -> provider.rateTypes.contains(rateType) }
.toList()
.map { provider ->
async {
@@ -632,9 +685,9 @@ internal class SwapAmountModel @Inject constructor(
userWallet = userWallet,
fromCryptoCurrency = fromCryptoCurrency,
toCryptoCurrency = toCryptoCurrency,
- amount = fromAmountValue,
- amountType = SwapAmountType.From,
- rateType = ExpressRateType.Float,
+ amount = amountValue,
+ amountType = state.selectedAmountType,
+ rateType = rateType,
provider = provider,
).fold(
ifLeft = { error ->
@@ -650,7 +703,7 @@ internal class SwapAmountModel @Inject constructor(
swapDirection = swapDirection,
allowanceContract = quote.allowanceContract,
isApprovalNeeded = checkAllowance(state, quote),
- fromAmount = fromAmountValue,
+ fromAmount = amountValue,
).convert(
SwapQuoteUMConverter.Data(
quote = quote,
@@ -669,12 +722,48 @@ internal class SwapAmountModel @Inject constructor(
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
isSilentReload = isSilentReload,
isNeedApplyFcaRestrictions = userCountry.needApplyFCARestrictions(),
+ isBalanceHidden = params.isBalanceHidingFlow.value,
+ primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
+ primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
),
)
feeSelectorReloadTrigger.triggerUpdate()
}
}
+ /**
+ * Compute secondary amount from primary amount using fiat rate conversion
+ * secondaryAmount = primaryAmount × primaryFiatRate / secondaryFiatRate
+ *
+ */
+ private fun computeAndSetSecondaryAmount(state: SwapAmountUM.Content) {
+ val secondaryStatus = state.secondaryCryptoCurrencyStatus ?: return
+ val primaryFiatRate = state.primaryCryptoCurrencyStatus.value.fiatRate ?: return
+ val secondaryFiatRate = secondaryStatus.value.fiatRate ?: return
+ if (secondaryFiatRate <= BigDecimal.ZERO) return
+
+ val primaryAmountField = (state.primaryAmount as? SwapAmountFieldUM.Content)
+ ?.amountField as? AmountState.Data ?: return
+ val primaryAmount = primaryAmountField.amountTextField.cryptoAmount.value ?: return
+ if (primaryAmount <= BigDecimal.ZERO) return
+
+ val secondaryAmount = primaryAmount
+ .multiply(primaryFiatRate)
+ .divide(secondaryFiatRate, secondaryStatus.currency.decimals, java.math.RoundingMode.HALF_UP)
+
+ val secondaryValue = secondaryAmount.parseBigDecimal(secondaryStatus.currency.decimals)
+
+ uiState.transformerUpdate(
+ SwapAmountValueChangeTransformer(
+ primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
+ secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
+ primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
+ secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
+ value = secondaryValue,
+ ),
+ )
+ }
+
private fun startLoadingQuotesTask(isSilentReload: Boolean) {
quoteTaskScheduler.cancelTask()
loadQuotes(isSilentReload = isSilentReload)
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountQuoteUtils.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountQuoteUtils.kt
index f3b1614ce2..f0fee9face 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountQuoteUtils.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountQuoteUtils.kt
@@ -58,9 +58,9 @@ internal object SwapAmountQuoteUtils {
): SwapAmountUM {
if (this !is SwapAmountUM.Content) return this
- val updatedAmountField = if (
- selectedAmountType == SwapAmountType.From && swapDirection == SwapDirection.Direct
- ) {
+ val isPrimaryFieldEdited = selectedAmountType == SwapAmountType.From && swapDirection == SwapDirection.Direct
+
+ val updatedAmountField = if (isPrimaryFieldEdited) {
val amountFieldUM = primaryAmount as? SwapAmountFieldUM.Content ?: return this
amountFieldUM.onPrimaryAmount(primaryCryptoCurrencyStatus)
} else {
@@ -68,9 +68,9 @@ internal object SwapAmountQuoteUtils {
val amountFieldUM = secondaryAmount as? SwapAmountFieldUM.Content ?: return this
amountFieldUM.onSecondaryAmount(secondaryCryptoCurrencyStatus)
}
-
return copy(
- primaryAmount = updatedAmountField,
+ primaryAmount = if (isPrimaryFieldEdited) updatedAmountField else primaryAmount,
+ secondaryAmount = if (isPrimaryFieldEdited) secondaryAmount else updatedAmountField,
)
}
}
\ No newline at end of file
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapAmountFieldConverter.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapAmountFieldConverter.kt
index 9adad0ddeb..6bfb4de5bd 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapAmountFieldConverter.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapAmountFieldConverter.kt
@@ -5,12 +5,8 @@ import com.tangem.common.ui.amountScreen.converters.AmountAccountConverter
import com.tangem.common.ui.amountScreen.converters.AmountStateConverter
import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
import com.tangem.common.ui.amountScreen.models.AmountParameters
-import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.*
-import com.tangem.core.ui.format.bigdecimal.crypto
-import com.tangem.core.ui.format.bigdecimal.fiat
-import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
@@ -19,7 +15,7 @@ import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
-import com.tangem.utils.StringsSigns.DOT
+import java.math.BigDecimal
@Suppress("LongParameterList")
internal class SwapAmountFieldConverter(
@@ -36,24 +32,34 @@ internal class SwapAmountFieldConverter(
private val iconStateConverter = CryptoCurrencyToIconStateConverter()
private val maxEnterAmountConverter = MaxEnterAmountConverter()
- fun convert(selectedType: SwapAmountType, cryptoCurrencyStatus: CryptoCurrencyStatus): SwapAmountFieldUM {
+ fun convert(
+ swapAmountType: SwapAmountType,
+ cryptoCurrencyStatus: CryptoCurrencyStatus,
+ isSelected: Boolean,
+ isAmountEmpty: Boolean = true,
+ displayAmount: BigDecimal? = null,
+ ): SwapAmountFieldUM {
val walletTitle = if (isSingleWallet) {
resourceReference(R.string.send_from_title)
} else {
resourceReference(R.string.send_from_wallet_name, wrappedList(userWallet.name))
}
+ val subtitles = computeSubtitles(
+ swapAmountType = swapAmountType,
+ cryptoCurrencyStatus = cryptoCurrencyStatus,
+ isEntering = isSelected,
+ isAmountEmpty = isAmountEmpty,
+ displayAmount = displayAmount,
+ )
return SwapAmountFieldUM.Content(
- amountType = selectedType,
+ amountType = swapAmountType,
title = stringReference(cryptoCurrencyStatus.currency.name),
- subtitleLeft = getSubtitleLeft(selectedType = selectedType, cryptoCurrencyStatus = cryptoCurrencyStatus),
- subtitleEllipsisLeft = getSubtitleEllipsisLeft(
- selectedType = selectedType,
- cryptoCurrencyStatus = cryptoCurrencyStatus,
- ),
- subtitleRight = getSubtitleRight(selectedType = selectedType, cryptoCurrencyStatus = cryptoCurrencyStatus),
- subtitleEllipsisRight = TextEllipsis.OffsetEnd(appCurrency.symbol.length),
+ subtitleLeft = subtitles.subtitleLeft,
+ subtitleEllipsisLeft = subtitles.subtitleEllipsisLeft,
+ subtitleRight = subtitles.subtitleRight,
+ subtitleEllipsisRight = subtitles.subtitleEllipsisRight,
priceImpact = null,
- isClickEnabled = selectedType.isViewingField(),
+ isClickEnabled = true,
amountField = AmountStateConverter(
clickIntents = clickIntents,
appCurrency = appCurrency,
@@ -65,8 +71,8 @@ internal class SwapAmountFieldConverter(
isAccountsMode = isAccountsMode,
walletTitle = walletTitle,
prefixText = when {
- selectedType.isEnteringField() -> resourceReference(R.string.common_from)
- selectedType.isViewingField() -> resourceReference(R.string.common_to)
+ swapAmountType.isEnteringField() -> resourceReference(R.string.common_from)
+ swapAmountType.isViewingField() -> resourceReference(R.string.common_to)
else -> TextReference.Companion.EMPTY
},
).convert(account),
@@ -79,44 +85,29 @@ internal class SwapAmountFieldConverter(
)
}
- private fun getSubtitleLeft(selectedType: SwapAmountType, cryptoCurrencyStatus: CryptoCurrencyStatus) = when {
- selectedType.isEnteringField() -> combinedReference(
- stringReference(
- cryptoCurrencyStatus.value.amount.format {
- crypto(cryptoCurrency = cryptoCurrencyStatus.currency)
- },
- ),
- ).orMaskWithStars(isBalanceHidden)
- selectedType.isViewingField() -> resourceReference(R.string.send_with_swap_recipient_get_amount)
- else -> TextReference.Companion.EMPTY
+ private fun computeSubtitles(
+ swapAmountType: SwapAmountType,
+ cryptoCurrencyStatus: CryptoCurrencyStatus,
+ isEntering: Boolean,
+ isAmountEmpty: Boolean,
+ displayAmount: BigDecimal?,
+ ): SwapSubtitleResult = when (swapAmountType) {
+ SwapAmountType.From -> SwapFromSubtitleConverter.convert(
+ cryptoCurrencyStatus = cryptoCurrencyStatus,
+ isBalanceHidden = isBalanceHidden,
+ isEntering = isEntering,
+ isAmountEmpty = isAmountEmpty,
+ displayAmount = displayAmount,
+ )
+ SwapAmountType.To -> SwapToSubtitleConverter.convert(
+ cryptoCurrencyStatus = cryptoCurrencyStatus,
+ isBalanceHidden = isBalanceHidden,
+ isEntering = isEntering,
+ isAmountEmpty = isAmountEmpty,
+ displayAmount = displayAmount,
+ )
}
- private fun getSubtitleRight(selectedType: SwapAmountType, cryptoCurrencyStatus: CryptoCurrencyStatus) = when {
- selectedType.isEnteringField() -> if (isBalanceHidden) {
- TextReference.EMPTY
- } else {
- combinedReference(
- stringReference(value = " $DOT "),
- stringReference(
- cryptoCurrencyStatus.value.fiatAmount.format {
- fiat(
- fiatCurrencyCode = appCurrency.code,
- fiatCurrencySymbol = appCurrency.symbol,
- )
- },
- ),
- )
- }
- else -> TextReference.EMPTY
- }
-
- private fun getSubtitleEllipsisLeft(selectedType: SwapAmountType, cryptoCurrencyStatus: CryptoCurrencyStatus) =
- when {
- selectedType.isEnteringField() -> TextEllipsis.OffsetEnd(cryptoCurrencyStatus.currency.symbol.length)
- selectedType.isViewingField() -> TextEllipsis.End
- else -> TextEllipsis.End
- }
-
private fun SwapAmountType.isEnteringField(): Boolean {
return this == SwapAmountType.From && swapDirection == SwapDirection.Direct ||
this == SwapAmountType.To && swapDirection == SwapDirection.Reverse
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapAmountUpdateSubtitleConverter.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapAmountUpdateSubtitleConverter.kt
new file mode 100644
index 0000000000..1ed8a5c867
--- /dev/null
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapAmountUpdateSubtitleConverter.kt
@@ -0,0 +1,63 @@
+package com.tangem.features.swap.v2.impl.amount.model.converter
+
+import com.tangem.domain.models.currency.CryptoCurrencyStatus
+import com.tangem.domain.swap.models.SwapAmountType
+import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
+import java.math.BigDecimal
+
+/**
+ * Updates all subtitle fields on an existing [SwapAmountFieldUM.Content] field.
+ *
+ * Delegates to [SwapFromSubtitleConverter] / [SwapToSubtitleConverter] based on the field's
+ * [SwapAmountFieldUM.Content.amountType]. All other field properties are preserved.
+ *
+ * `isEntering` is derived internally: the field is "entering" when its amountType matches
+ * [selectedAmountType], so callers cannot accidentally pass a wrong value.
+ *
+ * @param selectedAmountType the currently selected amount type from [SwapAmountUM.Content]
+ * @param isBalanceHidden whether balance values should be masked
+ */
+internal class SwapAmountUpdateSubtitleConverter(
+ private val selectedAmountType: SwapAmountType,
+ private val isBalanceHidden: Boolean,
+) {
+
+ /**
+ * Updates all subtitle fields after a quote arrives.
+ *
+ * @param field the current field content to update
+ * @param cryptoCurrencyStatus status used for balance formatting
+ * @param isAmountEmpty whether the amount in this field is empty/null
+ * @param displayAmount optional override for the displayed amount (e.g. quote toAmount/fromAmount)
+ */
+ fun updateSubtitles(
+ field: SwapAmountFieldUM.Content,
+ cryptoCurrencyStatus: CryptoCurrencyStatus,
+ isAmountEmpty: Boolean,
+ displayAmount: BigDecimal? = null,
+ ): SwapAmountFieldUM.Content {
+ val isEntering = field.amountType == selectedAmountType
+ val subtitles = when (field.amountType) {
+ SwapAmountType.From -> SwapFromSubtitleConverter.convert(
+ cryptoCurrencyStatus = cryptoCurrencyStatus,
+ isBalanceHidden = isBalanceHidden,
+ isEntering = isEntering,
+ isAmountEmpty = isAmountEmpty,
+ displayAmount = displayAmount,
+ )
+ SwapAmountType.To -> SwapToSubtitleConverter.convert(
+ cryptoCurrencyStatus = cryptoCurrencyStatus,
+ isBalanceHidden = isBalanceHidden,
+ isEntering = isEntering,
+ isAmountEmpty = isAmountEmpty,
+ displayAmount = displayAmount,
+ )
+ }
+ return field.copy(
+ subtitleLeft = subtitles.subtitleLeft,
+ subtitleEllipsisLeft = subtitles.subtitleEllipsisLeft,
+ subtitleRight = subtitles.subtitleRight,
+ subtitleEllipsisRight = subtitles.subtitleEllipsisRight,
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapFromSubtitleConverter.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapFromSubtitleConverter.kt
new file mode 100644
index 0000000000..ed5c112fd0
--- /dev/null
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapFromSubtitleConverter.kt
@@ -0,0 +1,69 @@
+package com.tangem.features.swap.v2.impl.amount.model.converter
+
+import com.tangem.core.ui.components.atoms.text.TextEllipsis
+import com.tangem.core.ui.extensions.*
+import com.tangem.core.ui.format.bigdecimal.crypto
+import com.tangem.core.ui.format.bigdecimal.format
+import com.tangem.domain.models.currency.CryptoCurrencyStatus
+import com.tangem.features.swap.v2.impl.R
+import java.math.BigDecimal
+
+/**
+ * Computes all subtitle fields for the **From** (primary) field.
+ *
+ * | State | subtitleLeft | subtitleRight | ellipsisLeft |
+ * |--------------------------------|-----------------------------------|-----------------------------------|------------------------|
+ * | Entering (float), any | "Balance: " (empty param) | "{balance}" masked | OffsetEnd(symbol) |
+ * | Viewing (fixed), empty | "Balance: " (empty param) | "{balance}" masked (crypto only) | End |
+ * | Viewing (fixed), not empty | send_from_title | "{displayStr}" masked | OffsetEnd(symbol) |
+ */
+internal object SwapFromSubtitleConverter {
+
+ fun convert(
+ cryptoCurrencyStatus: CryptoCurrencyStatus,
+ isBalanceHidden: Boolean,
+ isEntering: Boolean,
+ isAmountEmpty: Boolean,
+ displayAmount: BigDecimal?,
+ ): SwapSubtitleResult {
+ val symbol = cryptoCurrencyStatus.currency.symbol
+ val balance = cryptoCurrencyStatus.value.amount.format {
+ crypto(cryptoCurrency = cryptoCurrencyStatus.currency)
+ }
+ val displayStr = displayAmount?.format {
+ crypto(cryptoCurrency = cryptoCurrencyStatus.currency)
+ } ?: balance
+
+ val subtitleLeft: TextReference
+ val subtitleRight: TextReference
+ val ellipsisLeft: TextEllipsis
+
+ when {
+ isEntering -> {
+ subtitleLeft = resourceReference(R.string.common_balance, wrappedList(""))
+ subtitleRight = combinedReference(stringReference(balance))
+ .orMaskWithStars(isBalanceHidden)
+ ellipsisLeft = TextEllipsis.OffsetEnd(symbol.length)
+ }
+ !isEntering && isAmountEmpty -> {
+ subtitleLeft = resourceReference(R.string.common_balance, wrappedList(""))
+ subtitleRight = combinedReference(stringReference(balance))
+ .orMaskWithStars(isBalanceHidden)
+ ellipsisLeft = TextEllipsis.End
+ }
+ else -> {
+ subtitleLeft = resourceReference(R.string.send_from_title)
+ subtitleRight = combinedReference(stringReference(displayStr))
+ .orMaskWithStars(isBalanceHidden)
+ ellipsisLeft = TextEllipsis.OffsetEnd(symbol.length)
+ }
+ }
+
+ return SwapSubtitleResult(
+ subtitleLeft = subtitleLeft,
+ subtitleRight = subtitleRight,
+ subtitleEllipsisLeft = ellipsisLeft,
+ subtitleEllipsisRight = TextEllipsis.OffsetEnd(symbol.length),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapQuoteUMConverter.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapQuoteUMConverter.kt
index c014e539e7..3f5255f097 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapQuoteUMConverter.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapQuoteUMConverter.kt
@@ -42,6 +42,10 @@ internal class SwapQuoteUMConverter(
append(rate.format { crypto(secondaryCurrency) })
}
+ val fromAmountValue = stringReference(
+ quote.fromTokenAmount?.format { crypto(primaryCurrency) }.orEmpty(),
+ )
+
return if (allowanceContract != null) {
if (isApprovalNeeded) {
SwapQuoteUM.Allowance(
@@ -51,11 +55,13 @@ internal class SwapQuoteUMConverter(
} else {
SwapQuoteUM.Content(
provider = provider,
- quoteAmount = quote.toTokenAmount,
+ toAmount = quote.toTokenAmount,
+ fromAmount = quote.fromTokenAmount,
diffPercent = DifferencePercent.Empty,
- quoteAmountValue = stringReference(
+ toAmountValue = stringReference(
quote.toTokenAmount.toQuoteValue(),
),
+ fromAmountValue = fromAmountValue,
rate = annotatedReference(rateString),
isSingleProvider = false,
)
@@ -63,11 +69,13 @@ internal class SwapQuoteUMConverter(
} else {
SwapQuoteUM.Content(
provider = provider,
- quoteAmount = quote.toTokenAmount,
+ toAmount = quote.toTokenAmount,
+ fromAmount = quote.fromTokenAmount,
diffPercent = DifferencePercent.Empty,
- quoteAmountValue = stringReference(
+ toAmountValue = stringReference(
quote.toTokenAmount.toQuoteValue(),
),
+ fromAmountValue = fromAmountValue,
rate = annotatedReference(rateString),
isSingleProvider = false,
)
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapSubtitleResult.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapSubtitleResult.kt
new file mode 100644
index 0000000000..3253f56f44
--- /dev/null
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapSubtitleResult.kt
@@ -0,0 +1,11 @@
+package com.tangem.features.swap.v2.impl.amount.model.converter
+
+import com.tangem.core.ui.components.atoms.text.TextEllipsis
+import com.tangem.core.ui.extensions.TextReference
+
+internal data class SwapSubtitleResult(
+ val subtitleLeft: TextReference,
+ val subtitleRight: TextReference,
+ val subtitleEllipsisLeft: TextEllipsis,
+ val subtitleEllipsisRight: TextEllipsis,
+)
\ No newline at end of file
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapToSubtitleConverter.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapToSubtitleConverter.kt
new file mode 100644
index 0000000000..cba9789884
--- /dev/null
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapToSubtitleConverter.kt
@@ -0,0 +1,73 @@
+package com.tangem.features.swap.v2.impl.amount.model.converter
+
+import com.tangem.core.ui.components.atoms.text.TextEllipsis
+import com.tangem.core.ui.extensions.*
+import com.tangem.core.ui.format.bigdecimal.crypto
+import com.tangem.core.ui.format.bigdecimal.format
+import com.tangem.domain.models.currency.CryptoCurrencyStatus
+import com.tangem.features.swap.v2.impl.R
+import com.tangem.utils.StringsSigns.TILDE_SIGN
+import java.math.BigDecimal
+
+/**
+ * Computes all subtitle fields for the **To** (secondary) field.
+ *
+ * | State | subtitleLeft | subtitleRight | ellipsisLeft |
+ * |--------------------------------|------------------------------------|----------------------------|------------------------|
+ * | Viewing (float), empty | "Will be sent to recipient" | EMPTY | End |
+ * | Viewing (float), not empty | "Recipient gets" (empty param) | "~{displayStr}" masked | End |
+ * | Entering (fixed), any | "Will be sent to recipient" | EMPTY | End |
+ */
+internal object SwapToSubtitleConverter {
+
+ fun convert(
+ cryptoCurrencyStatus: CryptoCurrencyStatus,
+ isBalanceHidden: Boolean,
+ isEntering: Boolean,
+ isAmountEmpty: Boolean,
+ displayAmount: BigDecimal?,
+ ): SwapSubtitleResult {
+ val symbol = cryptoCurrencyStatus.currency.symbol
+ val balance = cryptoCurrencyStatus.value.amount.format {
+ crypto(cryptoCurrency = cryptoCurrencyStatus.currency)
+ }
+ val displayStr = displayAmount?.format {
+ crypto(cryptoCurrency = cryptoCurrencyStatus.currency)
+ } ?: balance
+
+ val subtitleLeft: TextReference
+ val subtitleRight: TextReference
+ val ellipsisLeft: TextEllipsis
+
+ when {
+ isEntering -> {
+ subtitleLeft = resourceReference(R.string.send_amount_receive_token_subtitle)
+ subtitleRight = TextReference.EMPTY
+ ellipsisLeft = TextEllipsis.End
+ }
+ !isEntering && isAmountEmpty -> {
+ subtitleLeft = resourceReference(R.string.send_amount_receive_token_subtitle)
+ subtitleRight = TextReference.EMPTY
+ ellipsisLeft = TextEllipsis.End
+ }
+ else -> {
+ subtitleLeft = resourceReference(
+ R.string.send_with_swap_recipient_get_amount,
+ wrappedList(""),
+ )
+ subtitleRight = combinedReference(
+ stringReference(TILDE_SIGN),
+ stringReference(displayStr),
+ ).orMaskWithStars(isBalanceHidden)
+ ellipsisLeft = TextEllipsis.End
+ }
+ }
+
+ return SwapSubtitleResult(
+ subtitleLeft = subtitleLeft,
+ subtitleRight = subtitleRight,
+ subtitleEllipsisLeft = ellipsisLeft,
+ subtitleEllipsisRight = TextEllipsis.OffsetEnd(symbol.length),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountBalanceHiddenTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountBalanceHiddenTransformer.kt
index ede7a349fb..1fe1635001 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountBalanceHiddenTransformer.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountBalanceHiddenTransformer.kt
@@ -1,70 +1,55 @@
package com.tangem.features.swap.v2.impl.amount.model.transformers
-import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.common.ui.amountScreen.models.AmountState
-import com.tangem.domain.appcurrency.model.AppCurrency
-import com.tangem.domain.models.account.Account
-import com.tangem.domain.models.wallet.UserWallet
-import com.tangem.domain.swap.models.SwapAmountType
-import com.tangem.domain.swap.models.SwapDirection
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
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.amount.model.converter.SwapAmountUpdateSubtitleConverter
+import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.utils.transformer.Transformer
-@Suppress("LongParameterList")
internal class SwapAmountBalanceHiddenTransformer(
private val isBalanceHidden: Boolean,
- private val isSingleWallet: Boolean,
- private val userWallet: UserWallet,
- private val appCurrency: AppCurrency,
- private val swapDirection: SwapDirection,
- private val clickIntents: AmountScreenClickIntents,
- private val isAccountsMode: Boolean,
- private val account: Account?,
) : Transformer {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
val content = prevState as? SwapAmountUM.Content ?: return prevState
- val amountFieldConverter = SwapAmountFieldConverter(
- swapDirection = swapDirection,
+ val quoteContent = content.selectedQuote as? SwapQuoteUM.Content
+
+ val subtitleConverter = SwapAmountUpdateSubtitleConverter(
+ selectedAmountType = content.selectedAmountType,
isBalanceHidden = isBalanceHidden,
- userWallet = userWallet,
- appCurrency = appCurrency,
- clickIntents = clickIntents,
- isSingleWallet = isSingleWallet,
- isAccountsMode = isAccountsMode,
- account = account,
)
- val recalculatedPrimary = amountFieldConverter.convert(
- selectedType = SwapAmountType.From,
- cryptoCurrencyStatus = content.primaryCryptoCurrencyStatus,
- ) as SwapAmountFieldUM.Content
-
- val oldPrimary = content.primaryAmount as? SwapAmountFieldUM.Content
-
- val mergedAmountField = if (
- oldPrimary?.amountField is AmountState.Data && recalculatedPrimary.amountField is AmountState.Data
- ) {
- val oldData = oldPrimary.amountField
- val newData = recalculatedPrimary.amountField
- newData.copy(
- amountTextField = oldData.amountTextField,
- isPrimaryButtonEnabled = oldData.isPrimaryButtonEnabled,
- isEditingDisabled = oldData.isEditingDisabled,
- reduceAmountBy = oldData.reduceAmountBy,
- isIgnoreReduce = oldData.isIgnoreReduce,
+ val updatedPrimary = (content.primaryAmount as? SwapAmountFieldUM.Content)?.let { primary ->
+ val isAmountEmpty = (primary.amountField as? AmountState.Data)
+ ?.amountTextField?.cryptoAmount?.value == null
+ subtitleConverter.updateSubtitles(
+ field = primary,
+ cryptoCurrencyStatus = content.primaryCryptoCurrencyStatus,
+ isAmountEmpty = isAmountEmpty,
+ displayAmount = quoteContent?.fromAmount,
)
+ } ?: content.primaryAmount
+
+ val updatedSecondary = if (content.secondaryCryptoCurrencyStatus != null) {
+ (content.secondaryAmount as? SwapAmountFieldUM.Content)?.let { secondary ->
+ val isAmountEmpty = (secondary.amountField as? AmountState.Data)
+ ?.amountTextField?.cryptoAmount?.value == null
+ subtitleConverter.updateSubtitles(
+ field = secondary,
+ cryptoCurrencyStatus = content.secondaryCryptoCurrencyStatus,
+ isAmountEmpty = isAmountEmpty,
+ displayAmount = quoteContent?.toAmount,
+ )
+ } ?: content.secondaryAmount
} else {
- recalculatedPrimary.amountField
+ content.secondaryAmount
}
- val updatedPrimaryAmount = recalculatedPrimary.copy(
- amountField = mergedAmountField,
+ return content.copy(
+ primaryAmount = updatedPrimary,
+ secondaryAmount = updatedSecondary,
)
-
- return content.copy(primaryAmount = updatedPrimaryAmount)
}
}
\ No newline at end of file
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountErrorQuoteTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountErrorQuoteTransformer.kt
new file mode 100644
index 0000000000..edb2d9e257
--- /dev/null
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountErrorQuoteTransformer.kt
@@ -0,0 +1,58 @@
+package com.tangem.features.swap.v2.impl.amount.model.transformers
+
+import com.tangem.common.ui.amountScreen.models.AmountState
+import com.tangem.core.ui.extensions.resourceReference
+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.SwapAmountUM
+import com.tangem.domain.swap.models.SwapAmountType
+import com.tangem.utils.transformer.Transformer
+
+/**
+ * Sets a generic "Something went wrong" error on the selected amount field
+ * when all quotes returned errors.
+ */
+internal object SwapAmountErrorQuoteTransformer : Transformer {
+
+ override fun transform(prevState: SwapAmountUM): SwapAmountUM {
+ if (prevState !is SwapAmountUM.Content) return prevState
+
+ val error = resourceReference(R.string.send_with_swap_something_went_wrong)
+
+ val isPrimarySelected = prevState.selectedAmountType == SwapAmountType.From
+
+ val newPrimaryAmount = if (isPrimarySelected) {
+ applyError(prevState.primaryAmount, error)
+ } else {
+ prevState.primaryAmount
+ }
+
+ val newSecondaryAmount = if (!isPrimarySelected) {
+ applyError(prevState.secondaryAmount, error)
+ } else {
+ prevState.secondaryAmount
+ }
+
+ return prevState.copy(
+ isPrimaryButtonEnabled = false,
+ primaryAmount = newPrimaryAmount,
+ secondaryAmount = newSecondaryAmount,
+ )
+ }
+
+ private fun applyError(
+ field: SwapAmountFieldUM,
+ error: com.tangem.core.ui.extensions.TextReference,
+ ): SwapAmountFieldUM {
+ val content = field as? SwapAmountFieldUM.Content ?: return field
+ val amountData = content.amountField as? AmountState.Data ?: return field
+ return content.copy(
+ amountField = amountData.copy(
+ amountTextField = amountData.amountTextField.copy(
+ error = error,
+ isError = true,
+ ),
+ ),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountPrimaryReadyStateTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountPrimaryReadyStateTransformer.kt
index bb5046a48d..e2d648a6c9 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountPrimaryReadyStateTransformer.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountPrimaryReadyStateTransformer.kt
@@ -9,6 +9,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.domain.swap.models.SwapCurrencies
import com.tangem.domain.swap.models.SwapDirection
+import com.tangem.domain.swap.models.SwapRateMode
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.converter.SwapAmountFieldConverter
@@ -46,8 +47,9 @@ internal class SwapAmountPrimaryReadyStateTransformer(
isPrimaryButtonEnabled = false,
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
primaryAmount = amountFieldConverter.convert(
- selectedType = SwapAmountType.From,
+ swapAmountType = SwapAmountType.From,
cryptoCurrencyStatus = primaryCryptoCurrencyStatus,
+ isSelected = prevState.selectedAmountType == SwapAmountType.From,
),
secondaryCryptoCurrencyStatus = null,
secondaryAmount = SwapAmountFieldUM.Empty(
@@ -62,6 +64,7 @@ internal class SwapAmountPrimaryReadyStateTransformer(
appCurrency = appCurrency,
isShowBestRateAnimation = isShowBestRateAnimation,
isShowFCAWarning = false,
+ swapRateMode = (prevState as? SwapAmountUM.Content)?.swapRateMode ?: SwapRateMode.FLOAT_ONLY,
)
}
}
\ No newline at end of file
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSecondaryReadyStateTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSecondaryReadyStateTransformer.kt
index 545c13db6e..cc49a10c46 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSecondaryReadyStateTransformer.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSecondaryReadyStateTransformer.kt
@@ -2,13 +2,15 @@ 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.ExpressProvider
import com.tangem.domain.express.models.ExpressRateType
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
-import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.domain.swap.models.SwapCurrencies
import com.tangem.domain.swap.models.SwapDirection
+import com.tangem.domain.swap.models.SwapAmountType
+import com.tangem.domain.swap.models.SwapRateMode
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
@@ -20,6 +22,7 @@ internal class SwapAmountSecondaryReadyStateTransformer(
private val userWallet: UserWallet,
private val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
private val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus,
+ private val providers: List,
private val appCurrency: AppCurrency,
private val swapCurrencies: SwapCurrencies,
private val clickIntents: AmountScreenClickIntents,
@@ -43,24 +46,41 @@ internal class SwapAmountSecondaryReadyStateTransformer(
)
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
+ val rateTypes = providers.flatMapTo(mutableSetOf()) { it.rateTypes }
+ val swapRateMode = when {
+ rateTypes.containsAll(listOf(ExpressRateType.Float, ExpressRateType.Fixed)) -> SwapRateMode.FLOAT_AND_FIXED
+ rateTypes.contains(ExpressRateType.Fixed) -> SwapRateMode.FIXED_ONLY
+ else -> SwapRateMode.FLOAT_ONLY
+ }
+ val selectedAmountType = if (swapRateMode != SwapRateMode.FLOAT_ONLY) {
+ ExpressRateType.Fixed
+ } else {
+ ExpressRateType.Float
+ }
return SwapAmountUM.Content(
isPrimaryButtonEnabled = false,
primaryAmount = prevState.primaryAmount,
primaryCryptoCurrencyStatus = primaryCryptoCurrencyStatus,
secondaryAmount = amountFieldConverter.convert(
- selectedType = SwapAmountType.To,
+ swapAmountType = SwapAmountType.To,
cryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
+ isSelected = prevState.selectedAmountType == SwapAmountType.To,
),
secondaryCryptoCurrencyStatus = secondaryCryptoCurrencyStatus,
swapCurrencies = swapCurrencies,
- selectedAmountType = prevState.selectedAmountType,
+ selectedAmountType = if (selectedAmountType == ExpressRateType.Fixed) {
+ SwapAmountType.To
+ } else {
+ SwapAmountType.From
+ },
swapDirection = swapDirection,
- swapRateType = ExpressRateType.Float,
+ swapRateType = selectedAmountType,
swapQuotes = persistentListOf(),
selectedQuote = SwapQuoteUM.Empty,
appCurrency = appCurrency,
isShowBestRateAnimation = isShowBestRateAnimation,
isShowFCAWarning = false,
+ swapRateMode = swapRateMode,
)
}
}
\ No newline at end of file
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSelectQuoteTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSelectQuoteTransformer.kt
index 3f3f9ac158..8e1878c21c 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSelectQuoteTransformer.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSelectQuoteTransformer.kt
@@ -4,12 +4,15 @@ import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTrans
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.utils.parseBigDecimal
import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
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.amount.model.converter.SwapAmountUpdateSubtitleConverter
+import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.common.isRestrictedByFCA
import com.tangem.utils.extensions.orZero
@@ -20,63 +23,162 @@ internal class SwapAmountSelectQuoteTransformer(
private val secondaryMaximumAmountBoundary: EnterAmountBoundary?,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary?,
private val isNeedApplyFCARestrictions: Boolean,
+ private val isBalanceHidden: Boolean,
+ private val primaryMaximumAmountBoundary: EnterAmountBoundary? = null,
+ private val primaryMinimumAmountBoundary: EnterAmountBoundary? = null,
) : Transformer {
+
+ @Suppress("CyclomaticComplexMethod", "LongMethod")
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
- val providerErrorConverter = SwapAmountErrorConverter(
+ val isPrimarySelected = prevState.selectedAmountType == SwapAmountType.From
+ val isSecondarySelected = prevState.selectedAmountType == SwapAmountType.To
+
+ val primaryProviderErrorConverter = SwapAmountErrorConverter(
cryptoCurrency = prevState.primaryCryptoCurrencyStatus.currency,
)
+ val secondaryProviderErrorConverter = prevState.secondaryCryptoCurrencyStatus?.let {
+ SwapAmountErrorConverter(cryptoCurrency = it.currency)
+ }
- return prevState.copy(
- isPrimaryButtonEnabled = quoteUM is SwapQuoteUM.Content,
- selectedQuote = quoteUM,
- isShowFCAWarning = isNeedApplyFCARestrictions && quoteUM.provider?.isRestrictedByFCA() == true,
- primaryAmount = if (prevState.selectedAmountType == SwapAmountType.From) {
- val swapAmountField = prevState.primaryAmount as? SwapAmountFieldUM.Content
- val amountField = swapAmountField?.amountField as? AmountState.Data
+ val quoteContent = quoteUM as? SwapQuoteUM.Content
+ val fromAmount = quoteContent?.fromAmount
+ val toAmount = quoteContent?.toAmount
- val amountError = (quoteUM as? SwapQuoteUM.Error)?.expressError?.let(providerErrorConverter::convert)
+ val primarySwapAmountField = prevState.primaryAmount as? SwapAmountFieldUM.Content
+ val secondarySwapAmountField = prevState.secondaryAmount as? SwapAmountFieldUM.Content
- swapAmountField?.copy(
+ val subtitleConverter = SwapAmountUpdateSubtitleConverter(
+ selectedAmountType = prevState.selectedAmountType,
+ isBalanceHidden = isBalanceHidden,
+ )
+
+ val newPrimaryAmount = when {
+ fromAmount != null && primaryMaximumAmountBoundary != null -> {
+ primarySwapAmountField?.let { fromField ->
+ subtitleConverter.updateSubtitles(
+ field = fromField,
+ cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
+ isAmountEmpty = false,
+ displayAmount = fromAmount,
+ ).copy(
+ amountField = AmountFieldChangeTransformer(
+ cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
+ maxEnterAmount = primaryMaximumAmountBoundary,
+ minimumTransactionAmount = primaryMinimumAmountBoundary,
+ value = fromAmount.parseBigDecimal(
+ prevState.primaryCryptoCurrencyStatus.currency.decimals,
+ ),
+ ).transform(fromField.amountField),
+ )
+ } ?: prevState.primaryAmount
+ }
+ isPrimarySelected -> {
+ val amountField = primarySwapAmountField?.amountField as? AmountState.Data
+ val amountError = (quoteUM as? SwapQuoteUM.Error)?.expressError
+ ?.let(primaryProviderErrorConverter::convert)
+ primarySwapAmountField?.copy(
amountField = amountField?.copy(
amountTextField = amountField.amountTextField.copy(
error = amountError ?: TextReference.EMPTY,
isError = amountError != null,
),
- ) ?: swapAmountField.amountField,
+ ) ?: primarySwapAmountField.amountField,
) ?: prevState.primaryAmount
- } else {
- prevState.primaryAmount
- },
- 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()
- val toAmount = (quoteUM as? SwapQuoteUM.Content)?.quoteAmount
- val priceImpact = calculatePriceImpact(
- swapDirection = prevState.swapDirection,
- fromTokenAmount = fromAmount,
- toTokenAmount = toAmount.orZero(),
- primaryCryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
- secondaryCryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
- )
-
- secondaryAmountField?.copy(
- priceImpact = priceImpact,
+ }
+ !isPrimarySelected && primarySwapAmountField != null && primaryMaximumAmountBoundary != null -> {
+ subtitleConverter.updateSubtitles(
+ field = primarySwapAmountField,
+ cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
+ isAmountEmpty = true,
+ ).copy(
amountField = AmountFieldChangeTransformer(
- cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
- maxEnterAmount = secondaryMaximumAmountBoundary,
- minimumTransactionAmount = secondaryMinimumAmountBoundary,
- value = toAmount?.parseBigDecimal(prevState.secondaryCryptoCurrencyStatus.currency.decimals)
- .orEmpty(),
- ).transform(secondaryAmountField.amountField),
- ) ?: prevState.secondaryAmount
+ cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
+ maxEnterAmount = primaryMaximumAmountBoundary,
+ minimumTransactionAmount = primaryMinimumAmountBoundary,
+ value = "",
+ ).transform(primarySwapAmountField.amountField),
+ )
+ }
+ else -> prevState.primaryAmount
+ }
+
+ val newSecondaryAmount = if (
+ prevState.secondaryCryptoCurrencyStatus != null &&
+ secondaryMaximumAmountBoundary != null &&
+ secondarySwapAmountField != null
+ ) {
+ val fromAmountForPriceImpact = fromAmount
+ ?: (prevState.primaryAmount.amountField as? AmountState.Data)
+ ?.amountTextField?.cryptoAmount?.value.orZero()
+ val priceImpact = calculatePriceImpact(
+ swapDirection = prevState.swapDirection,
+ fromTokenAmount = fromAmountForPriceImpact,
+ toTokenAmount = toAmount.orZero(),
+ primaryCryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus,
+ secondaryCryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
+ )
+ val isAmountEmpty = toAmount == null
+ val secondaryAmountError = if (isSecondarySelected) {
+ (quoteUM as? SwapQuoteUM.Error)?.expressError
+ ?.let { secondaryProviderErrorConverter?.convert(it) }
} else {
- prevState.secondaryAmount
- },
+ null
+ }
+ val transformedSecondaryAmountField = if (isSecondarySelected && toAmount == null) {
+ secondarySwapAmountField.amountField
+ } else {
+ AmountFieldChangeTransformer(
+ cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
+ maxEnterAmount = secondaryMaximumAmountBoundary,
+ minimumTransactionAmount = secondaryMinimumAmountBoundary,
+ value = toAmount?.parseBigDecimal(prevState.secondaryCryptoCurrencyStatus.currency.decimals)
+ .orEmpty(),
+ ).transform(secondarySwapAmountField.amountField)
+ }
+ val insufficientFundsError = if (isSecondarySelected && fromAmount != null) {
+ val primaryBalance = prevState.primaryCryptoCurrencyStatus.value.amount
+ if (primaryBalance != null && fromAmount > primaryBalance) {
+ resourceReference(R.string.swapping_insufficient_funds)
+ } else {
+ null
+ }
+ } else {
+ null
+ }
+ val effectiveSecondaryError = secondaryAmountError ?: insufficientFundsError
+ val secondaryAmountFieldWithError = if (isSecondarySelected) {
+ (transformedSecondaryAmountField as? AmountState.Data)?.let { data ->
+ data.copy(
+ amountTextField = data.amountTextField.copy(
+ error = effectiveSecondaryError ?: TextReference.EMPTY,
+ isError = effectiveSecondaryError != null,
+ ),
+ )
+ } ?: transformedSecondaryAmountField
+ } else {
+ transformedSecondaryAmountField
+ }
+ subtitleConverter.updateSubtitles(
+ field = secondarySwapAmountField,
+ cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus,
+ isAmountEmpty = isAmountEmpty,
+ displayAmount = toAmount,
+ ).copy(
+ priceImpact = priceImpact,
+ amountField = secondaryAmountFieldWithError,
+ )
+ } else {
+ prevState.secondaryAmount
+ }
+
+ return prevState.copy(
+ isPrimaryButtonEnabled = quoteUM is SwapQuoteUM.Content,
+ selectedQuote = quoteUM,
+ isShowFCAWarning = isNeedApplyFCARestrictions && quoteUM.provider?.isRestrictedByFCA() == true,
+ primaryAmount = newPrimaryAmount,
+ secondaryAmount = newSecondaryAmount,
)
}
}
\ No newline at end of file
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSetQuotesTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSetQuotesTransformer.kt
index cd19623928..9349099eb2 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSetQuotesTransformer.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSetQuotesTransformer.kt
@@ -5,6 +5,7 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.domain.express.models.ExpressError
+import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM.Content.DifferencePercent
@@ -23,18 +24,29 @@ internal class SwapAmountSetQuotesTransformer(
private val secondaryMinimumAmountBoundary: EnterAmountBoundary?,
private val isSilentReload: Boolean,
private val isNeedApplyFcaRestrictions: Boolean,
+ private val isBalanceHidden: Boolean,
+ private val primaryMaximumAmountBoundary: EnterAmountBoundary? = null,
+ private val primaryMinimumAmountBoundary: EnterAmountBoundary? = null,
) : Transformer {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
+ val selectedAmountType = prevState.selectedAmountType
+ val comparator = SwapQuotesComparator(selectedAmountType)
+
val isSingleProvider = quotes.filter { swapQuoteUM ->
swapQuoteUM is SwapQuoteUM.Content || swapQuoteUM is SwapQuoteUM.Allowance ||
(swapQuoteUM as? SwapQuoteUM.Error)?.expressError is ExpressError.AmountError
}.isSingleItem()
- val sortedQuotes = quotes.sortedWith(SwapQuotesComparator)
- val bestQuote = findBestQuote(quotes) ?: SwapQuoteUM.Empty
- val quotesWithDiff = getQuotesWithDiff(sortedQuotes, bestQuote, isSingleProvider)
+ val sortedQuotes = quotes.sortedWith(comparator)
+ val bestQuote = findBestQuote(quotes, comparator) ?: SwapQuoteUM.Empty
+ val quotesWithDiff = getQuotesWithDiff(
+ sortedQuotes = sortedQuotes,
+ bestQuote = bestQuote,
+ isSingleProvider = isSingleProvider,
+ selectedAmountType = selectedAmountType,
+ )
val selectedQuote = if (isSilentReload && prevState.selectedQuote !is SwapQuoteUM.Loading) {
quotesWithDiff.firstOrNull { it.provider?.providerId == prevState.selectedQuote.provider?.providerId }
?: prevState.selectedQuote
@@ -51,14 +63,30 @@ internal class SwapAmountSetQuotesTransformer(
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
isNeedApplyFCARestrictions = isNeedApplyFcaRestrictions &&
selectedQuote.provider?.isRestrictedByFCA() == true,
+ isBalanceHidden = isBalanceHidden,
+ primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
+ primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
)
val updatedState = selectQuoteTransformer.transform(prevState = prevState)
if (updatedState !is SwapAmountUM.Content) return prevState
- return updatedState.copy(
- isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled && quotesWithDiff.isNotEmpty(),
- swapQuotes = getQuotesWithDiff(sortedQuotes, bestQuote, isSingleProvider),
+ val areAllQuotesErrors = quotes.all { it is SwapQuoteUM.Error }
+ val stateWithError = if (areAllQuotesErrors) {
+ SwapAmountErrorQuoteTransformer.transform(updatedState)
+ } else {
+ updatedState
+ }
+ if (stateWithError !is SwapAmountUM.Content) return prevState
+
+ return stateWithError.copy(
+ isPrimaryButtonEnabled = stateWithError.isPrimaryButtonEnabled && quotesWithDiff.isNotEmpty(),
+ swapQuotes = getQuotesWithDiff(
+ sortedQuotes = sortedQuotes,
+ bestQuote = bestQuote,
+ isSingleProvider = isSingleProvider,
+ selectedAmountType = selectedAmountType,
+ ),
)
}
@@ -66,54 +94,75 @@ internal class SwapAmountSetQuotesTransformer(
sortedQuotes: List,
bestQuote: SwapQuoteUM,
isSingleProvider: Boolean,
+ selectedAmountType: SwapAmountType,
): ImmutableList {
- return sortedQuotes.sortedWith(SwapQuotesComparator)
- .map { quote ->
- if (quote is SwapQuoteUM.Content && bestQuote is SwapQuoteUM.Content) {
- if (quote.provider.providerId == bestQuote.provider.providerId) {
- quote.copy(
- diffPercent = DifferencePercent.Best,
- isSingleProvider = isSingleProvider,
- )
- } else {
- // current / selected - 1
- val percent = quote.quoteAmount / bestQuote.quoteAmount - BigDecimal.ONE
- quote.copy(
- diffPercent = DifferencePercent.Diff(
- isPositive = percent.isPositive(),
- percent = stringReference(
- if (percent.isPositive()) {
- "${StringsSigns.PLUS}${percent.format { percent() }}"
- } else {
- "${StringsSigns.DASH_SIGN}${percent.format { percent() }}"
- },
- ),
- ),
- )
- }
+ return sortedQuotes.map { quote ->
+ if (quote is SwapQuoteUM.Content && bestQuote is SwapQuoteUM.Content) {
+ if (quote.provider.providerId == bestQuote.provider.providerId) {
+ quote.copy(
+ diffPercent = DifferencePercent.Best,
+ isSingleProvider = isSingleProvider,
+ )
} else {
- quote
+ val percent = if (selectedAmountType == SwapAmountType.To) {
+ // Fixed mode: best has lowest fromTokenAmount; compare as (best/current - 1)
+ val bestFrom = bestQuote.fromAmount
+ val quoteFrom = quote.fromAmount
+ if (bestFrom != null && quoteFrom != null && quoteFrom > BigDecimal.ZERO) {
+ bestFrom / quoteFrom - BigDecimal.ONE
+ } else {
+ BigDecimal.ZERO
+ }
+ } else {
+ // Float mode: best has highest toAmount; compare as (current/best - 1)
+ quote.toAmount / bestQuote.toAmount - BigDecimal.ONE
+ }
+ quote.copy(
+ diffPercent = DifferencePercent.Diff(
+ isPositive = percent.isPositive(),
+ percent = stringReference(
+ if (percent.isPositive()) {
+ "${StringsSigns.PLUS}${percent.format { percent() }}"
+ } else {
+ "${StringsSigns.DASH_SIGN}${percent.format { percent() }}"
+ },
+ ),
+ ),
+ )
}
- }.toPersistentList()
+ } else {
+ quote
+ }
+ }.toPersistentList()
}
- private fun findBestQuote(quotes: List): SwapQuoteUM? {
- return quotes
- .sortedWith(SwapQuotesComparator)
- .firstOrNull()
+ private fun findBestQuote(quotes: List, comparator: Comparator): SwapQuoteUM? {
+ return quotes.sortedWith(comparator).firstOrNull()
}
- private object SwapQuotesComparator : Comparator {
+ private class SwapQuotesComparator(private val selectedAmountType: SwapAmountType) : Comparator {
override fun compare(p0: SwapQuoteUM?, p1: SwapQuoteUM?): Int {
return when {
p0 is SwapQuoteUM.Content && p1 !is SwapQuoteUM.Content -> -1
p0 !is SwapQuoteUM.Content && p1 is SwapQuoteUM.Content -> 1
p0 is SwapQuoteUM.Error && p1 is SwapQuoteUM.Error -> compareErrorQuote(p0 = p0, p1 = p1)
- p0 is SwapQuoteUM.Content && p1 is SwapQuoteUM.Content -> p1.quoteAmount.compareTo(p0.quoteAmount)
+ p0 is SwapQuoteUM.Content && p1 is SwapQuoteUM.Content -> compareContent(p0, p1)
else -> 0
}
}
+ private fun compareContent(p0: SwapQuoteUM.Content, p1: SwapQuoteUM.Content): Int {
+ return if (selectedAmountType == SwapAmountType.To) {
+ // Fixed mode: lower fromTokenAmount = better (ascending)
+ val f0 = p0.fromAmount ?: BigDecimal.ZERO
+ val f1 = p1.fromAmount ?: BigDecimal.ZERO
+ f0.compareTo(f1)
+ } else {
+ // Float mode: higher toAmount = better (descending)
+ p1.toAmount.compareTo(p0.toAmount)
+ }
+ }
+
private fun compareErrorQuote(p0: SwapQuoteUM.Error, p1: SwapQuoteUM.Error): Int = when {
p0.expressError is ExpressError.AmountError && p1.expressError !is ExpressError.AmountError -> -1
p0.expressError !is ExpressError.AmountError && p1.expressError is ExpressError.AmountError -> 1
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/SwapAmountBlockContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/SwapAmountBlockContent.kt
index b4df59fefc..d75eb733a2 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/SwapAmountBlockContent.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/SwapAmountBlockContent.kt
@@ -37,9 +37,9 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
-import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
+import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.ui.preview.SwapAmountContentPreview
import com.tangem.features.swap.v2.impl.chooseprovider.ui.SwapChooseProviderContent
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/SwapAmountContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/SwapAmountContent.kt
index b314e610b9..69d9cb7e19 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/SwapAmountContent.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/SwapAmountContent.kt
@@ -4,6 +4,7 @@ import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
+import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
@@ -39,9 +40,9 @@ import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.express.models.ExpressRateType
-import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
+import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountClickIntents
import com.tangem.features.swap.v2.impl.amount.ui.preview.SwapAmountClickIntentsStub
@@ -156,10 +157,12 @@ private fun SwapAmountBlock(
Box {
SwapAmountEditBlock(
amountFieldUM = amountFieldUM,
+ isFixedRate = isFixedRate,
modifier = Modifier,
onValueChange = clickIntents::onAmountValueChange,
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
onCurrencyChange = clickIntents::onCurrencyChangeClick,
+ onRateClick = clickIntents::onRateClick,
)
HorizontalDivider(
modifier = Modifier
@@ -182,12 +185,15 @@ private fun SwapAmountBlock(
}
}
+@Suppress("LongParameterList")
@Composable
private fun SwapAmountEditBlock(
amountFieldUM: SwapAmountFieldUM,
+ isFixedRate: Boolean,
onValueChange: (String) -> Unit,
onValuePastedTriggerDismiss: () -> Unit,
onCurrencyChange: (Boolean) -> Unit,
+ onRateClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
@@ -209,8 +215,44 @@ private fun SwapAmountEditBlock(
onValueChange = onValueChange,
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
onCurrencyChange = onCurrencyChange,
+ reserveSpaceForError = false,
modifier = Modifier,
)
+ SwapRateBadge(
+ isFixedRate = isFixedRate,
+ onClick = onRateClick,
+ )
+ }
+}
+
+@Composable
+private fun SwapRateBadge(isFixedRate: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
+ val iconRes = if (isFixedRate) R.drawable.ic_fixed else R.drawable.ic_floating
+ val textRes = if (isFixedRate) R.string.send_rate_is_fixed else R.string.send_rate_floating_info_title
+ Row(
+ modifier = modifier
+ .clip(RoundedCornerShape(6.dp))
+ .clickable(onClick = onClick)
+ .border(
+ width = 1.dp,
+ color = TangemTheme.colors.stroke.primary,
+ shape = RoundedCornerShape(6.dp),
+ )
+ .padding(vertical = 2.dp, horizontal = 4.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ Icon(
+ painter = rememberVectorPainter(ImageVector.vectorResource(id = iconRes)),
+ contentDescription = null,
+ tint = TangemTheme.colors.icon.informative,
+ modifier = Modifier.padding(start = TangemTheme.dimens.spacing2),
+ )
+ Text(
+ text = stringResourceSafe(textRes),
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
}
}
@@ -227,6 +269,7 @@ private fun SwapAmountInfo(
modifier: Modifier = Modifier,
) {
val tokenIconState = (amountFieldUM.amountField as? AmountState.Data)?.tokenIconState ?: CurrencyIconState.Loading
+ val isEnabled = (amountFieldUM as? SwapAmountFieldUM.Content)?.isClickEnabled == true
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
@@ -234,11 +277,11 @@ private fun SwapAmountInfo(
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(),
- enabled = (amountFieldUM as? SwapAmountFieldUM.Content)?.isClickEnabled == true,
+ enabled = isEnabled,
onClick = {
- if (isFixedRate) {
+ if (!isSelectedAmountType) {
onExpandEditField()
- } else {
+ } else if (amountFieldUM.amountType == SwapAmountType.To) {
onSelectTokenClick()
}
},
@@ -256,15 +299,14 @@ private fun SwapAmountInfo(
SwapAmountInfoMain(
amountFieldUM = amountFieldUM,
selectedQuote = selectedQuote,
- isSelectedAmountType = isSelectedAmountType,
modifier = Modifier.weight(1f),
)
AnimatedContent(
targetState = isSelectedAmountType,
) { isSelected ->
- if (isSelected) {
+ if (isSelected && amountFieldUM.amountType == SwapAmountType.From) {
AmountMaxButton(onMaxAmountClick)
- } else {
+ } else if (isSelected && amountFieldUM.amountType == SwapAmountType.To) {
SwapAmountInfoQuote(
quoteUM = selectedQuote,
isFixedRate = isFixedRate,
@@ -279,7 +321,6 @@ private fun SwapAmountInfo(
private fun SwapAmountInfoMain(
amountFieldUM: SwapAmountFieldUM,
selectedQuote: SwapQuoteUM?,
- isSelectedAmountType: Boolean,
modifier: Modifier = Modifier,
) {
Column(
@@ -300,48 +341,52 @@ private fun SwapAmountInfoMain(
)
}
}
- AnimatedContent(isSelectedAmountType) { isSelected ->
- if (isSelected && amountFieldUM is SwapAmountFieldUM.Content) {
- SpacerH2()
- Row {
- EllipsisText(
- text = amountFieldUM.subtitleLeft.resolveReference(),
- style = TangemTheme.typography.caption2,
- color = TangemTheme.colors.text.tertiary,
- ellipsis = amountFieldUM.subtitleEllipsisLeft,
- modifier = Modifier.weight(1f, fill = false),
- )
- EllipsisText(
- text = amountFieldUM.subtitleRight.resolveReference(),
- style = TangemTheme.typography.caption2,
- color = TangemTheme.colors.text.tertiary,
- ellipsis = amountFieldUM.subtitleEllipsisRight,
- )
- }
- } else {
- AnimatedContent(selectedQuote) { quote ->
- when (quote) {
- is SwapQuoteUM.Content -> {
- SpacerH2()
+
+ AnimatedContent(selectedQuote) { quote ->
+ when (quote) {
+ is SwapQuoteUM.Content -> {
+ SpacerH2()
+ if (amountFieldUM is SwapAmountFieldUM.Content) {
+ SpacerH2()
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
EllipsisText(
- text = stringResourceSafe(
- R.string.send_with_swap_recipient_get_amount,
- quote.quoteAmountValue.resolveReference(),
- ),
+ text = amountFieldUM.subtitleLeft.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
+ ellipsis = amountFieldUM.subtitleEllipsisLeft,
+ modifier = Modifier.weight(1f, fill = false),
)
- }
- SwapQuoteUM.Loading -> {
- SpacerH2()
- TextShimmer(
+ EllipsisText(
+ text = amountFieldUM.subtitleRight.resolveReference(),
style = TangemTheme.typography.caption2,
- modifier = Modifier.width(72.dp),
+ color = TangemTheme.colors.text.tertiary,
+ ellipsis = amountFieldUM.subtitleEllipsisRight,
)
}
- else -> Unit
+ } else {
+ SpacerH2()
+ TextShimmer(
+ style = TangemTheme.typography.caption2,
+ modifier = Modifier.width(72.dp),
+ )
}
}
+ SwapQuoteUM.Loading -> {
+ SpacerH2()
+ TextShimmer(
+ style = TangemTheme.typography.caption2,
+ modifier = Modifier.width(72.dp),
+ )
+ }
+ else -> {
+ SpacerH2()
+ Text(
+ text = "",
+ style = TangemTheme.typography.caption2,
+ )
+ }
}
}
}
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountClickIntentsStub.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountClickIntentsStub.kt
index 4a7380480b..351e841fba 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountClickIntentsStub.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountClickIntentsStub.kt
@@ -10,6 +10,8 @@ internal object SwapAmountClickIntentsStub : SwapAmountClickIntents {
override fun onSelectTokenClick() {}
+ override fun onRateClick() {}
+
override fun onSeparatorClick() {}
override fun onProviderClick() {}
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt
index b4ebcff7cb..6f354c847a 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt
@@ -12,10 +12,11 @@ import com.tangem.domain.express.models.ExpressRateType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
-import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.domain.swap.models.SwapCurrencies
import com.tangem.domain.swap.models.SwapDirection
+import com.tangem.domain.swap.models.SwapRateMode
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
+import com.tangem.domain.swap.models.SwapAmountType
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.utils.StringsSigns
@@ -66,8 +67,10 @@ internal data object SwapAmountContentPreview {
private val quote = SwapQuoteUM.Content(
provider = provider,
- quoteAmount = "123".toBigDecimal(),
- quoteAmountValue = stringReference("123"),
+ toAmount = "123".toBigDecimal(),
+ fromAmount = null,
+ toAmountValue = stringReference("123"),
+ fromAmountValue = TextReference.EMPTY,
rate = stringReference("1 USD ≈ 123.123 POL"),
diffPercent = SwapQuoteUM.Content.DifferencePercent.Best,
isSingleProvider = false,
@@ -92,6 +95,7 @@ internal data object SwapAmountContentPreview {
appCurrency = AppCurrency.Default,
isShowBestRateAnimation = false,
isShowFCAWarning = false,
+ swapRateMode = SwapRateMode.FLOAT_ONLY,
)
val defaultState = SwapAmountUM.Content(
@@ -127,10 +131,11 @@ internal data object SwapAmountContentPreview {
selectedQuote = quote,
primaryCryptoCurrencyStatus = cryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = cryptoCurrencyStatus,
- swapRateType = ExpressRateType.Float,
+ swapRateType = ExpressRateType.Fixed,
isPrimaryButtonEnabled = true,
isShowBestRateAnimation = false,
isShowFCAWarning = true,
+ swapRateMode = SwapRateMode.FLOAT_AND_FIXED,
)
val defaultStateAccount: SwapAmountUM.Content
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderListItemConverter.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderListItemConverter.kt
index 56c7a0f93a..7383f568e4 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderListItemConverter.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderListItemConverter.kt
@@ -41,7 +41,7 @@ internal class SwapProviderListItemConverter(
title = stringReference(provider.name),
subtitle = stringReference(provider.type.typeName),
infoText = when (value) {
- is SwapQuoteUM.Content -> value.quoteAmountValue
+ is SwapQuoteUM.Content -> value.toAmountValue
else -> TextReference.EMPTY
},
iconUrl = provider.imageLarge,
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderStateConverter.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderStateConverter.kt
index 54f3030f4f..a591206770 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderStateConverter.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderStateConverter.kt
@@ -50,7 +50,7 @@ internal class SwapProviderStateConverter(
name = provider.name,
iconUrl = provider.imageLarge,
type = provider.type.typeName,
- subtitle = quoteAmountValue,
+ subtitle = toAmountValue,
additionalBadge = additionalBadge,
diffPercent = diffPercent,
isSelected = provider == selectedProvider,
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt
index 238cf49cb3..4765401f15 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt
@@ -35,8 +35,10 @@ internal object SwapChooseProviderContentPreview {
private val quote1 = SwapQuoteUM.Content(
provider = provider1,
- quoteAmount = "123".toBigDecimal(),
- quoteAmountValue = stringReference("123"),
+ toAmount = "123".toBigDecimal(),
+ fromAmount = null,
+ toAmountValue = stringReference("123"),
+ fromAmountValue = stringReference(""),
rate = stringReference("1 USD ≈ 123.123 POL"),
diffPercent = SwapQuoteUM.Content.DifferencePercent.Best,
isSingleProvider = false,
@@ -44,8 +46,10 @@ internal object SwapChooseProviderContentPreview {
private val quote2 = SwapQuoteUM.Content(
provider = provider2,
- quoteAmount = "13.12".toBigDecimal(),
- quoteAmountValue = stringReference("13.12"),
+ toAmount = "13.12".toBigDecimal(),
+ fromAmount = null,
+ toAmountValue = stringReference("13.12"),
+ fromAmountValue = stringReference(""),
rate = stringReference("1 USD ≈ 12.123 POL"),
diffPercent = SwapQuoteUM.Content.DifferencePercent.Empty,
isSingleProvider = false,
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/choosetoken/fromSupported/ui/SwapChooseTokenNetworkContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/choosetoken/fromSupported/ui/SwapChooseTokenNetworkContent.kt
index b4466b11a5..9a6c3072c5 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/choosetoken/fromSupported/ui/SwapChooseTokenNetworkContent.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/choosetoken/fromSupported/ui/SwapChooseTokenNetworkContent.kt
@@ -6,11 +6,9 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.Image
-import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
@@ -25,7 +23,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
-import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.message.*
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
@@ -141,21 +138,6 @@ private fun SwapChooseTokenNetworkContentList(swapNetworks: ImmutableList Unit): TextReference {
+private fun SendWithSwapFooter(
+ confirmUM: ConfirmUM,
+ stackState: ChildStack,
+ primaryButton: NavigationButton,
+ onLinkClick: (String) -> Unit,
+) {
+ Column {
+ AnimatedVisibility(
+ visible = stackState.active.configuration == SendWithSwapRoute.Confirm,
+ enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(),
+ exit = slideOutVertically(targetOffsetY = { it / 2 }) + fadeOut(),
+ ) {
+ val confirmContentUM = confirmUM as? ConfirmUM.Content
+ val sendFooter = confirmContentUM?.sendingFooter ?: TextReference.EMPTY
+ val legalFooter = getAnnotatedStringForLegals(
+ tosUM = confirmContentUM?.tosUM,
+ sendFooter = sendFooter,
+ onClick = onLinkClick,
+ )
+ val footerText = remember(sendFooter, legalFooter) {
+ if (sendFooter != TextReference.EMPTY || legalFooter != TextReference.EMPTY) {
+ combinedReference(sendFooter, legalFooter)
+ } else {
+ TextReference.EMPTY
+ }
+ }
+ SendingText(footerText = footerText)
+ }
+ NavigationPrimaryButton(
+ primaryButton = primaryButton,
+ modifier = Modifier.padding(
+ start = 16.dp,
+ end = 16.dp,
+ bottom = 16.dp,
+ ),
+ )
+ }
+}
+
+@Composable
+private fun getAnnotatedStringForLegals(
+ tosUM: ConfirmUM.Content.TosUM?,
+ sendFooter: TextReference,
+ onClick: (String) -> Unit,
+): TextReference {
if (tosUM == null) return TextReference.EMPTY
val tos = tosUM.tosLink
val policy = tosUM.policyLink
@@ -118,7 +144,9 @@ private fun getAnnotatedStringForLegals(tosUM: ConfirmUM.Content.TosUM?, onClick
val policyIndex = fullString.indexOf(policyTitle)
annotatedReference {
- append(StringsSigns.POINT_SIGN)
+ if (!sendFooter.resolveReference().endsWith(StringsSigns.POINT_SIGN)) {
+ append(StringsSigns.POINT_SIGN)
+ }
appendSpace()
append(fullString.substring(0, tosIndex))
withLink(