diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index d2788d35a4..97c7c85fb3 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -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( diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index b2a70e5799..2de0ce5464 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -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( diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index 8b42445651..00b174011d 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -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, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 89a2f5343e..c74bd436f7 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -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 } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 2b2ebd577e..43a6329ace 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -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().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"), diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index b16a5e4c21..e7b51283c2 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -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(), ) }