Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-04 19:48:09 +05:00
parent 4c720113f9
commit af88b092a2
9 changed files with 73 additions and 9 deletions

View file

@ -13,11 +13,18 @@ import java.math.BigInteger
@Immutable
sealed class FeeSelectorUM {
data object Loading : FeeSelectorUM()
abstract val isPrimaryButtonEnabled: Boolean
data class Error(val error: GetFeeError) : FeeSelectorUM()
data object Loading : FeeSelectorUM() {
override val isPrimaryButtonEnabled = false
}
data class Error(val error: GetFeeError) : FeeSelectorUM() {
override val isPrimaryButtonEnabled = false
}
data class Content(
override val isPrimaryButtonEnabled: Boolean,
val fees: TransactionFee,
val feeItems: ImmutableList<FeeItem>,
val selectedFeeItem: FeeItem,

View file

@ -1,10 +1,12 @@
package com.tangem.features.send.v2.feeselector.model.transformers
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.FeeItem
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
import com.tangem.utils.extensions.isZero
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.toImmutableList
@ -30,7 +32,16 @@ internal class FeeSelectorCustomValueChangedTransformer(
fee = customFeeConverter.convertBack(updatedCustomValues),
customValues = updatedCustomValues,
)
val customFeeValue = updatedCustomValues.firstOrNull()
val isNotEmptyCustom = if (customFeeValue != null) {
!customFeeValue.value.parseToBigDecimal(customFeeValue.decimals).isZero()
} else {
false
}
return state.copy(
isPrimaryButtonEnabled = isNotEmptyCustom,
feeItems = state.feeItems.map { if (it is FeeItem.Custom) newCustomFee else it }.toImmutableList(),
selectedFeeItem = newCustomFee,
)

View file

@ -49,6 +49,7 @@ internal class FeeSelectorLoadedTransformer(
val nonce = ((prevState as? FeeSelectorUM.Content)?.feeNonce as? FeeNonce.Nonce)?.nonce
return FeeSelectorUM.Content(
isPrimaryButtonEnabled = true,
fees = fees,
feeItems = feeItems,
selectedFeeItem = selectedFee,

View file

@ -203,6 +203,7 @@ private class FeeSelectorUMProvider : PreviewParameterProvider<FeeSelectorUM> {
override val values: Sequence<FeeSelectorUM> = sequenceOf(
FeeSelectorUM.Content(
isPrimaryButtonEnabled = true,
feeItems = persistentListOf(lowFeeItem),
selectedFeeItem = lowFeeItem,
feeExtraInfo = FeeExtraInfo(
@ -218,6 +219,7 @@ private class FeeSelectorUMProvider : PreviewParameterProvider<FeeSelectorUM> {
fees = TransactionFee.Single(lowFeeItem.fee),
),
FeeSelectorUM.Content(
isPrimaryButtonEnabled = false,
feeItems = persistentListOf(maxFeeItem),
selectedFeeItem = maxFeeItem,
feeExtraInfo = FeeExtraInfo(

View file

@ -87,6 +87,7 @@ internal fun FeeSelectorModalBottomSheet(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
enabled = state.isPrimaryButtonEnabled,
text = stringResourceSafe(R.string.common_done),
onClick = feeSelectorIntents::onDoneClick,
)
@ -484,6 +485,7 @@ private fun FeeSelectorBS_Preview(
private class FeeSelectorUMContentProvider : CollectionPreviewParameterProvider<FeeSelectorUM.Content>(
collection = listOf(
FeeSelectorUM.Content(
isPrimaryButtonEnabled = false,
feeItems = persistentListOf(
FeeItem.Suggested(
title = stringReference("Suggested by Tangem"),
@ -494,9 +496,6 @@ private class FeeSelectorUMContentProvider : CollectionPreviewParameterProvider<
FeeItem.Fast(fee = Fee.Common(Amount(value = BigDecimal("0.03"), blockchain = Blockchain.Ethereum))),
customFeeItem,
),
// selectedFeeItem = FeeItem.Market(
// amount = Amount(value = BigDecimal("0.02"), blockchain = Blockchain.Ethereum),
// ),
selectedFeeItem = customFeeItem,
feeExtraInfo = FeeExtraInfo(
isFeeApproximate = true,

View file

@ -11,6 +11,7 @@ import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.fee.model.checkExceedBalance
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter.Companion.ETHEREUM_GAS_UNIT
@ -18,7 +19,7 @@ import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.eth
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter.Companion.GAS_DECIMALS
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter.Companion.GIGA_DECIMALS
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.setEmpty
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import com.tangem.utils.extensions.isZero
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -91,7 +92,7 @@ internal class EthereumEIPCustomFeeConverter(
val mutableCustomValues = customValues.toMutableList()
return mutableCustomValues.apply {
when (index) {
FEE_AMOUNT -> setOnAmountChange(value, index)
FEE_AMOUNT -> setOnAmountChange(feeValue, value, index)
MAX_FEE -> setOnMaxFeeChange(value, index)
GAS_LIMIT -> setOnGasLimitChange(value, index)
else -> set(index, this[index].copy(value = value))
@ -99,8 +100,12 @@ internal class EthereumEIPCustomFeeConverter(
}.toImmutableList()
}
private fun MutableList<CustomFeeFieldUM>.setOnAmountChange(value: String, index: Int) {
val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals)
private fun MutableList<CustomFeeFieldUM>.setOnAmountChange(
feeValue: Fee.Ethereum.EIP1559,
value: String,
index: Int,
) {
val gasLimitRaw = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals)
if (value.isBlank()) {
setEmpty(FEE_AMOUNT)
setEmpty(MAX_FEE)
@ -108,6 +113,27 @@ internal class EthereumEIPCustomFeeConverter(
val newFeeAmountDecimal = value.parseToBigDecimal(this[FEE_AMOUNT].decimals)
val newFeeAmount = newFeeAmountDecimal.movePointRight(GIGA_DECIMALS) // from ETH to GWEI
val gasLimit = if (gasLimitRaw.isZero()) {
val gasLimitTemp = feeValue.gasLimit.toBigDecimal()
set(
index = GAS_LIMIT,
element = this[GAS_LIMIT].copy(value = gasLimitTemp.parseBigDecimal(GIGA_DECIMALS)),
)
gasLimitTemp
} else {
gasLimitRaw
}
if (this[PRIORITY_FEE].value.isBlank()) {
set(
index = PRIORITY_FEE,
element = this[PRIORITY_FEE].copy(
value = feeValue.priorityFee.toBigDecimal().movePointLeft(GIGA_DECIMALS)
.parseBigDecimal(GIGA_DECIMALS),
),
)
}
val newMaxFee = newFeeAmount.divide(gasLimit, this[MAX_FEE].decimals, RoundingMode.HALF_UP)
set(

View file

@ -242,6 +242,7 @@ class SendConfirmationNotificationsTransformerV2Test {
)
val transactionFee = TransactionFee.Single(fee)
return FeeSelectorUM.Content(
isPrimaryButtonEnabled = true,
fees = transactionFee,
feeItems = persistentListOf(FeeItem.Market(fee)),
selectedFeeItem = FeeItem.Market(fee),
@ -282,6 +283,7 @@ class SendConfirmationNotificationsTransformerV2Test {
priority = priorityFee,
)
return FeeSelectorUM.Content(
isPrimaryButtonEnabled = true,
fees = transactionFee,
feeItems = persistentListOf(
FeeItem.Custom(
@ -364,6 +366,7 @@ class SendConfirmationNotificationsTransformerV2Test {
priority = fee,
)
return FeeSelectorUM.Content(
isPrimaryButtonEnabled = true,
fees = transactionFee,
feeItems = persistentListOf(
FeeItem.Custom(
@ -434,6 +437,7 @@ class SendConfirmationNotificationsTransformerV2Test {
priority = priorityFee,
)
return FeeSelectorUM.Content(
isPrimaryButtonEnabled = true,
fees = transactionFee,
feeItems = persistentListOf(
FeeItem.Custom(

View file

@ -72,6 +72,7 @@ class TransformersComparisonTest {
val contentV1 = resultV1 as ConfirmUM.Content
val contentV2 = resultV2 as ConfirmUM.Content
assertThat(contentV1.isPrimaryButtonEnabled).isEqualTo(contentV2.isPrimaryButtonEnabled)
assertThat(contentV1.notifications.size).isEqualTo(contentV2.notifications.size)
assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter)
assertThat(contentV1.isPrimaryButtonEnabled).isEqualTo(contentV2.isPrimaryButtonEnabled)
@ -117,6 +118,8 @@ class TransformersComparisonTest {
val contentV1 = resultV1 as ConfirmUM.Content
val contentV2 = resultV2 as ConfirmUM.Content
assertThat(contentV1.isPrimaryButtonEnabled).isTrue()
assertThat(contentV2.isPrimaryButtonEnabled).isTrue()
assertThat(contentV1.notifications).hasSize(1)
assertThat(contentV2.notifications).hasSize(1)
assertThat(contentV1.notifications.first()).isInstanceOf(NotificationUM.Warning.FeeTooLow::class.java)
@ -165,6 +168,8 @@ class TransformersComparisonTest {
val contentV1 = resultV1 as ConfirmUM.Content
val contentV2 = resultV2 as ConfirmUM.Content
assertThat(contentV1.isPrimaryButtonEnabled).isTrue()
assertThat(contentV2.isPrimaryButtonEnabled).isTrue()
assertThat(contentV1.notifications).hasSize(1)
assertThat(contentV2.notifications).hasSize(1)
assertThat(contentV1.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java)
@ -215,6 +220,8 @@ class TransformersComparisonTest {
val contentV1 = resultV1 as ConfirmUM.Content
val contentV2 = resultV2 as ConfirmUM.Content
assertThat(contentV1.isPrimaryButtonEnabled).isTrue()
assertThat(contentV2.isPrimaryButtonEnabled).isTrue()
assertThat(contentV1.notifications).hasSize(2)
assertThat(contentV2.notifications).hasSize(2)
@ -268,6 +275,7 @@ class TransformersComparisonTest {
assertThat(contentV1.notifications).isEmpty()
assertThat(contentV2.notifications).isEmpty()
assertThat(contentV1.isPrimaryButtonEnabled).isEqualTo(contentV2.isPrimaryButtonEnabled)
assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter)
}
@ -377,6 +385,7 @@ class TransformersComparisonTest {
)
val transactionFee = TransactionFee.Single(fee)
return FeeSelectorUMV2.Content(
isPrimaryButtonEnabled = true,
fees = transactionFee,
feeItems = persistentListOf(
FeeItem.Market(fee),
@ -470,6 +479,7 @@ class TransformersComparisonTest {
priority = fee,
)
return FeeSelectorUMV2.Content(
isPrimaryButtonEnabled = true,
fees = transactionFee,
feeItems = persistentListOf(
FeeItem.Custom(
@ -605,6 +615,7 @@ class TransformersComparisonTest {
priority = priorityFee,
)
return FeeSelectorUMV2.Content(
isPrimaryButtonEnabled = true,
fees = transactionFee,
feeItems = persistentListOf(
FeeItem.Custom(
@ -740,6 +751,7 @@ class TransformersComparisonTest {
priority = priorityFee,
)
return FeeSelectorUMV2.Content(
isPrimaryButtonEnabled = true,
fees = transactionFee,
feeItems = persistentListOf(
FeeItem.Custom(
@ -839,6 +851,7 @@ class TransformersComparisonTest {
)
val transactionFee = TransactionFee.Single(fee)
return FeeSelectorUMV2.Content(
isPrimaryButtonEnabled = true,
fees = transactionFee,
feeItems = persistentListOf(
FeeItem.Market(fee),

View file

@ -379,6 +379,7 @@ private fun SendWithSwapSuccessContent_Preview() {
),
feeFiatRateUM = null,
feeNonce = FeeNonce.None,
isPrimaryButtonEnabled = false,
),
confirmUM = ConfirmUM.Success(
isPrimaryButtonEnabled = true,