Updated on 2026-08-14

This commit is contained in:
Tangem 2024-02-28 20:02:55 +05:00
commit 451e0ed797
12 changed files with 112 additions and 57 deletions

View file

@ -480,14 +480,12 @@
<string name="send_max_fee">Комиссия не превысит </string>
<string name="send_max_fee_footer">Максимальная cумма комиссии</string>
<string name="common_network_fee_title">Сетевая комиссия</string>
<string name="send_network_fee_warning_content">Сумма отправки будет уменьшена для покрытия выбранного уровня комиссии</string>
<string name="send_network_fee_warning_content">Сумма отправки будет уменьшена на %1$s для покрытия выбранного уровня комиссии. Получателю будет отправлено %2$s.</string>
<string name="send_network_fee_warning_title">Покрытие сетевой комиссии</string>
<string name="send_notification_exceed_balance_text">Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса</string>
<string name="send_notification_exceed_balance_title">Недостаточно средств</string>
<string name="send_notification_high_fee_title">Увеличение комиссии</string>
<string name="send_notification_high_fee_text">Комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить 0.01.</string>
<string name="send_notification_fee_too_high_accept">Оставить %s XTZ</string>
<string name="send_notification_fee_too_high_ignore">Отправить все</string>
<string name="send_notification_fee_too_high_title">Установлена высокая комиссия</string>
<string name="send_notification_fee_too_high_text">Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна.</string>
<string name="send_notification_invalid_amount_text">Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению</string>
@ -497,8 +495,10 @@
<string name="send_notification_invalid_reserve_amount_title">Сумма отправки не может быть менее %1$s</string>
<string name="send_notification_transaction_delay_text">Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции</string>
<string name="send_notification_transaction_delay_title">Возможны задержки по транзакции</string>
<string name="send_notification_reduce_by">Уменьшить до [%s]</string>
<string name="send_notification_reduce_to">Уменьшить на [%s]</string>
<string name="send_notifiaction_transaction_limit_title">Лимит транзакции</string>
<string name="send_notification_transaction_limit_text">Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму.</string>
<string name="send_notification_reduce_by">Уменьшить до %s</string>
<string name="send_notification_reduce_to">Уменьшить на %s</string>
<string name="send_optional_field">Необязательное</string>
<string name="send_recent_transactions">Последние</string>
<string name="send_recipient">Получатель</string>

View file

@ -459,8 +459,9 @@
<string name="send_amount_substract_footer">The recipient will receive %s</string>
<string name="send_alert_transaction_failed_title">The transaction is not completed</string>
<string name="send_alert_transaction_failed_text">Reason: %1$s\nCode: %2$s</string>
<string name="send_alert_fee_coverage_title">Недостаточно средств для покрытия комиссии сети. Вычесть комиссию из отправляемой сумму?</string>
<string name="send_alert_fee_coverage_subract_text">Вычесть</string>
<string name="send_alert_fee_coverage_title">Not enough funds to cover the network commission. Subtract the commission from the amount sent?</string>
<string name="send_alert_fee_coverage_subract_text">Subtract</string>
<string name="send_alert_fee_too_low_text">Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить?</string>
<string name="send_confirm_label">Confirm</string>
<string name="send_date_format">%1$s at %2$s</string>
<string name="send_destination_hint_address">Address</string>
@ -494,8 +495,6 @@
<string name="send_notification_exceed_balance_title">Total exceeds balance</string>
<string name="send_notification_high_fee_title">Fee is increased</string>
<string name="send_notification_high_fee_text">The fee for transferring the entire balance is higher. To reduce the commission, you can leave 0.01.</string>
<string name="send_notification_fee_too_high_accept">Reduce by %s XTZ</string>
<string name="send_notification_fee_too_high_ignore">No, send all</string>
<string name="send_notification_fee_too_high_title">Custom fee is high</string>
<string name="send_notification_fee_too_high_text">The commission amount is %s times the recommended amount. Make sure that the custom settings are correct.</string>
<string name="send_notification_invalid_amount_text">The included commission exceeds the transfer amount, leading to a negative value</string>
@ -506,11 +505,11 @@
<string name="send_notification_transaction_delay_text">Kindly be aware that your transaction may experience delays under specific fee settings</string>
<string name="send_notification_transaction_delay_title">Transaction delays are possible</string>
<string name="send_notifiaction_transaction_limit_title">Transaction limitation</string>
<string name="send_notifiaction_transaction_limit_text">Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount.</string>
<string name="send_notification_transaction_limit_text">Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount.</string>
<string name="send_notification_existential_deposit_title">Existential deposit</string>
<string name="send_notification_existential_deposit_text">The account will be wiped from the blockchain if a balance goes below the existential deposit. Please ensure that the remaining balance after sending will not be less than %s.</string>
<string name="send_notification_reduce_by">Reduce by [%s]</string>
<string name="send_notification_reduce_to">Reduce to [%s]</string>
<string name="send_notification_reduce_by">Reduce by %s</string>
<string name="send_notification_reduce_to">Reduce to %s</string>
<string name="send_optional_field">Optional</string>
<string name="send_qrcode_scan_info">Please align your QR code with the square to scan it. Ensure you scan %s network address.</string>
<string name="send_recent_transactions">Recent</string>

