Updated on 2026-08-14
This commit is contained in:
parent
8531237653
commit
762121b9b5
6 changed files with 56 additions and 19 deletions
|
|
@ -13,7 +13,7 @@ sealed interface SwapState {
|
|||
data class QuotesLoadedState(
|
||||
val fromTokenInfo: TokenSwapInfo,
|
||||
val toTokenInfo: TokenSwapInfo,
|
||||
val priceImpact: Float,
|
||||
val priceImpact: PriceImpact,
|
||||
val networkCurrency: String,
|
||||
val preparedSwapConfigState: PreparedSwapConfigState = PreparedSwapConfigState(
|
||||
isAllowedToSpend = false,
|
||||
|
|
@ -40,6 +40,20 @@ sealed interface SwapState {
|
|||
) : SwapState
|
||||
}
|
||||
|
||||
sealed class PriceImpact {
|
||||
|
||||
abstract val value: Float
|
||||
|
||||
fun getIntPercentValue() = (value * HUNDRED_PERCENTS).toInt()
|
||||
data class Empty(override val value: Float = 0f) : PriceImpact()
|
||||
data class ValueWithNotify(override val value: Float) : PriceImpact()
|
||||
data class Value(override val value: Float) : PriceImpact()
|
||||
|
||||
companion object {
|
||||
private const val HUNDRED_PERCENTS = 100
|
||||
}
|
||||
}
|
||||
|
||||
sealed class PermissionDataState {
|
||||
|
||||
data class PermissionReadyForRequest(
|
||||
|
|
|
|||
|
|
@ -772,6 +772,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
toTokenAmount = quoteModel.toTokenAmount,
|
||||
swapData = null,
|
||||
txFeeState = txFee,
|
||||
exchangeProviderType = exchangeProviderType,
|
||||
)
|
||||
|
||||
when (exchangeProviderType) {
|
||||
|
|
@ -930,6 +931,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
toTokenAmount = swapData.toTokenAmount,
|
||||
swapData = swapData,
|
||||
txFeeState = txFeeState,
|
||||
exchangeProviderType = ExchangeProviderType.DEX,
|
||||
)
|
||||
swapState.copy(
|
||||
permissionState = PermissionDataState.Empty,
|
||||
|
|
@ -967,6 +969,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
toTokenAmount: SwapAmount,
|
||||
swapData: SwapDataModel?,
|
||||
txFeeState: TxFeeState,
|
||||
exchangeProviderType: ExchangeProviderType,
|
||||
): SwapState.QuotesLoadedState {
|
||||
val fromToken = fromTokenStatus.currency
|
||||
val toToken = toTokenStatus.currency
|
||||
|
|
@ -991,6 +994,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
fromRate = rates[fromToken.id]?.fiatRate?.toDouble() ?: 0.0,
|
||||
toTokenAmount = toTokenAmount.value,
|
||||
toRate = rates[toToken.id]?.fiatRate?.toDouble() ?: 0.0,
|
||||
exchangeProviderType = exchangeProviderType,
|
||||
),
|
||||
networkCurrency = userWalletManager.getNetworkCurrency(networkId),
|
||||
swapDataModel = swapData,
|
||||
|
|
@ -1316,10 +1320,15 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
fromRate: Double,
|
||||
toTokenAmount: BigDecimal,
|
||||
toRate: Double,
|
||||
): Float {
|
||||
val toTokenFiatValue = toTokenAmount.multiply(toRate.toBigDecimal())
|
||||
exchangeProviderType: ExchangeProviderType,
|
||||
): PriceImpact {
|
||||
val fromTokenFiatValue = fromTokenAmount.multiply(fromRate.toBigDecimal())
|
||||
return (BigDecimal.ONE - toTokenFiatValue.divide(fromTokenFiatValue, 2, RoundingMode.HALF_UP)).toFloat()
|
||||
val toTokenFiatValue = toTokenAmount.multiply(toRate.toBigDecimal())
|
||||
val value = (BigDecimal.ONE - toTokenFiatValue.divide(fromTokenFiatValue, 2, RoundingMode.HALF_UP)).toFloat()
|
||||
if (exchangeProviderType == ExchangeProviderType.CEX) {
|
||||
return PriceImpact.Value(value)
|
||||
}
|
||||
return PriceImpact.ValueWithNotify(value)
|
||||
}
|
||||
|
||||
private suspend fun getApproveData(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.core.ui.components.states.Item
|
|||
import com.tangem.core.ui.components.states.SelectableItemsState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.domain.models.ui.PriceImpact
|
||||
import com.tangem.feature.swap.domain.models.ui.TxFee
|
||||
import com.tangem.feature.swap.models.states.FeeItemState
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
|
|
@ -24,6 +25,7 @@ data class SwapStateHolder(
|
|||
|
||||
val fee: FeeItemState = FeeItemState.Empty,
|
||||
val permissionState: SwapPermissionState = SwapPermissionState.Empty,
|
||||
val priceImpact: PriceImpact,
|
||||
|
||||
val successState: SwapSuccessStateHolder? = null,
|
||||
val selectTokenState: SwapSelectTokenStateHolder? = null,
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ internal class StateBuilder(
|
|||
changeCardsButtonState = ChangeCardsButtonState.UPDATE_IN_PROGRESS,
|
||||
onShowPermissionBottomSheet = actions.openPermissionBottomSheet,
|
||||
providerState = ProviderState.Empty(),
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -139,6 +140,7 @@ internal class StateBuilder(
|
|||
onClick = { },
|
||||
),
|
||||
changeCardsButtonState = ChangeCardsButtonState.DISABLED,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -187,6 +189,7 @@ internal class StateBuilder(
|
|||
providerState = ProviderState.Loading(),
|
||||
permissionState = uiStateHolder.permissionState,
|
||||
changeCardsButtonState = ChangeCardsButtonState.UPDATE_IN_PROGRESS,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +274,11 @@ internal class StateBuilder(
|
|||
selectionType = ProviderState.SelectionType.CLICK,
|
||||
onProviderClick = actions.onProviderClick,
|
||||
),
|
||||
priceImpact = if (quoteModel.priceImpact.value > PRICE_IMPACT_THRESHOLD) {
|
||||
quoteModel.priceImpact
|
||||
} else {
|
||||
PriceImpact.Empty()
|
||||
},
|
||||
tosState = createTosState(swapProvider),
|
||||
)
|
||||
}
|
||||
|
|
@ -337,10 +345,11 @@ internal class StateBuilder(
|
|||
warnings.add(SwapWarning.InsufficientFunds)
|
||||
}
|
||||
|
||||
if (quoteModel.priceImpact > PRICE_IMPACT_THRESHOLD) {
|
||||
val priceImpact = quoteModel.priceImpact
|
||||
if (priceImpact is PriceImpact.ValueWithNotify && priceImpact.value > PRICE_IMPACT_THRESHOLD) {
|
||||
warnings.add(
|
||||
SwapWarning.HighPriceImpact(
|
||||
priceImpact = (quoteModel.priceImpact * HUNDRED_PERCENTS).toInt(),
|
||||
priceImpact = priceImpact.getIntPercentValue(),
|
||||
notificationConfig = highPriceImpactNotificationConfig(),
|
||||
),
|
||||
)
|
||||
|
|
@ -442,6 +451,7 @@ internal class StateBuilder(
|
|||
ChangeCardsButtonState.DISABLED
|
||||
},
|
||||
providerState = providerState,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
tosState = createTosState(swapProvider),
|
||||
)
|
||||
}
|
||||
|
|
@ -553,6 +563,7 @@ internal class StateBuilder(
|
|||
ChangeCardsButtonState.DISABLED
|
||||
},
|
||||
providerState = ProviderState.Empty(),
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1226,7 +1237,6 @@ internal class StateBuilder(
|
|||
const val ADDRESS_FIRST_PART_LENGTH = 7
|
||||
const val ADDRESS_SECOND_PART_LENGTH = 4
|
||||
private const val PRICE_IMPACT_THRESHOLD = 0.1
|
||||
private const val HUNDRED_PERCENTS = 100
|
||||
private const val UNKNOWN_AMOUNT_SIGN = "—"
|
||||
private const val MAX_DECIMALS_TO_SHOW = 8
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.tangem.core.ui.extensions.resolveReference
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.swap.domain.models.ui.FeeType
|
||||
import com.tangem.feature.swap.domain.models.ui.PriceImpact
|
||||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.models.states.FeeItemState
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
|
|
@ -127,9 +128,9 @@ private fun MainInfo(state: SwapStateHolder) {
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
val (topCard, bottomCard, button) = createRefs()
|
||||
val priceImpactWarning = state.warnings.filterIsInstance<SwapWarning.HighPriceImpact>().firstOrNull()
|
||||
val priceImpact = state.priceImpact
|
||||
TransactionCardData(
|
||||
priceImpactWarning = priceImpactWarning,
|
||||
priceImpact = priceImpact,
|
||||
swapCardState = state.sendCardData,
|
||||
modifier = Modifier.constrainAs(topCard) {
|
||||
top.linkTo(parent.top)
|
||||
|
|
@ -138,7 +139,7 @@ private fun MainInfo(state: SwapStateHolder) {
|
|||
)
|
||||
val marginCard = TangemTheme.dimens.spacing16
|
||||
TransactionCardData(
|
||||
priceImpactWarning = priceImpactWarning,
|
||||
priceImpact = priceImpact,
|
||||
swapCardState = state.receiveCardData,
|
||||
modifier = Modifier.constrainAs(bottomCard) {
|
||||
top.linkTo(topCard.bottom, margin = marginCard)
|
||||
|
|
@ -159,7 +160,7 @@ private fun MainInfo(state: SwapStateHolder) {
|
|||
|
||||
@Composable
|
||||
private fun TransactionCardData(
|
||||
priceImpactWarning: SwapWarning.HighPriceImpact?,
|
||||
priceImpact: PriceImpact,
|
||||
swapCardState: SwapCardState,
|
||||
onSelectTokenClick: (() -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -186,7 +187,7 @@ private fun TransactionCardData(
|
|||
amountEquivalent = swapCardState.amountEquivalent,
|
||||
tokenIconUrl = swapCardState.tokenIconUrl ?: "",
|
||||
tokenCurrency = swapCardState.tokenCurrency,
|
||||
priceImpact = priceImpactWarning,
|
||||
priceImpact = priceImpact,
|
||||
networkIconRes = if (swapCardState.isNotNativeToken) swapCardState.networkIconRes else null,
|
||||
iconPlaceholder = swapCardState.coinId?.let {
|
||||
getActiveIconResByCoinId(it)
|
||||
|
|
@ -498,6 +499,7 @@ private val state = SwapStateHolder(
|
|||
permissionState = SwapPermissionState.InProgress,
|
||||
blockchainId = "POLYGON",
|
||||
providerState = ProviderState.Loading(),
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
tosState = TosState(
|
||||
tosLink = LegalState(
|
||||
title = stringReference("Tangem"),
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import com.tangem.core.ui.R
|
|||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.ImageBackgroundContrastChecker
|
||||
import com.tangem.feature.swap.models.SwapWarning
|
||||
import com.tangem.feature.swap.domain.models.ui.PriceImpact
|
||||
import com.tangem.feature.swap.models.TransactionCardType
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ fun TransactionCard(
|
|||
tokenIconUrl: String,
|
||||
tokenCurrency: String,
|
||||
amountEquivalent: String?,
|
||||
priceImpact: SwapWarning.HighPriceImpact?,
|
||||
priceImpact: PriceImpact,
|
||||
textFieldValue: TextFieldValue?,
|
||||
modifier: Modifier = Modifier,
|
||||
@DrawableRes iconPlaceholder: Int? = null,
|
||||
|
|
@ -138,7 +138,7 @@ fun TransactionCardEmpty(
|
|||
type = type,
|
||||
amountEquivalent = amountEquivalent,
|
||||
textFieldValue = textFieldValue,
|
||||
priceImpact = null,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -221,7 +221,7 @@ private fun Header(type: TransactionCardType, balance: String, modifier: Modifie
|
|||
private fun Content(
|
||||
type: TransactionCardType,
|
||||
amountEquivalent: String?,
|
||||
priceImpact: SwapWarning.HighPriceImpact?,
|
||||
priceImpact: PriceImpact,
|
||||
textFieldValue: TextFieldValue?,
|
||||
) {
|
||||
Row(
|
||||
|
|
@ -274,10 +274,10 @@ private fun Content(
|
|||
SpacerH8()
|
||||
|
||||
if (amountEquivalent != null) {
|
||||
if (type is TransactionCardType.ReceiveCard && priceImpact != null) {
|
||||
if (type is TransactionCardType.ReceiveCard && priceImpact !is PriceImpact.Empty) {
|
||||
Row {
|
||||
Text(
|
||||
text = makePriceImpactBalanceWarning(amountEquivalent, priceImpact.priceImpact),
|
||||
text = makePriceImpactBalanceWarning(amountEquivalent, priceImpact.getIntPercentValue()),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
modifier = Modifier
|
||||
|
|
@ -491,7 +491,7 @@ private fun TransactionCardPreview() {
|
|||
onChangeTokenClick = {},
|
||||
balance = "123",
|
||||
textFieldValue = TextFieldValue(),
|
||||
priceImpact = null,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue