Updated on 2026-08-14
This commit is contained in:
commit
b2a27ca21f
12 changed files with 195 additions and 13 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -64,5 +64,6 @@ internal sealed class SendTextField {
|
|||
val title: TextReference,
|
||||
val footer: TextReference,
|
||||
val label: TextReference? = null,
|
||||
val isReadonly: Boolean = false,
|
||||
) : SendTextField()
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
@ -64,7 +64,7 @@ internal fun LazyListScope.customFee(
|
|||
item(
|
||||
key = FEE_CUSTOM_KEY,
|
||||
) {
|
||||
SendCustomFeeEthereum(
|
||||
SendCustomFee(
|
||||
customValues = feeSendState.customValues,
|
||||
selectedFee = feeSendState.selectedFee,
|
||||
hasNotifications = hasNotifications,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ^
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue