Updated on 2026-08-14
This commit is contained in:
parent
7b15bb555e
commit
7f3d810755
13 changed files with 104 additions and 14 deletions
|
|
@ -335,6 +335,8 @@
|
|||
<string name="swapping_error_wrapper">Error: %s</string>
|
||||
<string name="swapping_generic_error">There was an error. Please try again.</string>
|
||||
<string name="swapping_give_permission">Give Permission</string>
|
||||
<string name="swapping_high_price_impact">High price impact!</string>
|
||||
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
|
||||
<string name="swapping_insufficient_funds">Insufficient funds</string>
|
||||
<string name="swapping_not_enough_funds_for_fee">Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.</string>
|
||||
<string name="swapping_pending_transaction_subtitle">Transaction in progress...</string>
|
||||
|
|
|
|||
|
|
@ -335,6 +335,8 @@
|
|||
<string name="swapping_error_wrapper">Error: %s</string>
|
||||
<string name="swapping_generic_error">There was an error. Please try again.</string>
|
||||
<string name="swapping_give_permission">Give Permission</string>
|
||||
<string name="swapping_high_price_impact">High price impact!</string>
|
||||
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
|
||||
<string name="swapping_insufficient_funds">Insufficient funds</string>
|
||||
<string name="swapping_not_enough_funds_for_fee">Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.</string>
|
||||
<string name="swapping_pending_transaction_subtitle">Transaction in progress...</string>
|
||||
|
|
|
|||
|
|
@ -335,6 +335,8 @@
|
|||
<string name="swapping_error_wrapper">Error: %s</string>
|
||||
<string name="swapping_generic_error">There was an error. Please try again.</string>
|
||||
<string name="swapping_give_permission">Give Permission</string>
|
||||
<string name="swapping_high_price_impact">High price impact!</string>
|
||||
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
|
||||
<string name="swapping_insufficient_funds">Insufficient funds</string>
|
||||
<string name="swapping_not_enough_funds_for_fee">Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.</string>
|
||||
<string name="swapping_pending_transaction_subtitle">Transaction in progress...</string>
|
||||
|
|
|
|||
|
|
@ -335,6 +335,8 @@
|
|||
<string name="swapping_error_wrapper">Ошибка: %s</string>
|
||||
<string name="swapping_generic_error">Произошла ошибка. Пожалуйста, попробуйте еще раз.</string>
|
||||
<string name="swapping_give_permission">Дать разрешение</string>
|
||||
<string name="swapping_high_price_impact">Сильные колебания цены!</string>
|
||||
<string name="swapping_high_price_impact_description">Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму.</string>
|
||||
<string name="swapping_insufficient_funds">Недостаточно средств</string>
|
||||
<string name="swapping_not_enough_funds_for_fee">Недостаточно средств для оплаты на вашем %1$s кошельке для создания транзакции. Сначала пополните свой %2$s кошелек.</string>
|
||||
<string name="swapping_pending_transaction_subtitle">Транзакция в процессе…</string>
|
||||
|
|
|
|||
|
|
@ -335,6 +335,8 @@
|
|||
<string name="swapping_error_wrapper">錯誤: %s</string>
|
||||
<string name="swapping_generic_error">有錯誤。請再試一遍</string>
|
||||
<string name="swapping_give_permission">賦予權限</string>
|
||||
<string name="swapping_high_price_impact">High price impact!</string>
|
||||
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
|
||||
<string name="swapping_insufficient_funds">餘額不足</string>
|
||||
<string name="swapping_not_enough_funds_for_fee">您的 %1$s 錢包中沒有足夠的資金來創建交易。首先為您的 %2$s 錢包充值</string>
|
||||
<string name="swapping_pending_transaction_subtitle">交易進行中...</string>
|
||||
|
|
|
|||
|
|
@ -335,6 +335,8 @@
|
|||
<string name="swapping_error_wrapper">Error: %s</string>
|
||||
<string name="swapping_generic_error">There was an error. Please try again.</string>
|
||||
<string name="swapping_give_permission">Give Permission</string>
|
||||
<string name="swapping_high_price_impact">High price impact!</string>
|
||||
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
|
||||
<string name="swapping_insufficient_funds">Insufficient funds</string>
|
||||
<string name="swapping_not_enough_funds_for_fee">Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.</string>
|
||||
<string name="swapping_pending_transaction_subtitle">Transaction in progress...</string>
|
||||
|
|
|
|||
|
|
@ -67,8 +67,6 @@ import com.tangem.feature.referral.presentation.R
|
|||
import com.valentinilk.shimmer.shimmer
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val GOOGLE_DOC_LINK_WRAPPER = "https://docs.google.com/viewer?url=%s&embedded=false"
|
||||
|
||||
/**
|
||||
* Referral program screen for participant and non-participant
|
||||
*
|
||||
|
|
@ -87,8 +85,8 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder) {
|
|||
sheetContent = {
|
||||
AgreementBottomSheetContent(
|
||||
url = when (val state = stateHolder.referralInfoState) {
|
||||
is ReferralInfoState.NonParticipantContent -> String.format(GOOGLE_DOC_LINK_WRAPPER, state.url)
|
||||
is ReferralInfoState.ParticipantContent -> String.format(GOOGLE_DOC_LINK_WRAPPER, state.url)
|
||||
is ReferralInfoState.NonParticipantContent -> state.url
|
||||
is ReferralInfoState.ParticipantContent -> state.url
|
||||
is ReferralInfoState.Loading -> ""
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import com.tangem.lib.crypto.models.ProxyFiatCurrency
|
|||
import com.tangem.lib.crypto.models.transactions.SendTxResult
|
||||
import com.tangem.utils.toFiatString
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LargeClass")
|
||||
|
|
@ -496,6 +497,12 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
),
|
||||
),
|
||||
fee = formattedFee,
|
||||
priceImpact = calculatePriceImpact(
|
||||
fromTokenAmount = fromTokenAmount.value,
|
||||
fromRate = rates[fromToken.id] ?: 0.0,
|
||||
toTokenAmount = toTokenAmount.value,
|
||||
toRate = rates[toToken.id] ?: 0.0,
|
||||
),
|
||||
networkCurrency = userWalletManager.getNetworkCurrency(networkId),
|
||||
preparedSwapConfigState = preparedSwapConfigState,
|
||||
swapDataModel = swapDataModel,
|
||||
|
|
@ -605,6 +612,17 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
return amountToSwap.replace(",", ".").toBigDecimalOrNull()
|
||||
}
|
||||
|
||||
private fun calculatePriceImpact(
|
||||
fromTokenAmount: BigDecimal,
|
||||
fromRate: Double,
|
||||
toTokenAmount: BigDecimal,
|
||||
toRate: Double,
|
||||
): Float {
|
||||
val toTokenFiatValue = toTokenAmount.multiply(toRate.toBigDecimal()).setScale(2, RoundingMode.HALF_UP)
|
||||
val fromTokenFiatValue = fromTokenAmount.multiply(fromRate.toBigDecimal()).setScale(2, RoundingMode.HALF_UP)
|
||||
return (BigDecimal.ONE - toTokenFiatValue / fromTokenFiatValue).toFloat()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_SLIPPAGE = 2
|
||||
private const val ZERO_BALANCE = "0"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ sealed interface SwapState {
|
|||
val fromTokenInfo: TokenSwapInfo,
|
||||
val toTokenInfo: TokenSwapInfo,
|
||||
val fee: String,
|
||||
val priceImpact: Float,
|
||||
val networkCurrency: String,
|
||||
val preparedSwapConfigState: PreparedSwapConfigState,
|
||||
val permissionState: PermissionDataState = PermissionDataState.Empty,
|
||||
|
|
|
|||
|
|
@ -84,7 +84,12 @@ sealed interface SwapWarning {
|
|||
val onClick: () -> Unit,
|
||||
) : SwapWarning
|
||||
// data class RateExpired(val onClick: () -> Unit) : SwapWarning
|
||||
// object HighPriceImpact : SwapWarning
|
||||
/**
|
||||
* High price impact warning
|
||||
*
|
||||
* @property priceImpact in format = 10 (means 10%)
|
||||
*/
|
||||
data class HighPriceImpact(val priceImpact: Int) : SwapWarning
|
||||
}
|
||||
|
||||
enum class GenericWarningType {
|
||||
|
|
|
|||
|
|
@ -124,6 +124,10 @@ internal class StateBuilder(val actions: UiActions) {
|
|||
if (!quoteModel.preparedSwapConfigState.isBalanceEnough) {
|
||||
warnings.add(SwapWarning.InsufficientFunds)
|
||||
}
|
||||
|
||||
if (quoteModel.priceImpact > PRICE_IMPACT_THRESHOLD) {
|
||||
warnings.add(SwapWarning.HighPriceImpact((quoteModel.priceImpact * HUNDRED_PERCENTS).toInt()))
|
||||
}
|
||||
val feeState = if (quoteModel.preparedSwapConfigState.isFeeEnough) {
|
||||
FeeState.Loaded(tangemFee = quoteModel.tangemFee, fee = quoteModel.fee)
|
||||
} else {
|
||||
|
|
@ -358,5 +362,7 @@ internal class StateBuilder(val actions: UiActions) {
|
|||
const val ADDRESS_MIN_LENGTH = 11
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -161,6 +161,7 @@ private fun MainInfo(state: SwapStateHolder) {
|
|||
getActiveIconRes(state.blockchainId)
|
||||
}
|
||||
val (topCard, bottomCard, button) = createRefs()
|
||||
val priceImpactWarning = state.warnings.filterIsInstance<SwapWarning.HighPriceImpact>().firstOrNull()
|
||||
TransactionCard(
|
||||
type = state.sendCardData.type,
|
||||
balance = state.sendCardData.balance,
|
||||
|
|
@ -168,6 +169,7 @@ private fun MainInfo(state: SwapStateHolder) {
|
|||
amountEquivalent = state.sendCardData.amountEquivalent,
|
||||
tokenIconUrl = state.sendCardData.tokenIconUrl,
|
||||
tokenCurrency = state.sendCardData.tokenCurrency,
|
||||
priceImpact = priceImpactWarning,
|
||||
networkIconRes = if (state.sendCardData.isNotNativeToken) networkIconRes else null,
|
||||
iconPlaceholder = state.sendCardData.coinId?.let {
|
||||
getActiveIconResByCoinId(it, state.networkId)
|
||||
|
|
@ -185,6 +187,7 @@ private fun MainInfo(state: SwapStateHolder) {
|
|||
amountEquivalent = state.receiveCardData.amountEquivalent,
|
||||
tokenIconUrl = state.receiveCardData.tokenIconUrl,
|
||||
tokenCurrency = state.receiveCardData.tokenCurrency,
|
||||
priceImpact = priceImpactWarning,
|
||||
networkIconRes = if (state.receiveCardData.isNotNativeToken) networkIconRes else null,
|
||||
iconPlaceholder = state.receiveCardData.coinId?.let {
|
||||
getActiveIconResByCoinId(it, state.networkId)
|
||||
|
|
@ -289,9 +292,12 @@ private fun SwapWarnings(
|
|||
) {
|
||||
warnings.forEach { warning ->
|
||||
when (warning) {
|
||||
// SwapWarning.HighPriceImpact -> {
|
||||
// WarningCard(title = stringResource(id = R.string.), description = stringResource(id = R.string.))
|
||||
// }
|
||||
is SwapWarning.HighPriceImpact -> {
|
||||
WarningCard(
|
||||
title = stringResource(id = R.string.swapping_high_price_impact),
|
||||
description = stringResource(id = R.string.swapping_high_price_impact_description),
|
||||
)
|
||||
}
|
||||
is SwapWarning.PermissionNeeded -> {
|
||||
WarningCard(
|
||||
title = stringResource(id = R.string.swapping_permission_header),
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -45,7 +48,9 @@ import com.tangem.core.ui.components.ResizableText
|
|||
import com.tangem.core.ui.components.SpacerH4
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.SpacerW16
|
||||
import com.tangem.core.ui.components.SpacerW4
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.swap.models.SwapWarning
|
||||
import com.tangem.feature.swap.models.TransactionCardType
|
||||
import com.valentinilk.shimmer.shimmer
|
||||
|
||||
|
|
@ -57,6 +62,7 @@ fun TransactionCard(
|
|||
tokenIconUrl: String,
|
||||
tokenCurrency: String,
|
||||
amountEquivalent: String?,
|
||||
priceImpact: SwapWarning.HighPriceImpact?,
|
||||
textFieldValue: TextFieldValue?,
|
||||
modifier: Modifier = Modifier,
|
||||
@DrawableRes iconPlaceholder: Int? = null,
|
||||
|
|
@ -82,6 +88,7 @@ fun TransactionCard(
|
|||
type = type,
|
||||
amountEquivalent = amountEquivalent,
|
||||
textFieldValue = textFieldValue,
|
||||
priceImpact = priceImpact,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -171,6 +178,7 @@ private fun Header(
|
|||
private fun Content(
|
||||
type: TransactionCardType,
|
||||
amountEquivalent: String?,
|
||||
priceImpact: SwapWarning.HighPriceImpact?,
|
||||
textFieldValue: TextFieldValue?,
|
||||
) {
|
||||
Row(
|
||||
|
|
@ -227,12 +235,34 @@ private fun Content(
|
|||
SpacerH8()
|
||||
|
||||
if (amountEquivalent != null) {
|
||||
Text(
|
||||
text = amountEquivalent,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20),
|
||||
)
|
||||
if (type is TransactionCardType.ReceiveCard && priceImpact != null) {
|
||||
Row {
|
||||
Text(
|
||||
text = makePriceImpactBalanceWarning(amountEquivalent, priceImpact.priceImpact),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
modifier = Modifier
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size20)
|
||||
.align(Alignment.CenterVertically),
|
||||
)
|
||||
SpacerW4()
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_alert_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.attention,
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size20)
|
||||
.align(Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = amountEquivalent,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -339,6 +369,19 @@ fun ChangeTokenSelector() {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun makePriceImpactBalanceWarning(value: String, priceImpactPercents: Int): AnnotatedString {
|
||||
val fullValue = "$value (-$priceImpactPercents%)"
|
||||
return buildAnnotatedString {
|
||||
append(fullValue)
|
||||
addStyle(
|
||||
style = SpanStyle(color = TangemTheme.colors.text.attention),
|
||||
start = value.length,
|
||||
end = fullValue.length,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenImageShimmer(
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -384,6 +427,7 @@ private fun TransactionCardPreview() {
|
|||
onChangeTokenClick = {},
|
||||
balance = "123",
|
||||
textFieldValue = TextFieldValue(),
|
||||
priceImpact = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue