Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-11 19:59:05 +05:00
parent 1264ada90d
commit 08ddee9acc
9 changed files with 73 additions and 76 deletions

View file

@ -79,7 +79,7 @@
<string name="common_explore_transaction_history">Посмотреть историю транзакций</string>
<string name="common_explorer">Обозреватель</string>
<string name="common_fee_label">Комиссия</string>
<string name="common_fee_selector_footer">Сетевые комиссии это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s</string>
<string name="common_fee_selector_footer">Сетевые комиссии это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s</string>
<string name="common_fee_selector_option_custom">Свое</string>
<string name="common_fee_selector_option_fast">Быстро</string>
<string name="common_fee_selector_option_market">По рынку</string>
@ -310,7 +310,7 @@
<string name="onboarding_exit_alert_message">В этом случае вам будет необходимо начать процесс заново.</string>
<string name="onboarding_exit_alert_title">Вы хотите выйти из процесса активации?</string>
<string name="onboarding_getting_started">Подготовка</string>
<string name="onboarding_linking_error_card_with_wallets">Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Хотите сбросить его и использовать карту для бэкапа?</string>
<string name="onboarding_linking_error_card_with_wallets">Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Если на нем есть средства, пожалуйста сначала выведите их, а затем сделайте сброс до заводских настроек и используйте как резервную.</string>
<string name="onboarding_navbar_title_creating_backup">Резервная копия</string>
<string name="onboarding_seed_button_read_more">Прочитать о seed-фразе</string>
<plurals name="onboarding_seed_generate_message_words_count">
@ -426,12 +426,12 @@
<string name="scan_card_settings_title">Приготовьте свою карту</string>
<string name="send_additional_field_already_included">Уже содержится в введенном адресе</string>
<string name="send_alert_fee_coverage_subract_text">Вычесть</string>
<string name="send_alert_fee_coverage_title">Недостаточно средств для покрытия комиссии сети. Вычесть комиссию %s из отправляемой сумму?</string>
<string name="send_alert_fee_coverage_title">Недостаточно средств для покрытия комиссии сети. Вычесть комиссию %s из отправляемой суммы?</string>
<string name="send_alert_fee_too_high_text">Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна.</string>
<string name="send_alert_fee_too_low_text">Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить?</string>
<string name="send_alert_transaction_failed_text">Причина: %1$s\nКод: %2$s</string>
<string name="send_alert_transaction_failed_title">Транзакция не выполнена</string>
<string name="send_amount_label">Сумма</string>
<string name="send_confirm_label">Подтверждение</string>
<string name="send_date_format">%1$s, %2$s</string>
<string name="send_destination_hint_address">Адрес</string>
<string name="send_destination_tag_field">Код назначения</string>
@ -536,7 +536,7 @@
<string name="toast_balances_hidden">Балансы скрыты</string>
<string name="toast_balances_shown">Балансы показаны</string>
<string name="toast_undo">Отменить</string>
<string name="token_button_unavailability_reason_buy_unavailable">В данный момент покупка монеты %s недоступна. Но мы работаем над её добавлением.</string>
<string name="token_button_unavailability_reason_buy_unavailable">В данный момент покупка монеты %s недоступна. Но мы работаем над её добавлением.</string>
<string name="token_button_unavailability_reason_empty_balance">У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства.</string>
<string name="token_button_unavailability_reason_no_quotes">Выбранная операция в данный момент недоступна. Попробуйте позже.</string>
<string name="token_button_unavailability_reason_not_exchangeable">Обмен %s не доступен. Но мы работаем над его добавлением.</string>

View file

@ -310,7 +310,7 @@
<string name="onboarding_exit_alert_message">In this case, you will need to start from the beginning.</string>
<string name="onboarding_exit_alert_title">Do you want to exit the activation process?</string>
<string name="onboarding_getting_started">Getting started</string>
<string name="onboarding_linking_error_card_with_wallets">Another wallet has already been created on the card you\'re trying to add. Do you want to reset it and use the card for a new wallet?</string>
<string name="onboarding_linking_error_card_with_wallets">Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup.</string>
<string name="onboarding_navbar_title_creating_backup">Creating a backup</string>
<string name="onboarding_seed_button_read_more">Read more about seed phrase</string>
<plurals name="onboarding_seed_generate_message_words_count">
@ -422,11 +422,11 @@
<string name="send_additional_field_already_included">Already included in the entered address</string>
<string name="send_alert_fee_coverage_subract_text">Subtract</string>
<string name="send_alert_fee_coverage_title">Not enough funds to cover the network commission. Subtract the commission %s from the amount sent?</string>
<string name="send_alert_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_alert_fee_too_low_text">You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue?</string>
<string name="send_alert_transaction_failed_text">Reason: %1$s\nCode: %2$s</string>
<string name="send_alert_transaction_failed_title">The transaction is not completed</string>
<string name="send_amount_label">Amount</string>
<string name="send_confirm_label">Confirm</string>
<string name="send_date_format">%1$s, %2$s</string>
<string name="send_destination_hint_address">Address</string>
<string name="send_destination_tag_field">Destination Tag</string>

View file

@ -58,6 +58,16 @@ internal sealed class SendAlertState {
override val confirmButtonText: TextReference = resourceReference(R.string.common_continue)
}
data class FeeTooHigh(
val times: String,
override val onConfirmClick: () -> Unit,
) : SendAlertState() {
override val title: TextReference? = null
override val message: TextReference =
resourceReference(id = R.string.send_alert_fee_too_high_text, wrappedList(times))
override val confirmButtonText: TextReference = resourceReference(R.string.common_continue)
}
data class FeeCoverage(
val amount: String,
override val onConfirmClick: (() -> Unit),

View file

@ -115,6 +115,20 @@ internal class SendEventStateFactory(
)
}
fun getFeeTooHighAlert(diff: String, onConsume: () -> Unit): SendUiState {
return currentStateProvider().copy(
event = triggeredEvent(
data = SendEvent.ShowAlert(
SendAlertState.FeeTooHigh(
onConfirmClick = clickIntents::showSend,
times = diff,
),
),
onConsume = onConsume,
),
)
}
fun getGenericErrorState(error: Throwable? = null, onConsume: () -> Unit): SendUiState {
val state = currentStateProvider()
return state.copy(

View file

@ -30,6 +30,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import java.math.BigDecimal
import java.math.BigInteger
@Suppress("LongParameterList")
internal class SendNotificationFactory(
@ -64,6 +65,7 @@ internal class SendNotificationFactory(
// warnings
addExistentialWarningNotification(feeAmount, amountValue)
addHighFeeWarningNotification(feeAmount, amountValue, sendState.ignoreAmountReduce)
addTooHighNotification(feeState.feeSelectorState)
addTooLowNotification(feeState)
}.toImmutableList()
}
@ -279,6 +281,18 @@ internal class SendNotificationFactory(
}
}
private fun MutableList<SendNotification>.addTooHighNotification(feeSelectorState: FeeSelectorState) {
if (feeSelectorState !is FeeSelectorState.Content) return
val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return
val highValue = multipleFees.priority.amount.value ?: return
val customAmount = feeSelectorState.customValues.firstOrNull() ?: return
val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals)
val diff = (customValue / highValue).toBigInteger()
if (feeSelectorState.selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF) {
add(SendNotification.Warning.TooHigh(diff.toString()))
}
}
private suspend fun MutableList<SendNotification>.addExceedsBalanceNotification(fee: Fee?) {
val feeValue = fee?.amount?.value ?: BigDecimal.ZERO
val userWalletId = userWalletProvider().walletId
@ -359,5 +373,6 @@ internal class SendNotificationFactory(
companion object {
private const val DOGECOIN_MINIMUM = "0.01"
private val TEZOS_FEE_THRESHOLD = BigDecimal("0.01")
internal val FEE_MAX_DIFF = BigInteger("5")
}
}

View file

@ -4,6 +4,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory
/**
* Check if sending amount with fee is greater than balance
@ -26,4 +27,19 @@ internal fun checkIfFeeTooLow(state: SendUiState): Boolean {
val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals)
return feeSelectorState.selectedFee == FeeType.Custom && minimumValue > customValue
}
/**
* Check if custom fee is too high
*/
internal fun checkIfFeeTooHigh(state: SendUiState, onShow: (String) -> Unit): Boolean {
val feeSelectorState = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false
val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return false
val highValue = multipleFees.priority.amount.value ?: return false
val customAmount = feeSelectorState.customValues.firstOrNull() ?: return false
val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals)
val diff = (customValue / highValue).toBigInteger()
val isShow = feeSelectorState.selectedFee == FeeType.Custom && diff > SendNotificationFactory.FEE_MAX_DIFF
if (isShow) onShow(diff.toString())
return isShow
}

View file

@ -1,20 +1,15 @@
package com.tangem.features.send.impl.presentation.state.fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.features.send.impl.presentation.state.SendNotification
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.SendUiStateType
import com.tangem.features.send.impl.presentation.state.StateRouter
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.state.SendNotification
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.toFormattedString
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import java.math.BigDecimal
@Suppress("LongParameterList")
internal class FeeNotificationFactory(
@ -29,16 +24,7 @@ internal class FeeNotificationFactory(
val state = currentStateProvider()
val feeState = state.feeState ?: return@map persistentListOf()
buildList {
when (val feeSelectorState = feeState.feeSelectorState) {
FeeSelectorState.Error -> {
addFeeUnreachableNotification(feeSelectorState)
}
is FeeSelectorState.Content -> {
val customFee = feeSelectorState.customValues
val selectedFee = feeSelectorState.selectedFee
addTooHighNotification(feeSelectorState.fees, selectedFee, customFee)
}
}
addFeeUnreachableNotification(feeState.feeSelectorState)
}.toImmutableList()
}
@ -47,24 +33,4 @@ internal class FeeNotificationFactory(
add(SendNotification.Warning.NetworkFeeUnreachable(clickIntents::feeReload))
}
}
private fun MutableList<SendNotification>.addTooHighNotification(
transactionFee: TransactionFee,
selectedFee: FeeType,
customFee: List<SendTextField.CustomFee>,
) {
val multipleFees = transactionFee as? TransactionFee.Choosable ?: return
val highValue = multipleFees.priority.amount.value ?: return
val customAmount = customFee.firstOrNull() ?: return
val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals)
val diff = customValue / highValue
if (selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF) {
add(SendNotification.Warning.TooHigh(diff.toFormattedString(HIGH_FEE_DIFF_DECIMALS)))
}
}
companion object {
private val FEE_MAX_DIFF = BigDecimal(5)
private const val HIGH_FEE_DIFF_DECIMALS = 0
}
}

View file

@ -3,7 +3,6 @@ package com.tangem.features.send.impl.presentation.ui.fee
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.animation.*
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
@ -12,15 +11,12 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.rows.SelectorRowItem
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.SendNotification
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
import com.tangem.features.send.impl.presentation.state.fee.FeeType
@ -64,11 +60,6 @@ internal fun SendSpeedSelectorItem(
showDivider = showDivider,
)
SendSpeedSelectorItemError(isError = feeSelectorState is FeeSelectorState.Error)
if (feeType == FeeType.Custom) {
val showWarning = state.notifications.any { it is SendNotification.Warning.TooHigh }
WarningIcon(showWarning = showWarning)
}
}
}
}
@ -97,29 +88,6 @@ private fun SendSpeedSelectorItemError(isError: Boolean) {
}
}
@Composable
private fun WarningIcon(showWarning: Boolean = false) {
Row {
SpacerWMax()
AnimatedVisibility(
visible = showWarning,
label = "Custom fee warning indicator",
enter = fadeIn(),
exit = fadeOut(),
) {
Image(
painter = painterResource(R.drawable.ic_alert_triangle_20),
contentDescription = null,
modifier = Modifier
.padding(
vertical = TangemTheme.dimens.spacing12,
horizontal = TangemTheme.dimens.spacing14,
),
)
}
}
}
private fun FeeSelectorState.Content.getAmount(feeType: FeeType): Amount? {
val choosableFees = fees as? TransactionFee.Choosable
return when (feeType) {

View file

@ -525,7 +525,15 @@ internal class SendViewModel @Inject constructor(
)
return true
}
return false
return checkIfFeeTooHigh(
state = uiState,
onShow = { diff ->
uiState = eventStateFactory.getFeeTooHighAlert(
diff = diff,
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
)
},
)
}
private fun onFeeCoverageAlert(): Boolean {