View file

@ -50,6 +50,14 @@ internal sealed class SendAlertState {
override val message: TextReference = resourceReference(id = R.string.send_notification_high_fee_title)
}
data class FeeTooLow(
override val onConfirmClick: () -> Unit,
) : SendAlertState() {
override val title: TextReference? = null
override val message: TextReference = resourceReference(id = R.string.send_alert_fee_too_low_text)
override val confirmButtonText: TextReference = resourceReference(R.string.common_continue)
}
data class FeeCoverage(
override val onConfirmClick: (() -> Unit),
) : SendAlertState() {

View file

@ -3,6 +3,7 @@ package com.tangem.features.send.impl.presentation.state
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
import com.tangem.features.send.impl.presentation.state.fee.FeeStateFactory
@ -88,6 +89,29 @@ internal class SendEventStateFactory(
}
}
fun getFeeTooLowAlert(onConsume: () -> Unit): SendUiState {
val state = currentStateProvider()
val feeSelectorState = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return state
val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return state
val minimumValue = multipleFees.minimum.amount.value ?: return state
val customAmount = feeSelectorState.customValues.firstOrNull() ?: return state
val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals)
val isFeeTooLow = feeSelectorState.selectedFee == FeeType.Custom && minimumValue > customValue
if (!isFeeTooLow) return state
return state.copy(
event = triggeredEvent(
data = SendEvent.ShowAlert(
SendAlertState.FeeTooLow(
onConfirmClick = clickIntents::showSend,
),
),
onConsume = onConsume,
),
)
}
fun getGenericErrorState(error: Throwable? = null, onConsume: () -> Unit): SendUiState {
val state = currentStateProvider()
return state.copy(

View file

@ -12,12 +12,14 @@ internal sealed class SendNotification(val config: NotificationConfig) {
title: TextReference,
subtitle: TextReference,
buttonState: NotificationConfig.ButtonsState? = null,
onCloseClick: (() -> Unit)? = null,
) : SendNotification(
config = NotificationConfig(
title = title,
subtitle = subtitle,
iconResId = R.drawable.ic_alert_24,
buttonsState = buttonState,
onCloseClick = onCloseClick,
),
) {
@ -45,12 +47,17 @@ internal sealed class SendNotification(val config: NotificationConfig) {
val cryptoCurrency: String,
val utxoLimit: String,
val amountLimit: String,
val onConfirmClick: () -> Unit,
) : Error(
title = resourceReference(R.string.send_notifiaction_transaction_limit_title),
subtitle = resourceReference(
R.string.send_notifiaction_transaction_limit_text,
R.string.send_notification_transaction_limit_text,
wrappedList(cryptoCurrency, utxoLimit, amountLimit),
),
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
text = resourceReference(R.string.send_notification_reduce_to, wrappedList(amountLimit)),
onClick = onConfirmClick,
),
)
}
@ -58,27 +65,28 @@ internal sealed class SendNotification(val config: NotificationConfig) {
title: TextReference,
subtitle: TextReference,
buttonsState: NotificationConfig.ButtonsState? = null,
onCloseClick: (() -> Unit)? = null,
) : SendNotification(
config = NotificationConfig(
title = title,
subtitle = subtitle,
iconResId = R.drawable.img_attention_20,
buttonsState = buttonsState,
onCloseClick = onCloseClick,
),
) {
data class HighFeeError(
val amount: String,
val onConfirmClick: () -> Unit,
val onDismissClick: () -> Unit,
val onCloseClick: () -> Unit,
) : Warning(
title = resourceReference(R.string.send_notification_high_fee_title),
subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(amount)),
buttonsState = NotificationConfig.ButtonsState.PairButtonsConfig(
primaryText = resourceReference(R.string.send_notification_fee_too_high_accept, wrappedList(amount)),
onPrimaryClick = onConfirmClick,
secondaryText = resourceReference(R.string.send_notification_fee_too_high_ignore),
onSecondaryClick = onDismissClick,
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
text = resourceReference(R.string.send_notification_reduce_by, wrappedList(amount)),
onClick = onConfirmClick,
),
onCloseClick = onCloseClick,
)
data class ExistentialDeposit(val deposit: String) : Warning(
@ -96,5 +104,10 @@ internal sealed class SendNotification(val config: NotificationConfig) {
formatArgs = wrappedList(amountReducedBy, amountReduced),
),
)
data object FeeTooLow : Warning(
title = resourceReference(id = R.string.send_notification_transaction_delay_title),
subtitle = resourceReference(id = R.string.send_notification_transaction_delay_text),
)
}
}

