Updated on 2026-08-14

This commit is contained in:
Tangem 2024-02-15 10:08:56 +01:00
parent 3e7825b2e3
commit eadc650d26
9 changed files with 133 additions and 33 deletions

View file

@ -536,6 +536,9 @@
<string name="swapping_give_permission">Дать разрешение</string>
<string name="swapping_high_price_impact_description">Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму.</string>
<string name="swapping_insufficient_funds">Недостаточно средств</string>
<string name="swapping_alert_title">Комиссии</string>
<string name="swapping_alert_cex_description">В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя</string>
<string name="swapping_alert_dex_description">В сумму включена комиссия провайдера сервиса.</string>
<string name="swapping_not_enough_funds_for_fee">Недостаточно средств для оплаты комиссии на вашем %1$s кошельке для создания транзакции. Сначала пополните свой %2$s кошелек.</string>
<string name="swapping_pending_transaction_subtitle">Транзакция в процессе…</string>
<string name="swapping_pending_transaction_title">Подождите</string>

View file

@ -549,6 +549,9 @@
<string name="swapping_give_permission">Give Permission</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_alert_title">Fees</string>
<string name="swapping_alert_cex_description">The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address.</string>
<string name="swapping_alert_dex_description">The amount includes the service provider\'s fee.</string>
<string name="swapping_not_enough_funds_for_fee">Insufficient funds in your %1$s wallet to cover fees. Top up your %2$s wallet first.</string>
<string name="swapping_pending_transaction_subtitle">Transaction in progress...</string>
<string name="swapping_pending_transaction_title">Waiting</string>

View file

