Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-09 15:17:18 +05:00
commit b2a27ca21f
12 changed files with 195 additions and 13 deletions

View file

@ -57,6 +57,7 @@ fun InputRowEnterInfoAmount(
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
showDivider: Boolean = false,
isReadOnly: Boolean = false,
) {
DividerContainer(
modifier = modifier,
@ -83,6 +84,7 @@ fun InputRowEnterInfoAmount(
),
onValueChange = onValueChange,
color = textColor,
isEnabled = !isReadOnly,
textStyle = TangemTheme.typography.body2,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,

View file

@ -59,7 +59,7 @@ fun DecimalFormat.getValidatedNumberWithFixedDecimals(text: String, decimals: In
return if (filteredChars.count { it == decimalSeparator } == 1) {
val beforeDecimal = filteredChars.substringBefore(decimalSeparator)
val afterDecimal = filteredChars.substringAfter(decimalSeparator)
beforeDecimal + decimalSeparator + afterDecimal.take(decimals)
decimals.getWithIntegerDecimals(beforeDecimal, decimalSeparator, afterDecimal)
}
// If there is no dot, just take all digits
else {
@ -82,7 +82,7 @@ fun DecimalFormat.formatWithThousands(text: String, decimals: Int): String {
.joinToString(thousandsSeparator.toString())
.reversed()
val afterDecimal = localizedText.substringAfter(decimalSeparator)
beforeDecimal + decimalSeparator + afterDecimal.take(decimals)
decimals.getWithIntegerDecimals(beforeDecimal, decimalSeparator, afterDecimal)
}
// If there is no dot, just take all digits
else {
@ -150,4 +150,10 @@ fun BigDecimal.parseBigDecimal(decimals: Int, roundingMode: RoundingMode = Round
} catch (e: Exception) {
""
}
}
private fun Int.getWithIntegerDecimals(before: String, separator: Char, after: String): String = if (this == 0) {
before
} else {
before + separator + after.take(this)
}

View file

@ -5,6 +5,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee
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.impl.presentation.state.fee.custom.BitcoinCustomFeeConverter
import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
@ -24,6 +25,14 @@ internal class FeeConverter(
)
}
private val bitcoinCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) {
BitcoinCustomFeeConverter(
clickIntents = clickIntents,
appCurrencyProvider = appCurrencyProvider,
feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider,
)
}
override fun convert(value: FeeSelectorState.Content): Fee {
return when (val fees = value.fees) {
is TransactionFee.Choosable -> {
@ -46,6 +55,7 @@ internal class FeeConverter(
} else {
when (normalFee) {
is Fee.Ethereum -> ethereumCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues)
is Fee.Bitcoin -> bitcoinCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues)
else -> {
val customFee = customValues.firstOrNull()
Fee.Common(

View file

@ -3,6 +3,7 @@ package com.tangem.features.send.impl.presentation.state.fee
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.presentation.state.fee.custom.BitcoinCustomFeeConverter
import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
@ -25,22 +26,35 @@ internal class SendFeeCustomFieldConverter(
)
}
private val bitcoinCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) {
BitcoinCustomFeeConverter(
clickIntents = clickIntents,
appCurrencyProvider = appCurrencyProvider,
feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider,
)
}
override fun convert(value: Fee): ImmutableList<SendTextField.CustomFee> {
return when (value) {
is Fee.Ethereum -> {
ethereumCustomFeeConverter.convert(value)
}
is Fee.Ethereum -> ethereumCustomFeeConverter.convert(value)
is Fee.Bitcoin -> bitcoinCustomFeeConverter.convert(value)
else -> persistentListOf()
}
}
fun onValueChange(feeSelectorState: FeeSelectorState.Content, index: Int, value: String) = feeSelectorState.copy(
customValues = when (feeSelectorState.fees.normal) {
customValues = when (val fee = feeSelectorState.fees.normal) {
is Fee.Ethereum -> ethereumCustomFeeConverter.onValueChange(
customValues = feeSelectorState.customValues,
index = index,
value = value,
)
is Fee.Bitcoin -> bitcoinCustomFeeConverter.onValueChange(
customValues = feeSelectorState.customValues,
index = index,
value = value,
txSize = fee.txSize,
)
else -> feeSelectorState.customValues
},
)

View file

@ -0,0 +1,139 @@
package com.tangem.features.send.impl.presentation.state.fee.custom
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.extensions.isZero
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.utils.BigDecimalFormatter
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.impl.R
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
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 kotlinx.collections.immutable.toImmutableList
import java.math.BigDecimal
import java.math.RoundingMode
internal class BitcoinCustomFeeConverter(
private val clickIntents: SendClickIntents,
private val appCurrencyProvider: Provider<AppCurrency>,
private val feeCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
) : CustomFeeConverter<Fee.Bitcoin> {
override fun convert(value: Fee.Bitcoin): ImmutableList<SendTextField.CustomFee> {
val feeValue = value.amount.value
return persistentListOf(
SendTextField.CustomFee(
value = feeValue?.parseBigDecimal(value.amount.decimals).orEmpty(),
decimals = value.amount.decimals,
symbol = value.amount.currencySymbol,
onValueChange = { clickIntents.onCustomFeeValueChange(FEE_AMOUNT_INDEX, it) },
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Next,
keyboardType = KeyboardType.Number,
),
title = resourceReference(R.string.send_max_fee),
footer = resourceReference(R.string.send_max_fee_footer),
label = getFeeFormatted(feeValue),
keyboardActions = KeyboardActions(),
isReadonly = true,
),
SendTextField.CustomFee(
value = toSatoshiPerByte(
amount = feeValue,
decimals = value.amount.decimals,
txSize = value.txSize,
).toString(),
decimals = SATOSHI_DECIMALS,
symbol = "",
title = resourceReference(R.string.send_gas_price),
footer = resourceReference(R.string.send_gas_price_footer),
onValueChange = { clickIntents.onCustomFeeValueChange(FEE_SATOSHI_INDEX, it) },
keyboardOptions = KeyboardOptions(
imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done,
keyboardType = KeyboardType.Number,
),
keyboardActions = KeyboardActions(),
),
)
}
override fun convertBack(normalFee: Fee.Bitcoin, value: ImmutableList<SendTextField.CustomFee>): Fee.Bitcoin {
val feeAmount = value[FEE_AMOUNT_INDEX].value.parseToBigDecimal(value[FEE_AMOUNT_INDEX].decimals)
val satoshiPerByte = value[FEE_SATOSHI_INDEX].value.parseToBigDecimal(value[FEE_SATOSHI_INDEX].decimals)
return normalFee.copy(
amount = normalFee.amount.copy(value = feeAmount),
satoshiPerByte = satoshiPerByte,
)
}
fun onValueChange(
customValues: ImmutableList<SendTextField.CustomFee>,
index: Int,
value: String,
txSize: BigDecimal,
): ImmutableList<SendTextField.CustomFee> {
val mutableCustomValues = customValues.toMutableList()
return mutableCustomValues.apply {
if (index == FEE_SATOSHI_INDEX) {
val newSatoshiPerKb = value.parseToBigDecimal(this[FEE_SATOSHI_INDEX].decimals)
val newFeeAmount = newSatoshiPerKb.multiply(txSize)
.movePointLeft(this[FEE_AMOUNT_INDEX].decimals)
.setScale(this[FEE_AMOUNT_INDEX].decimals, RoundingMode.DOWN)
set(
FEE_AMOUNT_INDEX,
this[FEE_AMOUNT_INDEX].copy(
value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT_INDEX].decimals),
label = getFeeFormatted(newFeeAmount),
),
)
set(index, this[index].copy(value = value))
}
}.toImmutableList()
}
private fun getFeeFormatted(fee: BigDecimal?): TextReference {
val appCurrency = appCurrencyProvider()
val rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate
val fiatFee = rate?.let { fee?.multiply(it) }
return stringReference(
BigDecimalFormatter.formatFiatAmount(
fiatAmount = fiatFee,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
),
)
}
private fun checkExceedBalance(feeAmount: BigDecimal?): Boolean {
val cryptoCurrencyStatus = feeCryptoCurrencyStatusProvider()
val currencyCryptoAmount = cryptoCurrencyStatus?.value?.amount ?: BigDecimal.ZERO
return feeAmount == null || feeAmount.isZero() || feeAmount > currencyCryptoAmount
}
private fun toSatoshiPerByte(amount: BigDecimal?, decimals: Int, txSize: BigDecimal): BigDecimal? {
val newFeeAmount = amount?.movePointRight(decimals)
return newFeeAmount?.divide(
txSize,
SATOSHI_DECIMALS,
RoundingMode.HALF_UP,
)?.setScale(SATOSHI_DECIMALS, RoundingMode.HALF_UP)
}
private companion object {
private const val FEE_AMOUNT_INDEX = 0
private const val FEE_SATOSHI_INDEX = 1
private const val SATOSHI_DECIMALS = 0
}
}

View file

@ -0,0 +1,10 @@
package com.tangem.features.send.impl.presentation.state.fee.custom
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.ImmutableList
internal interface CustomFeeConverter<T : Fee> : Converter<T, ImmutableList<SendTextField.CustomFee>> {
fun convertBack(normalFee: T, value: ImmutableList<SendTextField.CustomFee>): T
}

View file

@ -18,7 +18,6 @@ import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -29,7 +28,7 @@ internal class EthereumCustomFeeConverter(
private val clickIntents: SendClickIntents,
private val appCurrencyProvider: Provider<AppCurrency>,
private val feeCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
) : Converter<Fee.Ethereum, ImmutableList<SendTextField.CustomFee>> {
) : CustomFeeConverter<Fee.Ethereum> {
override fun convert(value: Fee.Ethereum): ImmutableList<SendTextField.CustomFee> {
val feeValue = value.amount.value
@ -77,7 +76,7 @@ internal class EthereumCustomFeeConverter(
)
}
fun convertBack(normalFee: Fee.Ethereum, value: ImmutableList<SendTextField.CustomFee>): Fee.Ethereum {
override fun convertBack(normalFee: Fee.Ethereum, value: ImmutableList<SendTextField.CustomFee>): Fee.Ethereum {
val feeAmount = value[FEE_AMOUNT].value.parseToBigDecimal(value[FEE_AMOUNT].decimals)
val gasPrice = value[GAS_PRICE].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger()
val gasLimit = value[GAS_LIMIT].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger()

View file

@ -64,5 +64,6 @@ internal sealed class SendTextField {
val title: TextReference,
val footer: TextReference,
val label: TextReference? = null,
val isReadonly: Boolean = false,
) : SendTextField()
}

View file

@ -17,7 +17,7 @@ import com.tangem.features.send.impl.presentation.ui.common.FooterContainer
import kotlinx.collections.immutable.ImmutableList
@Composable
internal fun SendCustomFeeEthereum(
internal fun SendCustomFee(
customValues: ImmutableList<SendTextField.CustomFee>,
selectedFee: FeeType,
hasNotifications: Boolean,
@ -55,6 +55,7 @@ internal fun SendCustomFeeEthereum(
keyboardActions = value.keyboardActions,
onValueChange = value.onValueChange,
showDivider = false,
isReadOnly = value.isReadonly,
modifier = Modifier
.background(
color = TangemTheme.colors.background.action,

View file

@ -64,7 +64,7 @@ internal fun LazyListScope.customFee(
item(
key = FEE_CUSTOM_KEY,
) {
SendCustomFeeEthereum(
SendCustomFee(
customValues = feeSendState.customValues,
selectedFee = feeSendState.selectedFee,
hasNotifications = hasNotifications,

View file

@ -1406,10 +1406,10 @@ internal class SwapInteractorImpl @Inject constructor(
private fun Fee.getGasLimit(): Int {
return when (this) {
is Fee.Common -> 0
is Fee.Ethereum -> gasLimit.toInt()
is Fee.VeChain -> gasLimit.toInt()
is Fee.Aptos -> gasLimit.toInt()
else -> 0
}
}

View file

@ -85,7 +85,7 @@ leakcanary = "2.13"
# endregion Other libraries
# region Tangem
tangemBlockchainSdk = "release-app_5.9-560"
tangemBlockchainSdk = "release-app_5.9-580"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "release-app_5.9-340"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^