View file

@ -1,11 +1,15 @@
package com.tangem.features.send.impl.presentation.state
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
import com.tangem.features.send.impl.presentation.state.fee.FeeType
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.isNullOrZero
@ -48,13 +52,16 @@ internal class SendNotificationFactory(
addFeeCoverageNotification(sendState.isSubtract, sendAmount)
addExistentialWarningNotification(feeAmount, sendAmount)
addHighFeeWarningNotification(sendAmount, sendState.ignoreAmountReduce)
addTooLowNotification(feeState)
}.toImmutableList()
}
fun dismissHighFeeWarningState(): SendUiState {
fun dismissNotificationState(clazz: Class<out SendNotification>): SendUiState {
val state = currentStateProvider()
val sendState = state.sendState
val updatedNotifications = sendState.notifications.filterNot { it is SendNotification.Warning.HighFeeError }
val notificationsToRemove = sendState.notifications.filterIsInstance(clazz)
val updatedNotifications = sendState.notifications.toMutableList()
updatedNotifications.removeAll(notificationsToRemove)
return state.copy(
sendState = sendState.copy(
ignoreAmountReduce = true,
@ -162,6 +169,13 @@ internal class SendNotificationFactory(
cryptoAmount = utxoLimit.maxAmount,
cryptoCurrency = cryptoCurrency,
),
onConfirmClick = {
val reduceTo = utxoLimit.maxAmount.toPlainString()
clickIntents.onAmountReduceClick(
reduceTo,
SendNotification.Error.TransactionLimitError::class.java,
)
},
),
)
}
@ -207,9 +221,11 @@ internal class SendNotificationFactory(
amount = TEZOS_FEE_THRESHOLD.toPlainString(),
onConfirmClick = {
val reduceTo = sendAmount.minus(TEZOS_FEE_THRESHOLD).toPlainString()
clickIntents.onAmountReduceClick(reduceTo)
clickIntents.onAmountReduceClick(reduceTo, SendNotification.Warning.HighFeeError::class.java)
},
onCloseClick = {
clickIntents.onNotificationCancel(SendNotification.Warning.HighFeeError::class.java)
},
onDismissClick = clickIntents::onAmountReduceIgnoreClick,
),
)
}
@ -261,6 +277,17 @@ internal class SendNotificationFactory(
}
}
private fun MutableList<SendNotification>.addTooLowNotification(feeState: SendStates.FeeState) {
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return
val minimumValue = multipleFees.minimum.amount.value ?: return
val customAmount = feeSelectorState.customValues.firstOrNull() ?: return
val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals)
if (feeSelectorState.selectedFee == FeeType.Custom && minimumValue > customValue) {
add(SendNotification.Warning.FeeTooLow)
}
}
companion object {
private const val CARDANO_MINIMUM = "1"
private const val DOGECOIN_MINIMUM = "0.01"

View file

@ -47,7 +47,6 @@ internal class FeeNotificationFactory(
is FeeSelectorState.Content -> {
val customFee = feeSelectorState.customValues
val selectedFee = feeSelectorState.selectedFee
addTooLowNotification(feeSelectorState.fees, selectedFee, customFee)
addTooHighNotification(feeSelectorState.fees, selectedFee, customFee)
addExceedsBalanceNotification(feeState.fee)
}
@ -61,20 +60,6 @@ internal class FeeNotificationFactory(
}
}
private fun MutableList<SendFeeNotification>.addTooLowNotification(
transactionFee: TransactionFee,
selectedFee: FeeType,
customFee: List<SendTextField.CustomFee>,
) {
val multipleFees = transactionFee as? TransactionFee.Choosable ?: return
val minimumValue = multipleFees.minimum.amount.value ?: return
val customAmount = customFee.firstOrNull() ?: return
val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals)
if (selectedFee == FeeType.Custom && minimumValue > customValue) {
add(SendFeeNotification.Warning.TooLow)
}
}
private fun MutableList<SendFeeNotification>.addTooHighNotification(
transactionFee: TransactionFee,
selectedFee: FeeType,

View file

@ -20,11 +20,6 @@ sealed class SendFeeNotification(val config: NotificationConfig) {
buttonsState = buttonsState,
),
) {
object TooLow : Warning(
title = resourceReference(id = R.string.send_notification_transaction_delay_title),
subtitle = resourceReference(id = R.string.send_notification_transaction_delay_text),
)
data class TooHigh(
val value: String,
) : Warning(

View file

@ -74,10 +74,7 @@ private fun LazyListScope.middleNotifications(
modifier: Modifier = Modifier,
) {
notifications(
configs = configs.filter {
it is SendFeeNotification.Warning.TooLow ||
it is SendFeeNotification.Warning.TooHigh
}.toImmutableList(),
configs = configs.filterIsInstance<SendFeeNotification.Warning.TooHigh>().toImmutableList(),
modifier = modifier,
)
}

View file

@ -117,10 +117,7 @@ internal fun SendSpeedSelector(
visible = fees.normal is Fee.Ethereum,
label = "Custom fee appearance animation",
) {
val showWarning = state.notifications.any {
it is SendFeeNotification.Warning.TooHigh ||
it is SendFeeNotification.Warning.TooLow
}
val showWarning = state.notifications.any { it is SendFeeNotification.Warning.TooHigh }
SendSpeedSelectorItem(
titleRes = R.string.common_fee_selector_option_custom,
iconRes = R.drawable.ic_edit_24,

View file

@ -3,6 +3,7 @@ package com.tangem.features.send.impl.presentation.viewmodel
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource
import com.tangem.features.send.impl.presentation.state.SendNotification
import com.tangem.features.send.impl.presentation.state.fee.FeeType
@Suppress("TooManyFunctions")
@ -57,12 +58,14 @@ internal interface SendClickIntents {
fun showFee()
fun showSend()
fun onExploreClick()
fun onShareClick()
fun onAmountReduceClick(reducedAmount: String)
fun onAmountReduceClick(reducedAmount: String, clazz: Class<out SendNotification>)
fun onAmountReduceIgnoreClick()
fun onNotificationCancel(clazz: Class<out SendNotification>)
// endregion
}

View file

@ -394,6 +394,9 @@ internal class SendViewModel @Inject constructor(
val currentState = stateRouter.currentState.value
val isCurrentFee = currentState.type == SendUiStateType.Fee
if (isCurrentFee) {
uiState = eventStateFactory.getFeeTooLowAlert(
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
)
val isFeeCoverage = checkFeeCoverage(uiState, cryptoCurrencyStatus)
if (isAmountSubtractAvailable && isFeeCoverage) {
uiState = eventStateFactory.getFeeCoverageAlert(
@ -606,6 +609,10 @@ internal class SendViewModel @Inject constructor(
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Fee))
}
override fun showSend() {
stateRouter.showSend()
}
override fun onExploreClick() {
analyticsEventHandler.send(SendAnalyticEvents.ExploreButtonClicked)
innerRouter.openUrl(uiState.sendState.txUrl)
@ -615,14 +622,14 @@ internal class SendViewModel @Inject constructor(
analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked)
}
override fun onAmountReduceClick(reducedAmount: String) {
override fun onAmountReduceClick(reducedAmount: String, clazz: Class<out SendNotification>) {
uiState = amountStateFactory.getOnAmountValueChange(reducedAmount)
uiState = sendNotificationFactory.dismissHighFeeWarningState()
uiState = sendNotificationFactory.dismissNotificationState(clazz)
loadFee()
}
override fun onAmountReduceIgnoreClick() {
uiState = sendNotificationFactory.dismissHighFeeWarningState()
override fun onNotificationCancel(clazz: Class<out SendNotification>) {
uiState = sendNotificationFactory.dismissNotificationState(clazz)
}
private fun verifyAndSendTransaction() {