@ -42,10 +42,12 @@ sealed class PriceImpact {
abstract val value: Float
fun getIntPercentValue() = (value * HUNDRED_PERCENTS).toInt()
data class Empty(override val value: Float = 0f) : PriceImpact()
data class Value(override val value: Float) : PriceImpact()
fun getIntPercentValue() = (value * HUNDRED_PERCENTS).toInt()
companion object {
private const val HUNDRED_PERCENTS = 100
}

View file

@ -84,6 +84,7 @@ sealed interface TransactionCardType {
) : TransactionCardType
data class ReadOnly(
val showWarning: Boolean = false,
val onWarningClick: (() -> Unit)? = null,
@StringRes override val headerResId: Int = R.string.swapping_to_title,
) : TransactionCardType
@ -105,12 +106,12 @@ sealed interface SwapWarning {
object InsufficientFunds : SwapWarning
data class NoAvailableTokensToSwap(val notificationConfig: NotificationConfig) : SwapWarning
data class GenericWarning(
val title: TextReference? = null,
val message: TextReference? = null,
val type: GenericWarningType = GenericWarningType.OTHER,
val shouldWrapMessage: Boolean = false,
val onClick: () -> Unit,
) : SwapWarning
// data class RateExpired(val onClick: () -> Unit) : SwapWarning
data class GeneralError(val notificationConfig: NotificationConfig) : SwapWarning
data class UnableToCoverFeeWarning(val notificationConfig: NotificationConfig) : SwapWarning
data class GeneralWarning(val notificationConfig: NotificationConfig) : SwapWarning

View file

@ -233,7 +233,10 @@ internal class StateBuilder(
isBalanceHidden = isBalanceHiddenProvider(),
),
receiveCardData = SwapCardState.SwapCardData(
type = TransactionCardType.ReadOnly(actions.onReceiveCardWarningClick),
type = TransactionCardType.ReadOnly(
showWarning = true,
actions.onReceiveCardWarningClick,
),
amountTextFieldValue = TextFieldValue(quoteModel.toTokenInfo.tokenAmount.formatToUIRepresentation()),
amountEquivalent = getFormattedFiatAmount(quoteModel.toTokenInfo.amountFiat),
token = toCurrencyStatus,
@ -852,17 +855,30 @@ internal class StateBuilder(
)
}
fun createImpactAlert(
fun createAlert(
uiState: SwapStateHolder,
isPriceImpact: Boolean,
token: String,
providerType: ExchangeProviderType,
onAlertClick: () -> Unit,
): SwapStateHolder {
val message = when (providerType) {
ExchangeProviderType.CEX -> resourceReference(R.string.express_cex_fee_explanation)
ExchangeProviderType.DEX -> resourceReference(R.string.swapping_high_price_impact_description)
ExchangeProviderType.CEX -> resourceReference(R.string.swapping_alert_cex_description, wrappedList(token))
ExchangeProviderType.DEX -> {
val refs = buildList {
if (isPriceImpact) {
add(resourceReference(R.string.swapping_high_price_impact_description))
add(stringReference("\n\n"))
}
add(resourceReference(R.string.swapping_alert_dex_description))
}
combinedReference(refs.toWrappedList())
}
}
return uiState.copy(
alert = SwapWarning.GenericWarning(
title = resourceReference(R.string.swapping_alert_title),
message = message,
onClick = onAlertClick,
type = GenericWarningType.OTHER,

View file

@ -111,8 +111,13 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi
} else {
state.alert.message?.resolveReference() ?: stringResource(id = R.string.common_unknown_error)
}
SimpleOkDialog(
BasicDialog(
title = state.alert.title?.resolveReference(),
message = message,
confirmButton = DialogButton(
title = stringResource(id = R.string.common_ok),
onClick = state.alert.onClick,
),
onDismissDialog = state.alert.onClick,
)
}

View file

@ -274,29 +274,49 @@ private fun Content(
SpacerH8()
if (amountEquivalent != null) {
if (type is TransactionCardType.ReadOnly && priceImpact !is PriceImpact.Empty) {
if (type is TransactionCardType.ReadOnly) {
Row {
Text(
text = makePriceImpactBalanceWarning(amountEquivalent, priceImpact.getIntPercentValue()),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
modifier = Modifier
.defaultMinSize(minHeight = TangemTheme.dimens.size20)
.align(Alignment.CenterVertically),
)
SpacerW4()
IconButton(
onClick = {
type.onWarningClick?.invoke()
},
modifier = Modifier.size(size = TangemTheme.dimens.size20),
) {
Icon(
painter = painterResource(id = R.drawable.ic_alert_24),
contentDescription = null,
tint = TangemTheme.colors.icon.attention,
modifier = Modifier.align(Alignment.CenterVertically),
if (priceImpact is PriceImpact.Value) {
Text(
text = makePriceImpactBalanceWarning(
amountEquivalent,
priceImpact.getIntPercentValue(),
),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
modifier = Modifier
.defaultMinSize(minHeight = TangemTheme.dimens.size20)
.align(Alignment.CenterVertically),
)
} else {
AnimatedContent(targetState = amountEquivalent, label = "") {
Text(
text = it,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20),
)
}
}
if (type.showWarning) {
SpacerW4()
IconButton(
onClick = {
type.onWarningClick?.invoke()
},
modifier = Modifier.size(size = TangemTheme.dimens.size20),
) {
Icon(
painter = painterResource(id = R.drawable.ic_alert_24),
contentDescription = null,
tint = if (priceImpact is PriceImpact.Value) {
TangemTheme.colors.text.attention
} else {
TangemTheme.colors.text.tertiary
},
modifier = Modifier.align(Alignment.CenterVertically),
)
}
}
}
} else {
@ -471,22 +491,52 @@ private fun makePriceImpactBalanceWarning(value: String, priceImpactPercents: In
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
@Composable
private fun Preview_SwapMainCard_InLightTheme() {
private fun Preview_TransactionCard_InLightTheme() {
TangemTheme(isDark = false) {
TransactionCardPreview()
}
}
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
@Composable
private fun Preview_TransactionCardWithPriceImpact_InLightTheme() {
TangemTheme(isDark = false) {
TransactionCardPreviewWithPriceImpact()
}
}
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
@Composable
private fun Preview_SwapMainCard_InDarkTheme() {
TangemTheme(isDark = true) {
private fun Preview_TransactionCardWithoutPriceImpact_InLightTheme() {
TangemTheme(isDark = false) {
TransactionCardPreviewWithoutPriceImpact()
}
}
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
@Composable
private fun Preview_TransactionCard_InDarkTheme() {
TangemTheme(isDark = false) {
TransactionCardPreview()
}
}
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
@Composable
private fun Preview_TransactionCardWithPriceImpact_InDarkTheme() {
TangemTheme(isDark = false) {
TransactionCardPreviewWithPriceImpact()
}
}
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
@Composable
private fun Preview_TransactionCardWithoutPriceImpact_InDarkTheme() {
TangemTheme(isDark = false) {
TransactionCardPreviewWithoutPriceImpact()
}
}
@Composable
private fun TransactionCardPreview() {
TransactionCard(
@ -518,4 +568,20 @@ private fun TransactionCardPreviewWithPriceImpact() {
)
}
@Composable
@Suppress("MagicNumber")
private fun TransactionCardPreviewWithoutPriceImpact() {
TransactionCard(
type = TransactionCardType.ReadOnly(),
amountEquivalent = "1 000 000",
tokenIconUrl = "",
tokenCurrency = "DAI",
networkIconRes = R.drawable.img_polygon_22,
onChangeTokenClick = {},
balance = "123",
textFieldValue = TextFieldValue(),
priceImpact = PriceImpact.Empty(),
)
}
// endregion preview

View file

@ -18,7 +18,7 @@ data class SwapProcessDataState(
val approveDataModel: RequestApproveStateData? = null,
val approveType: ApproveType? = null,
val swapDataModel: SwapDataModel? = null,
val selectedFee: TxFee? = null, // todo
val selectedFee: TxFee? = null,
val tokensDataState: TokensDataStateExpress? = null,
val selectedProvider: SwapProvider? = null,
val lastLoadedSwapStates: Map<SwapProvider, SwapState> = emptyMap(),

View file

@ -879,8 +879,12 @@ internal class SwapViewModel @Inject constructor(
},
onReceiveCardWarningClick = {
val selectedProvider = dataState.selectedProvider ?: return@UiActions
uiState = stateBuilder.createImpactAlert(
val currencySymbol = dataState.toCryptoCurrency?.currency?.symbol ?: return@UiActions
val isPriceImpact = uiState.priceImpact is PriceImpact.Value
uiState = stateBuilder.createAlert(
uiState = uiState,
isPriceImpact = isPriceImpact,
token = currencySymbol,
providerType = selectedProvider.type,
) {
uiState = stateBuilder.clearAlert(uiState)