Updated on 2026-08-14
This commit is contained in:
parent
50fa269746
commit
4cb147b27e
7 changed files with 345 additions and 10 deletions
|
|
@ -515,6 +515,13 @@ internal class SendConfirmModel @Inject constructor(
|
|||
feeCryptoCurrencyStatus = getCurrencyStatusForFeePayment(),
|
||||
),
|
||||
)
|
||||
val balance = cryptoCurrencyStatus.value.amount.orZero()
|
||||
val feeValue = confirmData.fee?.amount?.value.orZero()
|
||||
val isTotalSendingMoreThanBalance = confirmData.enteredAmount.orZero() + feeValue > balance
|
||||
val isFeeSubtractedFromAmount = isAmountSubtractAvailable && isTotalSendingMoreThanBalance
|
||||
// Fee alone can't be covered by the balance → nothing can be sent, footer must show $0.
|
||||
val isFeeExceedingBalance = isAmountSubtractAvailable && feeValue > balance
|
||||
|
||||
_uiState.update { state ->
|
||||
state.copy(
|
||||
confirmUM = SendConfirmationNotificationsTransformerV2(
|
||||
|
|
@ -524,6 +531,8 @@ internal class SendConfirmModel @Inject constructor(
|
|||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
isFeeSubtractedFromAmount = isFeeSubtractedFromAmount,
|
||||
isFeeExceedingBalance = isFeeExceedingBalance,
|
||||
).transform(uiState.value.confirmUM),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ import com.tangem.features.send.common.ui.state.ConfirmUM
|
|||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SendConfirmationNotificationsTransformerV2(
|
||||
private val feeSelectorUM: FeeSelectorUM,
|
||||
private val amountUM: AmountState,
|
||||
|
|
@ -29,6 +31,8 @@ internal class SendConfirmationNotificationsTransformerV2(
|
|||
private val cryptoCurrency: CryptoCurrency,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val analyticsCategoryName: String,
|
||||
private val isFeeSubtractedFromAmount: Boolean,
|
||||
private val isFeeExceedingBalance: Boolean,
|
||||
) : Transformer<ConfirmUM> {
|
||||
override fun transform(prevState: ConfirmUM): ConfirmUM {
|
||||
val state = prevState as? ConfirmUM.Content ?: return prevState
|
||||
|
|
@ -73,10 +77,14 @@ internal class SendConfirmationNotificationsTransformerV2(
|
|||
|
||||
val isFeeConvertibleToFiat = feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat
|
||||
|
||||
val fiatSendingValue = if (isFeeConvertibleToFiat) {
|
||||
fiatFeeValue?.let { fiatAmountValue?.plus(it) }
|
||||
} else {
|
||||
fiatAmountValue
|
||||
val fiatSendingValue = when {
|
||||
!isFeeConvertibleToFiat -> fiatAmountValue
|
||||
// Fee alone exceeds the balance → the transaction can't go through, nothing is sent.
|
||||
isFeeExceedingBalance -> BigDecimal.ZERO
|
||||
// When the fee is subtracted from the amount, the entered amount is the gross that already
|
||||
// includes the fee, so it must not be added again — that double-counts it.
|
||||
isFeeSubtractedFromAmount -> fiatAmountValue
|
||||
else -> fiatFeeValue?.let { fiatAmountValue?.plus(it) }
|
||||
}
|
||||
|
||||
val fiatSending = fiatSendingValue.format {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.features.send.send.confirm.model.transformers
|
||||
package com.tangem.features.send.v2.send.confirm.model.transformers
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.blockchain.common.Amount
|
||||
|
|
@ -8,6 +8,11 @@ import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
|||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -18,7 +23,9 @@ import com.tangem.features.send.api.entity.FeeFiatRateUM
|
|||
import com.tangem.features.send.api.entity.FeeItem
|
||||
import com.tangem.features.send.api.entity.FeeNonce
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.utils.formatFooterFiatFee
|
||||
import com.tangem.features.send.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.send.confirm.model.transformers.SendConfirmationNotificationsTransformerV2
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
|
@ -77,6 +84,8 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
isFeeSubtractedFromAmount = false,
|
||||
isFeeExceedingBalance = false,
|
||||
)
|
||||
val initialState: ConfirmUM = ConfirmUM.Empty
|
||||
|
||||
|
|
@ -99,6 +108,8 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
isFeeSubtractedFromAmount = false,
|
||||
isFeeExceedingBalance = false,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
|
|
@ -121,6 +132,8 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
isFeeSubtractedFromAmount = false,
|
||||
isFeeExceedingBalance = false,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
|
|
@ -134,6 +147,58 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
assertThat(content.sendingFooter).isNotEqualTo(initialState.sendingFooter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee subtracted from amount WHEN transform THEN footer sending excludes the fee`() = runTest {
|
||||
// GIVEN: fee is taken out of the entered amount, so the footer must show the amount alone (not amount + fee).
|
||||
val feeSelectorUM = createFiatConvertibleFeeSelectorUM(feeValue = BigDecimal("0.001"))
|
||||
val amountUM = createTestAmountUM()
|
||||
val transformer = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
isFeeSubtractedFromAmount = true,
|
||||
isFeeExceedingBalance = false,
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(createTestConfirmUM())
|
||||
|
||||
// THEN: sending = entered fiat amount (50.00), fee NOT added on top.
|
||||
val content = result as ConfirmUM.Content
|
||||
assertThat(content.sendingFooter).isEqualTo(
|
||||
expectedFiatFooter(sendingValue = BigDecimal("50.00"), feeSelectorUM = feeSelectorUM),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee exceeds balance WHEN transform THEN footer sending is zero`() = runTest {
|
||||
// GIVEN: the fee alone exceeds the balance → nothing can be sent.
|
||||
val feeSelectorUM = createFiatConvertibleFeeSelectorUM(feeValue = BigDecimal("0.001"))
|
||||
val amountUM = createTestAmountUM()
|
||||
val transformer = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
isFeeSubtractedFromAmount = true,
|
||||
isFeeExceedingBalance = true,
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(createTestConfirmUM())
|
||||
|
||||
// THEN: sending = $0.
|
||||
val content = result as ConfirmUM.Content
|
||||
assertThat(content.sendingFooter).isEqualTo(
|
||||
expectedFiatFooter(sendingValue = BigDecimal.ZERO, feeSelectorUM = feeSelectorUM),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee too high WHEN transform THEN returns state with too high notification`() = runTest {
|
||||
// GIVEN
|
||||
|
|
@ -146,6 +211,8 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
isFeeSubtractedFromAmount = false,
|
||||
isFeeExceedingBalance = false,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
|
|
@ -171,6 +238,8 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
isFeeSubtractedFromAmount = false,
|
||||
isFeeExceedingBalance = false,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
|
|
@ -197,6 +266,8 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
isFeeSubtractedFromAmount = false,
|
||||
isFeeExceedingBalance = false,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
|
|
@ -264,6 +335,43 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createFiatConvertibleFeeSelectorUM(feeValue: BigDecimal): FeeSelectorUM.Content {
|
||||
val fee = Fee.Common(amount = Amount(currencySymbol = "SOL", value = feeValue, decimals = 8))
|
||||
return FeeSelectorUM.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
fees = TransactionFee.Single(fee),
|
||||
feeItems = persistentListOf(FeeItem.Market(fee)),
|
||||
selectedFeeItem = FeeItem.Market(fee),
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = true,
|
||||
isTronToken = false,
|
||||
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
),
|
||||
feeFiatRateUM = FeeFiatRateUM(rate = BigDecimal("50000"), appCurrency = appCurrency),
|
||||
feeNonce = FeeNonce.Nonce(nonce = BigInteger.ZERO, onNonceChange = {}),
|
||||
)
|
||||
}
|
||||
|
||||
/** Builds the expected footer reference for a fiat-convertible fee, mirroring the transformer's formatting. */
|
||||
private fun expectedFiatFooter(sendingValue: BigDecimal, feeSelectorUM: FeeSelectorUM.Content): TextReference {
|
||||
val fee = feeSelectorUM.selectedFeeItem.fee
|
||||
val fiatFeeValue = fee.amount.value?.multiply(feeSelectorUM.feeFiatRateUM!!.rate)
|
||||
val sending = sendingValue.format {
|
||||
fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol)
|
||||
}
|
||||
val feeText = formatFooterFiatFee(
|
||||
amount = fee.amount.copy(value = fiatFeeValue),
|
||||
isFeeConvertibleToFiat = true,
|
||||
isFeeApproximate = feeSelectorUM.feeExtraInfo.isFeeApproximate,
|
||||
appCurrency = appCurrency,
|
||||
)
|
||||
return resourceReference(
|
||||
id = R.string.send_summary_transaction_description,
|
||||
formatArgs = wrappedList(sending, feeText),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createNormalFeeSelectorUM(): FeeSelectorUM.Content {
|
||||
val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue