Updated on 2026-08-14
This commit is contained in:
parent
a3f451bb19
commit
f98622acab
22 changed files with 123 additions and 233 deletions
|
|
@ -92,7 +92,6 @@
|
|||
<string name="common_explorer">Обозреватель</string>
|
||||
<string name="common_fee_label">Комиссия</string>
|
||||
<string name="common_fee_selector_footer">Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s</string>
|
||||
<string name="common_fee_selector_link_description">Подробнее</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>
|
||||
|
|
@ -111,6 +110,7 @@
|
|||
<string name="common_ok">OK</string>
|
||||
<string name="common_origin_card">Основная карта</string>
|
||||
<string name="common_paste">Вставить</string>
|
||||
<string name="common_read_more">Подробнее</string>
|
||||
<string name="common_receive">Получить</string>
|
||||
<string name="common_reject">Отклонить</string>
|
||||
<string name="common_reload">Перезагрузить</string>
|
||||
|
|
@ -457,6 +457,8 @@
|
|||
<string name="send_alert_button_request_support">Поддержка</string>
|
||||
<string name="send_alert_transaction_failed_title">Транзакция не выполнена</string>
|
||||
<string name="send_alert_transaction_failed_text">Причина: %1$s\nКод: %2$s</string>
|
||||
<string name="send_alert_fee_coverage_title">Недостаточно средств для покрытия комиссии сети. Вычесть комиссию из отправляемой сумму?</string>
|
||||
<string name="send_alert_fee_coverage_subract_text">Вычесть</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>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@
|
|||
<string name="common_explorer">Explorer</string>
|
||||
<string name="common_fee_label">Fee</string>
|
||||
<string name="common_fee_selector_footer">Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s</string>
|
||||
<string name="common_fee_selector_link_description">Read more</string>
|
||||
<string name="common_fee_selector_option_custom">Custom</string>
|
||||
<string name="common_fee_selector_option_fast">Fast</string>
|
||||
<string name="common_fee_selector_option_market">Market</string>
|
||||
|
|
@ -110,6 +109,7 @@
|
|||
<string name="common_ok">OK</string>
|
||||
<string name="common_origin_card">Primary Card</string>
|
||||
<string name="common_paste">Paste</string>
|
||||
<string name="common_read_more">Read more</string>
|
||||
<string name="common_receive">Receive</string>
|
||||
<string name="common_reject">Reject</string>
|
||||
<string name="common_reload">Reload</string>
|
||||
|
|
@ -459,6 +459,8 @@
|
|||
<string name="send_alert_button_request_support">Support</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_confirm_label">Confirm</string>
|
||||
<string name="send_date_format">%1$s at %2$s</string>
|
||||
<string name="send_destination_hint_address">Address</string>
|
||||
|
|
@ -486,7 +488,7 @@
|
|||
<string name="send_max_fee_footer">Maximum fee amount</string>
|
||||
<string name="send_memo_destination_tag_error">Numbers only for Destination Tag</string>
|
||||
<string name="common_network_fee_title">Network fee</string>
|
||||
<string name="send_network_fee_warning_content">Sending amount will be reduced to cover the selected fee level</string>
|
||||
<string name="send_network_fee_warning_content">Sending amount will be reduced by %1$s to cover the selected commission level. The recipient will get %2$s.</string>
|
||||
<string name="send_network_fee_warning_title">Network fee coverage</string>
|
||||
<string name="send_notification_exceed_balance_text">Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance</string>
|
||||
<string name="send_notification_exceed_balance_title">Total exceeds balance</string>
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ internal class SendOnNextScreenAnalyticSender(
|
|||
}
|
||||
analyticsEventHandler.send(SendAnalyticEvents.SelectedFee(selectedFee.name))
|
||||
}
|
||||
if (feeState.isSubtract) {
|
||||
analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount)
|
||||
}
|
||||
}
|
||||
SendUiStateType.Amount -> {
|
||||
val isFiatSelected = state.amountState?.amountTextField?.isFiatValue ?: return
|
||||
|
|
|
|||
|
|
@ -45,11 +45,20 @@ internal sealed class SendAlertState {
|
|||
override val message: TextReference = resourceReference(id = R.string.warning_demo_mode_message)
|
||||
}
|
||||
|
||||
object FeeIncreased : SendAlertState() {
|
||||
data object FeeIncreased : SendAlertState() {
|
||||
override val title: TextReference? = null
|
||||
override val message: TextReference = resourceReference(id = R.string.send_notification_high_fee_title)
|
||||
}
|
||||
|
||||
data class FeeCoverage(
|
||||
override val onConfirmClick: (() -> Unit),
|
||||
) : SendAlertState() {
|
||||
override val title: TextReference? = null
|
||||
override val message: TextReference = resourceReference(id = R.string.send_alert_fee_coverage_title)
|
||||
override val confirmButtonText: TextReference =
|
||||
resourceReference(id = R.string.send_alert_fee_coverage_subract_text)
|
||||
}
|
||||
|
||||
data class ReserveAmount(val amount: String) : SendAlertState() {
|
||||
override val title: TextReference =
|
||||
resourceReference(id = R.string.send_notification_invalid_reserve_amount_title, wrappedList(amount))
|
||||
|
|
|
|||
|
|
@ -43,6 +43,20 @@ internal class SendEventStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
fun getFeeCoverageAlert(onConsume: () -> Unit): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
event = triggeredEvent(
|
||||
data = SendEvent.ShowAlert(
|
||||
SendAlertState.FeeCoverage(
|
||||
onConfirmClick = clickIntents::onSubtractSelect,
|
||||
),
|
||||
),
|
||||
onConsume = onConsume,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getFeeUpdatedAlert(fee: TransactionFee, onConsume: () -> Unit, onFeeNotIncreased: () -> Unit): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val feeSelector = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return state
|
||||
|
|
|
|||
|
|
@ -85,5 +85,16 @@ internal sealed class SendNotification(val config: NotificationConfig) {
|
|||
title = resourceReference(R.string.send_notification_existential_deposit_title),
|
||||
subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)),
|
||||
)
|
||||
|
||||
data class NetworkCoverage(
|
||||
val amountReducedBy: String,
|
||||
val amountReduced: String,
|
||||
) : Warning(
|
||||
title = resourceReference(id = R.string.send_network_fee_warning_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.send_network_fee_warning_content,
|
||||
formatArgs = wrappedList(amountReducedBy, amountReduced),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -30,20 +30,22 @@ internal class SendNotificationFactory(
|
|||
.filter { it.type == SendUiStateType.Send }
|
||||
.map {
|
||||
val state = currentStateProvider()
|
||||
val sendState = state.sendState
|
||||
val feeState = state.feeState ?: return@map persistentListOf()
|
||||
val feeAmount = feeState.fee?.amount?.value ?: BigDecimal.ZERO
|
||||
val amountValue = state.amountState?.amountTextField?.value?.toBigDecimalOrNull() ?: BigDecimal.ZERO
|
||||
val sendAmount = if (feeState.isSubtract) feeState.receivedAmountValue else amountValue
|
||||
val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO
|
||||
val sendAmount = if (sendState.isSubtract) amountValue.minus(feeAmount) else amountValue
|
||||
buildList {
|
||||
// errors
|
||||
addExceedBalanceNotification(feeAmount, sendAmount)
|
||||
addInvalidAmountNotification(feeState.isSubtract, sendAmount)
|
||||
addInvalidAmountNotification(sendState.isSubtract, sendAmount)
|
||||
addMinimumAmountErrorNotification(feeAmount, sendAmount)
|
||||
addDustWarningNotification(feeAmount, sendAmount)
|
||||
addTransactionLimitErrorNotification(feeAmount, sendAmount)
|
||||
// warnings
|
||||
addFeeCoverageNotification(sendState.isSubtract, sendAmount)
|
||||
addExistentialWarningNotification(feeAmount, sendAmount)
|
||||
addHighFeeWarningNotification(amountValue, state.sendState.ignoreAmountReduce)
|
||||
addHighFeeWarningNotification(sendAmount, sendState.ignoreAmountReduce)
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
|
|
@ -234,6 +236,29 @@ internal class SendNotificationFactory(
|
|||
}
|
||||
}
|
||||
|
||||
private fun MutableList<SendNotification>.addFeeCoverageNotification(
|
||||
isSubtract: Boolean,
|
||||
amountValue: BigDecimal,
|
||||
) {
|
||||
val state = currentStateProvider()
|
||||
val cryptoCurrency = cryptoCurrencyStatusProvider().currency
|
||||
val feeAmount = state.feeState?.fee?.amount?.value ?: BigDecimal.ZERO
|
||||
|
||||
val amountReducedValue = amountValue.minus(feeAmount)
|
||||
val amountReducedByValue = amountValue.minus(amountReducedValue)
|
||||
val amountReducedBy = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = amountReducedByValue,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
val amountReduced = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = amountReducedValue,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
if (isSubtract) {
|
||||
add(SendNotification.Warning.NetworkCoverage(amountReducedBy, amountReduced))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CARDANO_MINIMUM = "1"
|
||||
private const val DOGECOIN_MINIMUM = "0.01"
|
||||
|
|
|
|||
|
|
@ -223,6 +223,13 @@ internal class SendStateFactory(
|
|||
//endregion
|
||||
|
||||
//region send
|
||||
fun onSubtractSelect(isSubtract: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
sendState = state.sendState.copy(isSubtract = isSubtract),
|
||||
)
|
||||
}
|
||||
|
||||
fun getSendingStateUpdate(isSending: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(sendState = state.sendState.copy(isSending = isSending))
|
||||
|
|
|
|||
|
|
@ -75,12 +75,7 @@ internal sealed class SendStates {
|
|||
override val type: SendUiStateType = SendUiStateType.Fee,
|
||||
override val isPrimaryButtonEnabled: Boolean = false,
|
||||
val feeSelectorState: FeeSelectorState,
|
||||
val isSubtractAvailable: Boolean,
|
||||
val isSubtract: Boolean,
|
||||
val isUserSubtracted: Boolean,
|
||||
val fee: Fee?,
|
||||
val receivedAmountValue: BigDecimal,
|
||||
val receivedAmount: String,
|
||||
val rate: BigDecimal?,
|
||||
val appCurrency: AppCurrency,
|
||||
val isFeeApproximate: Boolean,
|
||||
|
|
@ -94,6 +89,7 @@ internal sealed class SendStates {
|
|||
override val isPrimaryButtonEnabled: Boolean = true,
|
||||
val isSending: Boolean = false,
|
||||
val isSuccess: Boolean = false,
|
||||
val isSubtract: Boolean = false,
|
||||
val transactionDate: Long = 0L,
|
||||
val txUrl: String = "",
|
||||
val ignoreAmountReduce: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -119,12 +119,12 @@ internal class StateRouter(
|
|||
mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Fee, isFromConfirmation) }
|
||||
}
|
||||
|
||||
private fun continueToSend(show: () -> Unit) {
|
||||
if (currentState.value.isFromConfirmation) showSend() else show()
|
||||
}
|
||||
|
||||
private fun showSend() {
|
||||
fun showSend() {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.ConfirmationScreenOpened)
|
||||
mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Send, isFromConfirmation = false) }
|
||||
}
|
||||
|
||||
private fun continueToSend(show: () -> Unit) {
|
||||
if (currentState.value.isFromConfirmation) showSend() else show()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
package com.tangem.features.send.impl.presentation.state.fee
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Calculate receiving amount when fee is subtracted from sending amount
|
||||
* Check if sending amount with fee is greater than balance
|
||||
*/
|
||||
internal fun calculateReceiveAmount(state: SendUiState, feeAmount: Fee): BigDecimal {
|
||||
val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO
|
||||
val fee = feeAmount.amount.value ?: return BigDecimal.ZERO
|
||||
return amountValue.minus(fee)
|
||||
internal fun checkFeeCoverage(state: SendUiState, cryptoCurrencyStatus: CryptoCurrencyStatus): Boolean {
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: return false
|
||||
val fee = state.feeState?.fee?.amount?.value ?: return false
|
||||
val amount = state.amountState?.amountTextField?.cryptoAmount?.value ?: return false
|
||||
return balance <= amount + fee
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
|||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
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.fields.SendTextField
|
||||
|
|
@ -47,7 +46,6 @@ internal class FeeNotificationFactory(
|
|||
val selectedFee = feeSelectorState.selectedFee
|
||||
addTooLowNotification(feeSelectorState.fees, selectedFee, customFee)
|
||||
addTooHighNotification(feeSelectorState.fees, selectedFee, customFee)
|
||||
addFeeCoverageNotification(feeState, state.amountState)
|
||||
addExceedsBalanceNotification(feeState.fee)
|
||||
}
|
||||
}
|
||||
|
|
@ -89,20 +87,6 @@ internal class FeeNotificationFactory(
|
|||
}
|
||||
}
|
||||
|
||||
private fun MutableList<SendFeeNotification>.addFeeCoverageNotification(
|
||||
feeState: SendStates.FeeState,
|
||||
amountState: SendStates.AmountState?,
|
||||
) {
|
||||
if (!feeState.isSubtractAvailable) return
|
||||
|
||||
val cryptoAmount = coinCryptoCurrencyStatusProvider().value.amount ?: return
|
||||
val feeValue = feeState.fee?.amount?.value ?: return
|
||||
val value = amountState?.amountTextField?.cryptoAmount?.value ?: return
|
||||
if (cryptoAmount <= value + feeValue && feeState.isSubtract && !feeState.isUserSubtracted) {
|
||||
add(SendFeeNotification.Warning.NetworkCoverage)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun MutableList<SendFeeNotification>.addExceedsBalanceNotification(fee: Fee?) {
|
||||
val feeValue = fee?.amount?.value ?: BigDecimal.ZERO
|
||||
val userWalletId = userWalletProvider().walletId
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.features.send.impl.presentation.state.fee
|
|||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -14,7 +13,6 @@ import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
|||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Factory to produce fee state for [SendUiState]
|
||||
|
|
@ -22,7 +20,6 @@ import java.math.BigDecimal
|
|||
internal class FeeStateFactory(
|
||||
private val clickIntents: SendClickIntents,
|
||||
private val currentStateProvider: Provider<SendUiState>,
|
||||
private val coinCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val isFeeApproximateUseCase: IsFeeApproximateUseCase,
|
||||
|
|
@ -55,9 +52,8 @@ internal class FeeStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
fun onFeeOnLoadedState(fees: TransactionFee, isSubtractAvailable: Boolean): SendUiState {
|
||||
fun onFeeOnLoadedState(fees: TransactionFee): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val balance = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
|
||||
val feeState = state.feeState ?: return state
|
||||
val feeSelectorState = (feeState.feeSelectorState as? FeeSelectorState.Content)?.copy(
|
||||
fees = fees,
|
||||
|
|
@ -68,43 +64,15 @@ internal class FeeStateFactory(
|
|||
)
|
||||
|
||||
val fee = feeConverter.convert(feeSelectorState)
|
||||
val receivedAmount = calculateReceiveAmount(state, fee)
|
||||
return state.copy(
|
||||
feeState = feeState.copy(
|
||||
isSubtractAvailable = isSubtractAvailable,
|
||||
feeSelectorState = feeSelectorState,
|
||||
fee = fee,
|
||||
receivedAmountValue = receivedAmount,
|
||||
receivedAmount = getFormattedValue(receivedAmount),
|
||||
isSubtract = isSubtractAvailable && checkAutoSubtract(state, fee, balance),
|
||||
isFeeApproximate = isFeeApproximate(fee),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onFeeOnLoadedState(fees: TransactionFee): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val balance = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
|
||||
val feeState = state.feeState ?: return state
|
||||
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
|
||||
|
||||
val updatedFeeSelector = feeSelectorState.copy(
|
||||
fees = fees,
|
||||
customValues = customFeeFieldConverter.convert(fees.normal),
|
||||
)
|
||||
val fee = feeConverter.convert(updatedFeeSelector)
|
||||
val receivedAmount = calculateReceiveAmount(state, fee)
|
||||
return state.copy(
|
||||
feeState = feeState.copy(
|
||||
feeSelectorState = updatedFeeSelector,
|
||||
fee = fee,
|
||||
receivedAmountValue = receivedAmount,
|
||||
receivedAmount = getFormattedValue(receivedAmount),
|
||||
isSubtract = checkAutoSubtract(state, fee, balance),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onFeeOnErrorState(): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
|
|
@ -118,18 +86,13 @@ internal class FeeStateFactory(
|
|||
val state = currentStateProvider()
|
||||
val feeState = state.feeState ?: return state
|
||||
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
|
||||
val balance = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
|
||||
|
||||
val updatedFeeSelectorState = feeSelectorState.copy(selectedFee = feeType)
|
||||
val fee = feeConverter.convert(updatedFeeSelectorState)
|
||||
val receivedAmount = calculateReceiveAmount(state, fee)
|
||||
return state.copy(
|
||||
feeState = feeState.copy(
|
||||
fee = fee,
|
||||
feeSelectorState = updatedFeeSelectorState,
|
||||
receivedAmountValue = receivedAmount,
|
||||
receivedAmount = getFormattedValue(receivedAmount),
|
||||
isSubtract = checkAutoSubtract(state, fee, balance),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -139,34 +102,12 @@ internal class FeeStateFactory(
|
|||
val feeState = state.feeState ?: return state
|
||||
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
|
||||
val updatedFeeSelectorState = customFeeFieldConverter.onValueChange(feeSelectorState, index, value)
|
||||
val balance = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
|
||||
|
||||
val fee = feeConverter.convert(updatedFeeSelectorState)
|
||||
val receivedAmount = calculateReceiveAmount(state, fee)
|
||||
return state.copy(
|
||||
feeState = feeState.copy(
|
||||
feeSelectorState = updatedFeeSelectorState,
|
||||
fee = fee,
|
||||
receivedAmountValue = receivedAmount,
|
||||
receivedAmount = getFormattedValue(receivedAmount),
|
||||
isSubtract = checkAutoSubtract(state, fee, balance),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onSubtractSelect(value: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val feeState = state.feeState ?: return state
|
||||
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
|
||||
val fee = feeConverter.convert(feeSelectorState)
|
||||
val receivedAmount = calculateReceiveAmount(state, fee)
|
||||
return state.copy(
|
||||
feeState = feeState.copy(
|
||||
isSubtract = value,
|
||||
isUserSubtracted = true,
|
||||
receivedAmountValue = receivedAmount,
|
||||
receivedAmount = getFormattedValue(receivedAmount),
|
||||
fee = fee,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -187,9 +128,6 @@ internal class FeeStateFactory(
|
|||
): Boolean {
|
||||
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return false
|
||||
val customValue = feeSelectorState.customValues.firstOrNull()
|
||||
val balance = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
|
||||
val fee = feeConverter.convert(feeSelectorState)
|
||||
val feeValue = fee.amount.value ?: BigDecimal.ZERO
|
||||
|
||||
val isNotCustom = feeSelectorState.selectedFee != FeeType.Custom
|
||||
val isNotEmptyCustom = if (customValue != null) {
|
||||
|
|
@ -198,24 +136,8 @@ internal class FeeStateFactory(
|
|||
false
|
||||
}
|
||||
val noErrors = notifications.none { it is SendFeeNotification.Error }
|
||||
val isSubtractRequired = when {
|
||||
!feeState.isSubtractAvailable -> true // current currency is not fee currency
|
||||
feeValue + feeState.receivedAmountValue >= balance -> feeState.isSubtract
|
||||
else -> feeValue + feeState.receivedAmountValue <= balance
|
||||
}
|
||||
|
||||
return noErrors && isSubtractRequired && (isNotEmptyCustom || isNotCustom)
|
||||
}
|
||||
|
||||
private fun checkAutoSubtract(state: SendUiState, fee: Fee, balance: BigDecimal): Boolean {
|
||||
val feeState = state.feeState ?: return false
|
||||
val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO
|
||||
val feeAmount = fee.amount.value ?: BigDecimal.ZERO
|
||||
return if (feeState.isUserSubtracted) {
|
||||
feeState.isSubtract
|
||||
} else {
|
||||
amountValue + feeAmount >= balance
|
||||
}
|
||||
return noErrors && (isNotEmptyCustom || isNotCustom)
|
||||
}
|
||||
|
||||
private fun isFeeApproximate(fee: Fee): Boolean {
|
||||
|
|
@ -225,13 +147,4 @@ internal class FeeStateFactory(
|
|||
amountType = fee.amount.type,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getFormattedValue(value: BigDecimal): String {
|
||||
val cryptoCurrency = cryptoCurrencyStatusProvider().currency
|
||||
return BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = value,
|
||||
cryptoCurrency = cryptoCurrency.symbol,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -32,11 +32,6 @@ sealed class SendFeeNotification(val config: NotificationConfig) {
|
|||
subtitle = resourceReference(id = R.string.send_notification_fee_too_high_text, wrappedList(value)),
|
||||
)
|
||||
|
||||
object NetworkCoverage : Warning(
|
||||
title = resourceReference(id = R.string.send_network_fee_warning_title),
|
||||
subtitle = resourceReference(id = R.string.send_network_fee_warning_content),
|
||||
)
|
||||
|
||||
data class NetworkFeeUnreachable(val onRefresh: () -> Unit) : Warning(
|
||||
title = resourceReference(R.string.send_fee_unreachable_error_title),
|
||||
subtitle = resourceReference(R.string.send_fee_unreachable_error_text),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.features.send.impl.presentation.state.SendStates
|
|||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SendFeeStateConverter(
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
|
|
@ -17,12 +16,7 @@ internal class SendFeeStateConverter(
|
|||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
return SendStates.FeeState(
|
||||
feeSelectorState = FeeSelectorState.Loading,
|
||||
isSubtractAvailable = false,
|
||||
isSubtract = false,
|
||||
isUserSubtracted = false,
|
||||
fee = null,
|
||||
receivedAmountValue = BigDecimal.ZERO,
|
||||
receivedAmount = "",
|
||||
notifications = persistentListOf(),
|
||||
rate = cryptoCurrencyStatus.value.fiatRate,
|
||||
appCurrency = appCurrencyProvider(),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.utils.Provider
|
||||
|
|
@ -22,7 +21,6 @@ internal class SendAmountFieldChangeConverter(
|
|||
val state = currentStateProvider()
|
||||
val amountState = state.amountState ?: return state
|
||||
val amountTextField = amountState.amountTextField
|
||||
val feeState = state.feeState ?: return state
|
||||
|
||||
if (value.isEmpty()) return state.emptyState()
|
||||
val cryptoDecimals = amountTextField.cryptoAmount.decimals
|
||||
|
|
@ -36,7 +34,6 @@ internal class SendAmountFieldChangeConverter(
|
|||
|
||||
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
|
||||
val isExceedBalance = checkValue.checkExceedBalance(amountTextField)
|
||||
val isMaxAmount = checkValue.checkMaxAmount(amountTextField)
|
||||
return state.copy(
|
||||
amountState = amountState.copy(
|
||||
isPrimaryButtonEnabled = !isExceedBalance,
|
||||
|
|
@ -52,9 +49,6 @@ internal class SendAmountFieldChangeConverter(
|
|||
),
|
||||
),
|
||||
),
|
||||
feeState = feeState.copy(
|
||||
isSubtract = isMaxAmount,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -104,19 +98,4 @@ internal class SendAmountFieldChangeConverter(
|
|||
cryptoDecimal > currencyCryptoAmount || cryptoDecimal.isZero()
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.checkMaxAmount(amountTextField: SendTextField.AmountField): Boolean {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
|
||||
// If current currency is Token
|
||||
if (cryptoCurrencyStatus.currency is CryptoCurrency.Token) return false
|
||||
|
||||
val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO
|
||||
return if (amountTextField.isFiatValue) {
|
||||
parseToBigDecimal(amountTextField.fiatAmount.decimals) == currencyFiatAmount
|
||||
} else {
|
||||
parseToBigDecimal(amountTextField.cryptoAmount.decimals) == currencyCryptoAmount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,6 @@ internal class SendAmountFieldMaxAmountConverter(
|
|||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val amountState = state.amountState ?: return state
|
||||
val amountTextField = amountState.amountTextField
|
||||
val feeState = state.feeState ?: return state
|
||||
|
||||
val cryptoDecimals = amountTextField.cryptoAmount.decimals
|
||||
val fiatDecimals = amountTextField.fiatAmount.decimals
|
||||
|
|
@ -47,9 +46,6 @@ internal class SendAmountFieldMaxAmountConverter(
|
|||
),
|
||||
),
|
||||
),
|
||||
feeState = feeState.copy(
|
||||
isSubtract = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ import com.tangem.core.ui.components.notifications.Notification
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
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
|
||||
import com.tangem.features.send.impl.presentation.state.fee.SendFeeNotification
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -41,8 +40,6 @@ internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: S
|
|||
topNotifications(notifications)
|
||||
customFee(feeSendState)
|
||||
middleNotifications(notifications)
|
||||
subtractButton(state, clickIntents)
|
||||
bottomNotifications(notifications)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,17 +82,6 @@ private fun LazyListScope.middleNotifications(
|
|||
)
|
||||
}
|
||||
|
||||
private fun LazyListScope.bottomNotifications(
|
||||
configs: ImmutableList<SendFeeNotification>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
notifications(
|
||||
configs = configs.filterIsInstance<SendFeeNotification.Warning.NetworkCoverage>().toImmutableList(),
|
||||
isLast = true,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private fun LazyListScope.notifications(
|
||||
configs: ImmutableList<SendFeeNotification>,
|
||||
|
|
@ -159,36 +145,4 @@ internal fun LazyListScope.customFee(feeSendState: FeeSelectorState, modifier: M
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
internal fun LazyListScope.subtractButton(
|
||||
state: SendStates.FeeState,
|
||||
clickIntents: SendClickIntents,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val receivedAmount = state.receivedAmount
|
||||
val isSubtract = state.isSubtract
|
||||
val isSubtractAvailable = state.isSubtractAvailable
|
||||
val feeSendState = state.feeSelectorState
|
||||
if (isSubtractAvailable) {
|
||||
item {
|
||||
val feeStateContent = feeSendState as? FeeSelectorState.Content
|
||||
val isCustomAvailable = feeStateContent?.customValues.isNullOrEmpty().not()
|
||||
val isCustomSelected = feeStateContent?.selectedFee == FeeType.Custom
|
||||
val topPadding = if (isCustomSelected && isCustomAvailable) {
|
||||
TangemTheme.dimens.spacing12
|
||||
} else {
|
||||
TangemTheme.dimens.spacing20
|
||||
}
|
||||
SendSpeedSubtract(
|
||||
receivingAmount = receivedAmount,
|
||||
isSubtract = isSubtract,
|
||||
onSelectClick = clickIntents::onSubtractSelect,
|
||||
modifier = modifier
|
||||
.padding(top = topPadding)
|
||||
.animateItemPlacement(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -154,7 +154,7 @@ internal fun SendSpeedSelector(
|
|||
|
||||
@Composable
|
||||
private fun FooterText(onReadMoreClick: () -> Unit) {
|
||||
val linkText = stringResource(R.string.common_fee_selector_link_description)
|
||||
val linkText = stringResource(R.string.common_read_more)
|
||||
val fullString = stringResource(R.string.common_fee_selector_footer, linkText)
|
||||
val linkTextPosition = fullString.length - linkText.length
|
||||
val defaultStyle = TangemTheme.colors.text.tertiary
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ internal interface SendClickIntents {
|
|||
|
||||
fun onCustomFeeValueChange(index: Int, value: String)
|
||||
|
||||
fun onSubtractSelect(value: Boolean)
|
||||
fun onSubtractSelect()
|
||||
|
||||
fun onReadMoreClick()
|
||||
// endregion
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
|||
import com.tangem.features.send.impl.presentation.state.*
|
||||
import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeNotificationFactory
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeStateFactory
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeType
|
||||
import com.tangem.features.send.impl.presentation.state.fee.checkFeeCoverage
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -126,7 +126,6 @@ internal class SendViewModel @Inject constructor(
|
|||
private val feeStateFactory = FeeStateFactory(
|
||||
clickIntents = this,
|
||||
currentStateProvider = Provider { uiState },
|
||||
coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus },
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
||||
isFeeApproximateUseCase = isFeeApproximateUseCase,
|
||||
|
|
@ -411,6 +410,20 @@ internal class SendViewModel @Inject constructor(
|
|||
override fun popBackStack() = stateRouter.popBackStack()
|
||||
override fun onBackClick() = stateRouter.onBackClick(uiState.sendState.isSuccess)
|
||||
override fun onNextClick() {
|
||||
val currentState = stateRouter.currentState.value
|
||||
val isCurrentFee = currentState.type == SendUiStateType.Fee
|
||||
if (isCurrentFee) {
|
||||
val isFeeCoverage = checkFeeCoverage(uiState, cryptoCurrencyStatus)
|
||||
if (isAmountSubtractAvailable && isFeeCoverage) {
|
||||
uiState = eventStateFactory.getFeeCoverageAlert(
|
||||
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
|
||||
)
|
||||
return
|
||||
} else {
|
||||
uiState = stateFactory.onSubtractSelect(false)
|
||||
}
|
||||
}
|
||||
|
||||
val prevScreen = stateRouter.onNextClick()
|
||||
sendOnNextScreenAnalyticSender.send(prevScreen, uiState)
|
||||
}
|
||||
|
|
@ -520,9 +533,9 @@ internal class SendViewModel @Inject constructor(
|
|||
updateFeeNotifications()
|
||||
}
|
||||
|
||||
override fun onSubtractSelect(value: Boolean) {
|
||||
uiState = feeStateFactory.onSubtractSelect(value)
|
||||
updateFeeNotifications()
|
||||
override fun onSubtractSelect() {
|
||||
uiState = stateFactory.onSubtractSelect(true)
|
||||
stateRouter.showSend()
|
||||
}
|
||||
|
||||
override fun onReadMoreClick() {
|
||||
|
|
@ -539,12 +552,8 @@ internal class SendViewModel @Inject constructor(
|
|||
viewModelScope.launch(dispatchers.main) {
|
||||
uiState = feeStateFactory.onFeeOnLoadingState()
|
||||
uiState = callFeeUseCase()?.fold(
|
||||
ifRight = { fees ->
|
||||
feeStateFactory.onFeeOnLoadedState(fees, isAmountSubtractAvailable)
|
||||
},
|
||||
ifLeft = {
|
||||
feeStateFactory.onFeeOnErrorState()
|
||||
},
|
||||
ifRight = feeStateFactory::onFeeOnLoadedState,
|
||||
ifLeft = { feeStateFactory.onFeeOnErrorState() },
|
||||
) ?: feeStateFactory.onFeeOnErrorState()
|
||||
updateFeeNotifications()
|
||||
}.saveIn(feeJobHolder)
|
||||
|
|
@ -587,16 +596,19 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun showAmount() {
|
||||
uiState = stateFactory.onSubtractSelect(false)
|
||||
stateRouter.showAmount(isFromConfirmation = true)
|
||||
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Amount))
|
||||
}
|
||||
|
||||
override fun showRecipient() {
|
||||
uiState = stateFactory.onSubtractSelect(false)
|
||||
stateRouter.showRecipient(isFromConfirmation = true)
|
||||
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Address))
|
||||
}
|
||||
|
||||
override fun showFee() {
|
||||
uiState = stateFactory.onSubtractSelect(false)
|
||||
stateRouter.showFee(isFromConfirmation = true)
|
||||
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Fee))
|
||||
}
|
||||
|
|
@ -623,12 +635,12 @@ internal class SendViewModel @Inject constructor(
|
|||
private fun verifyAndSendTransaction() {
|
||||
val recipient = uiState.recipientState?.addressTextField?.value ?: return
|
||||
val feeState = uiState.feeState ?: return
|
||||
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
|
||||
val fee = feeState.fee ?: return
|
||||
val memo = uiState.recipientState?.memoTextField?.value
|
||||
val fee = feeStateFactory.feeConverter.convert(feeSelectorState)
|
||||
val amountValue = uiState.amountState?.amountTextField?.cryptoAmount?.value ?: return
|
||||
val amountToSend = if (feeState.isSubtract && isAmountSubtractAvailable) {
|
||||
feeState.receivedAmountValue
|
||||
val amountToSend = if (uiState.sendState.isSubtract && isAmountSubtractAvailable) {
|
||||
val feeValue = fee.amount.value ?: return
|
||||
amountValue.minus(feeValue)
|
||||
} else {
|
||||
amountValue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1119,7 +1119,7 @@ internal class StateBuilder(
|
|||
},
|
||||
readMoreUrl = buildReadMoreUrl(),
|
||||
feeItems = txFeeState.toFeeItemState(),
|
||||
readMore = resourceReference(R.string.common_fee_selector_link_description),
|
||||
readMore = resourceReference(R.string.common_read_more),
|
||||
onReadMoreClick = actions.onFeeReadMoreClick,
|
||||
)
|
||||
return uiState.copy(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue