From c04abfe0bbc22e59bc87687208e8313e3d11c7b6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 May 2024 18:08:47 +0500 Subject: [PATCH 1/3] Updated on 2026-08-14 --- .../ui/components/fields/AmountTextField.kt | 60 ++++++++++++------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt index 521245b940..2713162e91 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.* +import java.math.BigDecimal import java.text.DecimalFormat /** @@ -123,32 +124,49 @@ fun AmountTextField( private fun prepareEnter(oldValue: String, newValue: String, decimalFormat: DecimalFormat, decimals: Int): String { val decimalSymbol = decimalFormat.decimalFormatSymbols.decimalSeparator return if (decimalFormat.isValidSymbols(newValue)) { - val parsedValue = newValue.parseBigDecimalOrNull()?.toPlainString() - ?: if (newValue.isBlank()) "" else oldValue - val replacedWithSymbol = if (parsedValue.findLast { it != decimalSymbol } != null) { - when { - parsedValue.findLast { it == COMMA_SEPARATOR } != null -> { - parsedValue.replace(COMMA_SEPARATOR, decimalSymbol) - } - parsedValue.findLast { it == POINT_SEPARATOR } != null -> { - parsedValue.replace(POINT_SEPARATOR, decimalSymbol) - } - else -> parsedValue - } - } else { - parsedValue - } - val joinedSymbol = if (newValue.endsWith(COMMA_SEPARATOR) || newValue.endsWith(POINT_SEPARATOR)) { - replacedWithSymbol.plus(decimalSymbol) - } else { - replacedWithSymbol - } - decimalFormat.getValidatedNumberWithFixedDecimals(joinedSymbol, decimals) + val parsedDecimal = newValue.parseBigDecimalOrNull() + val parsedValue = parsedDecimal?.toPlainString() ?: if (newValue.isBlank()) "" else oldValue + + val replacedWithSymbol = parsedValue.replaceDecimalSymbol(decimalSymbol) + val joinedSymbol = replacedWithSymbol.preserveDecimalSymbol(newValue, decimalSymbol) + val withPreservedZeros = joinedSymbol.preserveTrailingZeros(newValue, parsedDecimal, decimalSymbol) + decimalFormat.getValidatedNumberWithFixedDecimals(withPreservedZeros, decimals) } else { oldValue } } +private fun String.replaceDecimalSymbol(decimalSymbol: Char) = if (this.findLast { it != decimalSymbol } != null) { + when { + this.findLast { it == COMMA_SEPARATOR } != null -> { + this.replace(COMMA_SEPARATOR, decimalSymbol) + } + this.findLast { it == POINT_SEPARATOR } != null -> { + this.replace(POINT_SEPARATOR, decimalSymbol) + } + else -> this + } +} else { + this +} + +private fun String.preserveDecimalSymbol(newValue: String, decimalSymbol: Char) = if ( + newValue.endsWith(COMMA_SEPARATOR) || newValue.endsWith(POINT_SEPARATOR) +) { + this.plus(decimalSymbol) +} else { + this +} + +private fun String.preserveTrailingZeros(newValue: String, parsedDecimal: BigDecimal?, decimalSymbol: Char): String { + val trailingZeros = newValue.split(decimalSymbol).getOrNull(1)?.takeLastWhile { it == '0' }.orEmpty() + return if (parsedDecimal?.scale() == 0 && trailingZeros.isNotEmpty()) { + "$this$decimalSymbol$trailingZeros" + } else { + this.plus(trailingZeros) + } +} + private fun DecimalFormat.isValidSymbols(text: String): Boolean { return checkDecimalSeparatorDuplicate(text) } From 8f5a03e936ff47def4104e8cefe9ef041e31095d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 May 2024 18:10:00 +0500 Subject: [PATCH 2/3] Updated on 2026-08-14 --- .../presentation/state/SendStateFactory.kt | 8 +++++ .../impl/presentation/state/SendUiState.kt | 1 - .../amount/SendAmountReduceToConverter.kt | 3 -- .../confirm/SendConfirmStateConverter.kt | 1 - .../state/confirm/SendNotificationFactory.kt | 6 ++-- .../presentation/state/fee/FeeCalculation.kt | 36 +++++-------------- .../presentation/viewmodel/SendViewModel.kt | 2 +- 7 files changed, 21 insertions(+), 36 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index 631cbdf100..f2ea95f678 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -296,6 +296,7 @@ internal class SendStateFactory( balance = balance, amountValue = amountValue, feeValue = feeValue, + reduceAmountBy = state.sendState?.reduceAmountBy, ), ) } @@ -331,6 +332,12 @@ internal class SendStateFactory( fun getSendNotificationState(notifications: ImmutableList): SendUiState { val state = currentStateProvider() val sendState = state.sendState ?: return state + val reducedBy = sendState.reduceAmountBy.takeIf { + notifications.none { + it is SendNotification.Error.ExistentialDeposit || + it is SendNotification.Error.TransactionLimitError + } + } return state.copy( sendState = sendState.copy( isPrimaryButtonEnabled = isPrimaryButtonEnabled( @@ -338,6 +345,7 @@ internal class SendStateFactory( isSending = sendState.isSending, notifications = notifications, ), + reduceAmountBy = reducedBy, notifications = notifications, showTapHelp = sendState.showTapHelp && notifications.isEmpty(), ), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt index 84adf93793..c7aa7ddff0 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt @@ -142,7 +142,6 @@ internal sealed class SendStates { val txUrl: String, val ignoreAmountReduce: Boolean, val reduceAmountBy: BigDecimal?, - val reduceAmountTo: BigDecimal?, val isFromConfirmation: Boolean, val showTapHelp: Boolean, val notifications: ImmutableList, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt index 5b40022bbb..76fd7e2654 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt @@ -38,9 +38,6 @@ internal class SendAmountReduceToConverter( val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else value.isZero() return state.copyWrapped( isEditState = isEditState, - sendState = state.sendState?.copy( - reduceAmountBy = value, - ), amountState = amountState.copy( isPrimaryButtonEnabled = !isExceedBalance && !isZero, amountTextField = amountTextField.copy( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt index 6e6e67132b..254a979807 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt @@ -17,7 +17,6 @@ internal class SendConfirmStateConverter( txUrl = "", ignoreAmountReduce = false, reduceAmountBy = null, - reduceAmountTo = null, isFromConfirmation = true, showTapHelp = isTapHelpPreviewEnabledProvider(), notifications = persistentListOf(), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index ad764dbb71..f6a100ff4e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -60,18 +60,20 @@ internal class SendNotificationFactory( val amountValue = amountState.amountTextField.cryptoAmount.value ?: BigDecimal.ZERO val feeValue = feeState.fee?.amount?.value ?: BigDecimal.ZERO + val reduceAmountBy = sendState.reduceAmountBy ?: BigDecimal.ZERO val isFeeCoverage = checkFeeCoverage( isSubtractAvailable = isSubtractAvailableProvider(), balance = balance, amountValue = amountValue, feeValue = feeValue, + reduceAmountBy = reduceAmountBy, ) val sendingAmount = checkAndCalculateSubtractedAmount( isAmountSubtractAvailable = isFeeCoverage, cryptoCurrencyStatus = cryptoCurrencyStatusProvider(), amountValue = amountValue, feeValue = feeValue, - reduceAmountBy = sendState.reduceAmountBy, + reduceAmountBy = reduceAmountBy, ) buildList { // errors @@ -211,7 +213,7 @@ internal class SendNotificationFactory( ), onConfirmClick = { clickIntents.onAmountReduceClick( - reduceAmountBy = currencyDeposit, + reduceAmountBy = currencyDeposit.minus(diff), clazz = SendNotification.Error.ExistentialDeposit::class.java, ) }, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt index a8a93bcd32..b500af1923 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt @@ -16,7 +16,7 @@ internal fun checkAndCalculateSubtractedAmount( cryptoCurrencyStatus: CryptoCurrencyStatus, amountValue: BigDecimal, feeValue: BigDecimal, - reduceAmountBy: BigDecimal?, + reduceAmountBy: BigDecimal, ): BigDecimal { val balance = cryptoCurrencyStatus.value.amount ?: return amountValue val isFeeCoverage = checkFeeCoverage( @@ -24,17 +24,12 @@ internal fun checkAndCalculateSubtractedAmount( balance = balance, amountValue = amountValue, feeValue = feeValue, + reduceAmountBy = reduceAmountBy, ) - val subtractedAmount = calculateSubtractedAmount( - isFeeCoverage = isFeeCoverage, - cryptoCurrencyStatus = cryptoCurrencyStatus, - amountValue = amountValue, - feeValue = feeValue, - ) - return if (reduceAmountBy != null) { - subtractedAmount.minus(reduceAmountBy) + return if (isFeeCoverage) { + balance.minus(reduceAmountBy).minus(feeValue) } else { - subtractedAmount + amountValue.minus(reduceAmountBy) } } @@ -46,26 +41,11 @@ internal fun checkFeeCoverage( balance: BigDecimal, amountValue: BigDecimal, feeValue: BigDecimal, + reduceAmountBy: BigDecimal?, ): Boolean { if (!isSubtractAvailable) return false - return balance < amountValue + feeValue && balance > feeValue && balance >= amountValue -} - -/** - * Calculates subtracted amount - */ -private fun calculateSubtractedAmount( - isFeeCoverage: Boolean, - cryptoCurrencyStatus: CryptoCurrencyStatus, - amountValue: BigDecimal, - feeValue: BigDecimal, -): BigDecimal { - val balance = cryptoCurrencyStatus.value.amount ?: return amountValue - return if (isFeeCoverage) { - minOf(amountValue, balance.minus(feeValue)) - } else { - amountValue - } + val amountWithReduced = (reduceAmountBy ?: BigDecimal.ZERO) + amountValue + return balance < amountWithReduced + feeValue && balance > feeValue && balance >= amountWithReduced } /** diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index a74e2df9ab..078ff442a9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -823,7 +823,7 @@ internal class SendViewModel @Inject constructor( cryptoCurrencyStatus = cryptoCurrencyStatus, amountValue = amountValue, feeValue = feeValue, - reduceAmountBy = uiState.sendState?.reduceAmountBy, + reduceAmountBy = uiState.sendState?.reduceAmountBy ?: BigDecimal.ZERO, ) viewModelScope.launch(dispatchers.main) { From dd45e6b4d5509095c0e2ffa8328c3977d4d1c95e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 May 2024 20:05:02 +0500 Subject: [PATCH 3/3] Updated on 2026-08-14 --- .../tokens/GetNetworkAddressesUseCase.kt | 17 ++++++++++------- .../SendRecipientWalletListConverter.kt | 2 ++ .../presentation/viewmodel/SendViewModel.kt | 18 ++++++++++-------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt index 0cc768a333..aea1cad8eb 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt @@ -11,14 +11,17 @@ class GetNetworkAddressesUseCase( internal val networksRepository: NetworksRepository, ) { - operator fun invoke(userWalletId: UserWalletId, network: Network): Flow = + operator fun invoke(userWalletId: UserWalletId, network: Network): Flow> = networksRepository.getNetworkStatusesUpdates(userWalletId, setOf(network)) .map { networkStatuses -> - when (val networkStatus = networkStatuses.singleOrNull { it.network.id == network.id }?.value) { - is NetworkStatus.NoAccount -> networkStatus.address.defaultAddress.value - is NetworkStatus.Unreachable -> networkStatus.address?.defaultAddress?.value.orEmpty() - is NetworkStatus.Verified -> networkStatus.address.defaultAddress.value - else -> "" - } + networkStatuses.filter { it.network.id == network.id } + .map { networkStatus -> + when (val status = networkStatus.value) { + is NetworkStatus.NoAccount -> status.address.defaultAddress.value + is NetworkStatus.Unreachable -> status.address?.defaultAddress?.value.orEmpty() + is NetworkStatus.Verified -> status.address.defaultAddress.value + else -> "" + } + } } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt index 6b92680baf..dae1452696 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt @@ -9,6 +9,7 @@ import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyLis import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.flow.filter internal class SendRecipientWalletListConverter : Converter, PersistentList> { @@ -21,6 +22,7 @@ internal class SendRecipientWalletListConverter : private fun List.filterWallets(): PersistentList { var walletsCounter = 0 return this.filterNotNull() + .filter { it.address.isNotBlank() } .groupBy { item -> item.name } .values.map { it.mapIndexed { index, item -> diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 078ff442a9..ff49afe1c3 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -410,7 +410,7 @@ internal class SendViewModel @Inject constructor( }.onSuccess { result -> combine(*result.toTypedArray()) { it } .onEach { wallets -> - userWallets = wallets.filter { it.address.isNotBlank() }.toList() + userWallets = wallets.flatMap { it }.toList() uiState = stateFactory.onLoadedWalletsList(wallets = userWallets) } .flowOn(dispatchers.main) @@ -421,7 +421,7 @@ internal class SendViewModel @Inject constructor( } } - private suspend fun List.toAvailableWallets(): List> = + private suspend fun List.toAvailableWallets(): List>> = filterNot { it.walletId == userWalletId || it.isLocked } .mapNotNull { wallet -> val status = if (!wallet.isMultiCurrency) { @@ -435,12 +435,14 @@ internal class SendViewModel @Inject constructor( } else { getNetworkAddressesUseCase(wallet.walletId, cryptoCurrency.network) } - status?.map { address -> - AvailableWallet( - name = wallet.name, - address = address, - userWalletId = wallet.walletId, - ) + status?.map { addresses -> + addresses.map { address -> + AvailableWallet( + name = wallet.name, + address = address, + userWalletId = wallet.walletId, + ) + } } }