Updated on 2026-08-14

This commit is contained in:
Tangem 2024-02-26 15:43:49 +05:00
parent a3f451bb19
commit f98622acab
22 changed files with 123 additions and 233 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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),
),
)
}
}

View file

@ -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"

View file

@ -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))

View file

@ -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,

View file

@ -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()
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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,
)
}
}

View file

@ -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),

View file

@ -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(),

View file

@ -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
}
}
}

View file

@ -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,
),
)
}
}

View file

@ -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(),
)
}
}
}

View file

@ -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

View file

@ -43,7 +43,7 @@ internal interface SendClickIntents {
fun onCustomFeeValueChange(index: Int, value: String)
fun onSubtractSelect(value: Boolean)
fun onSubtractSelect()
fun onReadMoreClick()
// endregion

View file

@ -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
}

View file

@ -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(