Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-18 13:58:49 +03:00
parent 8531237653
commit 762121b9b5
6 changed files with 56 additions and 19 deletions

View file

@ -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(

View file

@ -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(

View file

@ -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,

View file

@ -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
}

View file

@ -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"),

View file

@ -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(),
)
}