From ad31bdf58e8712ec61e0b4ab22573a43c226d71f Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 16:21:25 +0500 Subject: [PATCH 01/85] Updated on 2026-08-14 --- .../impl/presentation/state/confirm/SendNotificationFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0d147eb6fd..4abd80f5cd 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 @@ -247,7 +247,7 @@ internal class SendNotificationFactory( val totalAmount = feeAmount + receivedAmount val change = balance - totalAmount val isChangeLowerThanDust = change < dustValue && change != BigDecimal.ZERO - val isShowWarning = totalAmount < dustValue || isChangeLowerThanDust + val isShowWarning = receivedAmount < dustValue || isChangeLowerThanDust if (isShowWarning) { add( SendNotification.Error.MinimumAmountError(dustValue.toPlainString()), From aff8f6d270ec5003f3eec48ad48946a400591ed8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 16:21:57 +0500 Subject: [PATCH 02/85] Updated on 2026-08-14 --- .../presentation/state/amount/SendAmountSubtractConverter.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt index bf0f52dd73..fb5d2708ad 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt @@ -19,11 +19,13 @@ internal class SendAmountSubtractConverter( val feeValue = feeState.fee?.amount?.value ?: return state val amountTextField = amountState.amountTextField val amountValue = amountTextField.cryptoAmount.value ?: return state + val balance = cryptoCurrencyStatus.value.amount ?: return state val fiatRate = cryptoCurrencyStatus.value.fiatRate val cryptoDecimals = amountTextField.cryptoAmount.decimals val fiatDecimals = amountTextField.fiatAmount.decimals - val feeDiff = amountState.subtractedFee?.let { feeValue.minus(it) } ?: feeValue + val feeDiff = amountState.subtractedFee?.let { feeValue.minus(it) } + ?: amountValue.minus(balance.minus(feeValue)) val decimalCryptoValue = amountValue.minus(feeDiff) if (decimalCryptoValue < BigDecimal.ZERO) return state From ad96d7ce1a395e4bdd1ddd33e5a49e5b75753091 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 11:59:03 +0500 Subject: [PATCH 03/85] Updated on 2026-08-14 --- .../SendRecipientWalletListConverter.kt | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) 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 855e5d3a3b..6b92680baf 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 @@ -18,22 +18,25 @@ internal class SendRecipientWalletListConverter : } } - private fun List.filterWallets() = this.filterNotNull() - .groupBy { item -> item.name } - .values.map { - it.mapIndexed { index, item -> - val name = if (it.size > 1) { - "${item.name} ${index.inc()}" - } else { - item.name + private fun List.filterWallets(): PersistentList { + var walletsCounter = 0 + return this.filterNotNull() + .groupBy { item -> item.name } + .values.map { + it.mapIndexed { index, item -> + val name = if (it.size > 1) { + "${item.name} ${index.inc()}" + } else { + item.name + } + SendRecipientListContent( + id = "${WALLET_KEY_TAG}${walletsCounter++}", + title = TextReference.Str(item.address), + subtitle = TextReference.Str(name), + ) } - SendRecipientListContent( - id = "${WALLET_KEY_TAG}$index", - title = TextReference.Str(item.address), - subtitle = TextReference.Str(name), - ) } - } - .flatten() - .toPersistentList() + .flatten() + .toPersistentList() + } } \ No newline at end of file From dbedf097b91d221d2c189ad79c247d8b70003ffa Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 11:59:41 +0500 Subject: [PATCH 04/85] Updated on 2026-08-14 --- .../presentation/state/confirm/SendNotificationFactory.kt | 4 ++-- .../send/impl/presentation/viewmodel/SendViewModel.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 4abd80f5cd..441e6dd175 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 @@ -70,7 +70,7 @@ internal class SendNotificationFactory( }.toImmutableList() } - fun dismissNotificationState(clazz: Class): SendUiState { + fun dismissNotificationState(clazz: Class, isIgnored: Boolean = false): SendUiState { val state = currentStateProvider() val sendState = state.sendState ?: return state val notificationsToRemove = sendState.notifications.filterIsInstance(clazz) @@ -78,7 +78,7 @@ internal class SendNotificationFactory( updatedNotifications.removeAll(notificationsToRemove) return state.copy( sendState = sendState.copy( - ignoreAmountReduce = true, + ignoreAmountReduce = isIgnored, notifications = updatedNotifications.toImmutableList(), ), ) 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 ffad3f3633..57dc709ab1 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 @@ -795,7 +795,7 @@ internal class SendViewModel @Inject constructor( } override fun onNotificationCancel(clazz: Class) { - uiState = sendNotificationFactory.dismissNotificationState(clazz) + uiState = sendNotificationFactory.dismissNotificationState(clazz = clazz, isIgnored = true) } private fun verifyAndSendTransaction() { From 1c9c5d62f4e6bc0965fa8bf84a55124551b67bab Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 12:00:08 +0500 Subject: [PATCH 05/85] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 5 +++++ core/res/src/main/res/values/strings.xml | 9 +++++++++ .../core/ui/components/inputrow/inner/PasteButton.kt | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 5d25d41f8e..9f10054ca1 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -51,6 +51,10 @@ Заводские настройки Тип безопасности Настройки карты + Ввиду особенностей сети Cardano при транзакции токена %1$s помимо комиссии сети будет списано %2$s + Для совершения транзакции %1$s, вам необходимо внести немного %2$s (%3$s), чтобы покрыть комиссию сети и минимальное значение ADA для отправки. + Недостаточно ADA для отправки токена + Вывод всего баланса ADA невозможен при наличии средств на токенах сети Cardano. Сначала выведите средства на ваших токенах. Принять Доступ запрещен Применить @@ -97,6 +101,7 @@ OK Основная карта Кодовая фраза + Вставить Подробнее Получить Отклонить diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index a099c577d4..04985255c2 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -50,6 +50,10 @@ Reset to Factory Settings Security Mode Card Settings + Due to the peculiarities of the Cardano network, when transacting the %1$s token, in addition to the network commission, %2$s will be charged + To make a %1$s transaction, you must deposit some %2$s (%3$s) to cover the network fee and minimum ADA value + Insufficient ADA to token transfer + Withdrawal of the entire ADA balance is not possible if funds are available in Cardano network tokens. First, withdraw funds on your tokens. Accept Access denied Apply @@ -96,6 +100,7 @@ OK Primary Card Passphrase + Paste Read more Receive Reject @@ -427,6 +432,8 @@ Reason: %1$s\nCode: %2$s The transaction is not completed Amount + Base fee + Represents the part of the transaction fee that goes to the miner %1$s, %2$s Address Destination Tag @@ -474,6 +481,8 @@ Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount. Transaction limitation Optional + Priority fee + Represents the minimum gasUsed multiplier required for a transaction to be included in a block. This is the part of the transaction fee that is burnt. Please align your QR code with the square to scan it. Ensure you scan %s network address. Recent Recipient diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt index 88ed54192f..49c308e97e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt @@ -50,7 +50,7 @@ fun PasteButton(isPasteButtonVisible: Boolean, onClick: (String) -> Unit, modifi modifier = modifier, ) { Text( - text = "Paste", + text = stringResource(R.string.common_paste), style = TangemTheme.typography.button, color = TangemTheme.colors.text.primary2, modifier = Modifier From 31f3394afd9013b70f66202bf5c3e6bdade376cf Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 14:05:03 +0500 Subject: [PATCH 06/85] Updated on 2026-08-14 --- .../presentation/state/SendStateFactory.kt | 1 + .../state/confirm/SendNotificationFactory.kt | 7 ++ .../state/fee/FeeSelectorState.kt | 2 + .../presentation/state/fee/FeeStateFactory.kt | 5 + .../state/fee/SendFeeStateConverter.kt | 2 +- .../impl/presentation/ui/send/FeeBlock.kt | 93 ++++++++++++++----- .../presentation/ui/send/RecipientBlock.kt | 24 +++++ .../presentation/viewmodel/SendViewModel.kt | 4 +- 8 files changed, 113 insertions(+), 25 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 f3f304b7ff..0938d051f2 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 @@ -117,6 +117,7 @@ internal class SendStateFactory( recipientState = state.recipientState ?: recipientStateConverter.convert(SendRecipientStateConverter.Data(destinationAddress, memo)), feeState = state.feeState ?: feeStateConverter.convert(Unit), + sendState = confirmStateConverter.convert(Unit), isEditingDisabled = true, cryptoCurrencyName = cryptoCurrencyStatusProvider().currency.name, ) 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 441e6dd175..eb9fdba3a1 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 @@ -57,6 +57,7 @@ internal class SendNotificationFactory( val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO buildList { // errors + addFeeUnreachableNotification(feeState.feeSelectorState) addExceedBalanceNotification(feeAmount, amountValue) addExceedsBalanceNotification(feeState.fee) addMinimumAmountErrorNotification(feeAmount, amountValue) @@ -84,6 +85,12 @@ internal class SendNotificationFactory( ) } + private fun MutableList.addFeeUnreachableNotification(feeSelectorState: FeeSelectorState) { + if (feeSelectorState is FeeSelectorState.Error) { + add(SendNotification.Warning.NetworkFeeUnreachable(clickIntents::feeReload)) + } + } + private fun MutableList.addExceedBalanceNotification( feeAmount: BigDecimal, receivedAmount: BigDecimal, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt index 67fe86cf8e..cd1fe29939 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt @@ -15,6 +15,8 @@ internal sealed class FeeSelectorState { val customValues: ImmutableList = persistentListOf(), ) : FeeSelectorState() + data object Loading : FeeSelectorState() + data object Error : FeeSelectorState() } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index ba181f423f..5e34a54d61 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -47,6 +47,11 @@ internal class FeeStateFactory( return state.copy( amountState = state.amountState?.copy(isFeeLoading = true), feeState = feeState.copy( + feeSelectorState = if (feeState.feeSelectorState is FeeSelectorState.Content) { + feeState.feeSelectorState + } else { + FeeSelectorState.Loading + }, notifications = persistentListOf(), isPrimaryButtonEnabled = false, ), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt index d9b92b387d..f0cdd28425 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt @@ -14,7 +14,7 @@ internal class SendFeeStateConverter( override fun convert(value: Unit): SendStates.FeeState { return SendStates.FeeState( - feeSelectorState = FeeSelectorState.Error, + feeSelectorState = FeeSelectorState.Loading, fee = null, notifications = persistentListOf(), rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt index a3ee42ccbb..11423e7c73 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt @@ -1,21 +1,22 @@ package com.tangem.features.send.impl.presentation.ui.send +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState @@ -26,20 +27,12 @@ import com.tangem.features.send.impl.presentation.utils.getFiatReference @Composable internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick: () -> Unit) { - val fee = feeState.fee ?: return - val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return - val (title, icon) = when (feeSelectorState.selectedFee) { - FeeType.Slow -> R.string.common_fee_selector_option_slow to R.drawable.ic_tortoise_24 - FeeType.Market -> R.string.common_fee_selector_option_market to R.drawable.ic_bird_24 - FeeType.Fast -> R.string.common_fee_selector_option_fast to R.drawable.ic_hare_24 - FeeType.Custom -> R.string.common_fee_selector_option_custom to R.drawable.ic_edit_24 - } Column( modifier = Modifier .fillMaxWidth() .clip(TangemTheme.shapes.roundedCornersXMedium) .background(TangemTheme.colors.background.action) - .clickable(enabled = !isSuccess) { onClick() } + .clickable(enabled = !isSuccess && feeState.fee != null) { onClick() } .padding(TangemTheme.dimens.spacing12), ) { Text( @@ -47,17 +40,71 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, ) - SelectorRowItem( - titleRes = title, - iconRes = icon, - preDot = getCryptoReference(fee.amount, feeState.isFeeApproximate), - postDot = getFiatReference(fee.amount, feeState.rate, feeState.appCurrency), - ellipsizeOffset = fee.amount.currencySymbol.length, - isSelected = true, - showDivider = false, - paddingValues = PaddingValues(), + + Box( modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), - ) + ) { + val feeSelectorState = feeState.feeSelectorState + val feeAmount = feeState.fee?.amount + val (title, icon) = if (feeSelectorState is FeeSelectorState.Content) { + when (feeSelectorState.selectedFee) { + FeeType.Slow -> R.string.common_fee_selector_option_slow to R.drawable.ic_tortoise_24 + FeeType.Market -> R.string.common_fee_selector_option_market to R.drawable.ic_bird_24 + FeeType.Fast -> R.string.common_fee_selector_option_fast to R.drawable.ic_hare_24 + FeeType.Custom -> R.string.common_fee_selector_option_custom to R.drawable.ic_edit_24 + } + } else { + R.string.common_fee_selector_option_market to R.drawable.ic_bird_24 + } + SelectorRowItem( + titleRes = title, + iconRes = icon, + preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate), + postDot = feeAmount?.let { getFiatReference(it, feeState.rate, feeState.appCurrency) }, + ellipsizeOffset = feeAmount?.currencySymbol?.length, + isSelected = true, + showDivider = false, + paddingValues = PaddingValues(), + ) + FeeLoading(feeSelectorState) + FeeError(feeSelectorState) + } + } +} + +@Composable +private fun BoxScope.FeeLoading(feeSelectorState: FeeSelectorState) { + AnimatedContent( + targetState = feeSelectorState, + label = "Fee Loading State Change", + modifier = Modifier.align(Alignment.CenterEnd), + ) { + if (it == FeeSelectorState.Loading) { + RectangleShimmer( + radius = TangemTheme.dimens.radius3, + modifier = Modifier.size( + height = TangemTheme.dimens.size12, + width = TangemTheme.dimens.size90, + ), + ) + } + } +} + +@Composable +private fun BoxScope.FeeError(feeSelectorState: FeeSelectorState) { + AnimatedContent( + targetState = feeSelectorState, + label = "Fee Error State Change", + modifier = Modifier.align(Alignment.CenterEnd), + ) { + if (it == FeeSelectorState.Error) { + Text( + text = BigDecimalFormatter.EMPTY_BALANCE_SIGN, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.body2, + ) + } } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt index f276edf3c1..0801e63cd6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -40,6 +41,7 @@ internal fun RecipientBlock( .padding(TangemTheme.dimens.spacing12), ) { AddressBlock(recipientState.addressTextField) + MemoBlock(recipientState.memoTextField) } } @@ -70,6 +72,28 @@ private fun AddressBlock(address: SendTextField.RecipientAddress) { } } +@Composable +private fun MemoBlock(memo: SendTextField.RecipientMemo?) { + val showMemo = memo != null && memo.value.isNotBlank() + if (showMemo) { + HorizontalDivider( + color = TangemTheme.colors.icon.inactive, + modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12), + ) + Text( + text = memo?.label?.resolveReference().orEmpty(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.secondary, + ) + Text( + text = memo?.value.orEmpty(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), + ) + } +} + // region Preview @Preview @Composable 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 57dc709ab1..a25f112461 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 @@ -359,8 +359,9 @@ internal class SendViewModel @Inject constructor( stateRouter.showSend() } transactionId != null && amount != null && destinationAddress != null -> { + loadFee() uiState = stateFactory.getReadyState(amount, destinationAddress, memo) - stateRouter.showFee() + stateRouter.showSend() updateNotifications() } else -> { @@ -702,6 +703,7 @@ internal class SendViewModel @Inject constructor( if (result == null) { onFeeLoadFailed(isShowStatus, isToNextState) } + updateNotifications() updateFeeNotifications() }.saveIn(feeJobHolder) .invokeOnCompletion { From 94155919f7b75dbe08c2ebe9d6419df05b474649 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 18:30:45 +0100 Subject: [PATCH 07/85] Updated on 2026-08-14 --- .../moonpay/MoonPayService.kt | 2 +- .../deeplink/global/SellCurrencyDeepLink.kt | 2 + .../domain/tokens/model/CryptoCurrency.kt | 3 +- .../viewmodels/TokenDetailsViewModel.kt | 16 ----- .../deeplink/WalletDeepLinksHandler.kt | 59 +++++++++++-------- .../wallet/viewmodels/WalletViewModel.kt | 2 +- 6 files changed, 41 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt index 228273c9bd..c004c38b21 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -135,7 +135,7 @@ class MoonPayService( .appendQueryParameter("apiKey", apiKey) .appendQueryParameter("baseCurrencyCode", moonpayCurrency.currencyCode.uppercase()) .appendQueryParameter("refundWalletAddress", walletAddress) - .appendQueryParameter("redirectURL", "tangem://redirect_sell") + .appendQueryParameter("redirectURL", "tangem://redirect_sell?currency_id=${cryptoCurrency.id.value}") if (isDarkTheme) uri.appendQueryParameter("theme", "dark") diff --git a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt index 579c72689a..db28975f63 100644 --- a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt +++ b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt @@ -11,6 +11,7 @@ class SellCurrencyDeepLink(val onReceive: (data: Data) -> Unit) : DeepLink { transactionId = params["transactionId"] ?: return, baseCurrencyAmount = params["baseCurrencyAmount"] ?: return, depositWalletAddress = params["depositWalletAddress"] ?: return, + currencyId = params["currency_id"] ?: return, depositWalletAddressTag = params["depositWalletAddressTag"], ) @@ -21,6 +22,7 @@ class SellCurrencyDeepLink(val onReceive: (data: Data) -> Unit) : DeepLink { val transactionId: String, val baseCurrencyAmount: String, val depositWalletAddress: String, + val currencyId: String, val depositWalletAddressTag: String?, ) } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt index 87015b1305..db909d08f3 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt @@ -184,8 +184,9 @@ sealed class CryptoCurrency : Parcelable { } private companion object { + // should use delimiters that could be used in URL not like path or query delimiters const val PREFIX_DELIMITER = '_' - const val SUFFIX_DELIMITER = '#' + const val SUFFIX_DELIMITER = ';' const val DERIVATION_PATH_DELIMITER = 'd' } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index f4c38aca5c..02defbde3a 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -10,7 +10,6 @@ import com.tangem.blockchain.common.address.AddressType import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.global.BuyCurrencyDeepLink -import com.tangem.core.deeplink.global.SellCurrencyDeepLink import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.extensions.resourceReference @@ -165,7 +164,6 @@ internal class TokenDetailsViewModel @Inject constructor( viewModel = this, deepLinks = listOf( BuyCurrencyDeepLink(::onBuyCurrencyDeepLink), - SellCurrencyDeepLink(::onSellCurrencyDeepLink), ), ) } @@ -175,20 +173,6 @@ internal class TokenDetailsViewModel @Inject constructor( analyticsEventsHandler.send(TokenScreenAnalyticsEvent.Bought(currency.symbol)) } - private fun onSellCurrencyDeepLink(data: SellCurrencyDeepLink.Data) { - sendCurrency( - status = cryptoCurrencyStatus ?: return, - transactionInfo = data.let { - TransactionInfo( - transactionId = it.transactionId, - destinationAddress = it.depositWalletAddress, - amount = it.baseCurrencyAmount, - tag = it.depositWalletAddressTag, - ) - }, - ) - } - override fun onCreate(owner: LifecycleOwner) { analyticsEventsHandler.send( event = TokenScreenAnalyticsEvent.DetailsScreenOpened(token = cryptoCurrency.symbol), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt index 59e2022b58..3c1e89fbeb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt @@ -8,16 +8,14 @@ import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.global.BuyCurrencyDeepLink import com.tangem.core.deeplink.global.SellCurrencyDeepLink import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase -import com.tangem.domain.tokens.GetCryptoCurrencyUseCase -import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase -import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase +import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch @@ -29,23 +27,20 @@ internal class WalletDeepLinksHandler @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase, + private val getCryptoCurrencyStatusesSyncUseCase: GetCryptoCurrencyStatusesSyncUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, private val reduxStateHolder: ReduxStateHolder, ) { - private var deepLinks: List = emptyList() + private var deepLinksMap = mutableMapOf>() - fun registerForSingleCurrencyWallets(viewModel: ViewModel, userWallet: UserWallet) { - if (userWallet.isMultiCurrency) { - deepLinksRegistry.unregister(deepLinks) - } else { - if (deepLinks.isEmpty()) { - deepLinks = getDeepLinks(userWallet, viewModel.viewModelScope) - } - - deepLinksRegistry.register(deepLinks) + fun registerForWallet(viewModel: ViewModel, userWallet: UserWallet) { + val deepLinks = deepLinksMap.getOrPut(userWallet.walletId) { + getDeepLinks(userWallet, viewModel.viewModelScope) } + deepLinksRegistry.unregisterByIds(deepLinks.map { it.id }) + deepLinksRegistry.register(deepLinks) viewModel.addCloseable { deepLinksRegistry.unregister(deepLinks) @@ -60,20 +55,23 @@ internal class WalletDeepLinksHandler @Inject constructor( } }, ) - val buyCurrencyDeepLink = BuyCurrencyDeepLink( - onReceive = { - scope.launch { - onBuyCurrencyDeepLink(userWallet) - } - }, - ) - return listOf(sellCurrencyDeepLink, buyCurrencyDeepLink) + return buildList { + add(sellCurrencyDeepLink) + if (!userWallet.isMultiCurrency) { + add( + BuyCurrencyDeepLink( + onReceive = { + scope.launch { onBuyCurrencyDeepLink(userWallet) } + }, + ), + ) + } + } } private suspend fun onSellCurrencyDeepLink(userWallet: UserWallet, data: SellCurrencyDeepLink.Data) { - val cryptoCurrencyStatus = getCryptoCurrencyStatusSyncUseCase(userWallet.walletId) - .getOrNull() ?: return + val cryptoCurrencyStatus = findCryptoCurrencyStatus(userWallet, data.currencyId) ?: return val feeCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase( userWallet.walletId, cryptoCurrencyStatus, @@ -110,6 +108,19 @@ internal class WalletDeepLinksHandler @Inject constructor( analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol)) } + private suspend fun findCryptoCurrencyStatus( + userWallet: UserWallet, + currencyIdValue: String, + ): CryptoCurrencyStatus? { + return if (userWallet.isMultiCurrency) { + getCryptoCurrencyStatusesSyncUseCase(userWallet.walletId).getOrNull()?.let { currencies -> + currencies.find { currencyIdValue == it.currency.id.value } + } + } else { + getCryptoCurrencyStatusSyncUseCase(userWallet.walletId).getOrNull() + } + } + private fun sendCoin( userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 63db20714d..369ec32691 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -154,7 +154,7 @@ internal class WalletViewModel @Inject constructor( selectedWalletAnalyticsSender.send(selectedWallet) } - walletDeepLinksHandler.registerForSingleCurrencyWallets( + walletDeepLinksHandler.registerForWallet( viewModel = this, userWallet = selectedWallet, ) From bb4e442b4deec1e797a8a1daef4db2d70f6649b7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 18:27:59 +0500 Subject: [PATCH 08/85] Updated on 2026-08-14 --- .../core/ui/utils/BigDecimalFormatter.kt | 23 +++++++++++++++++++ .../state/confirm/SendNotificationFactory.kt | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt index 42309223b1..b724559114 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt @@ -40,6 +40,29 @@ object BigDecimalFormatter { } } + fun formatCryptoAmountUncapped( + cryptoAmount: BigDecimal?, + cryptoCurrency: CryptoCurrency, + locale: Locale = Locale.getDefault(), + ): String { + if (cryptoAmount == null) return EMPTY_BALANCE_SIGN + + val formatter = NumberFormat.getNumberInstance(locale).apply { + maximumFractionDigits = cryptoCurrency.decimals + minimumFractionDigits = 2 + isGroupingUsed = true + roundingMode = RoundingMode.DOWN + } + + return formatter.format(cryptoAmount).let { + if (cryptoCurrency.symbol.isEmpty()) { + it + } else { + it + "\u2009${cryptoCurrency.symbol}" + } + } + } + fun formatCryptoAmount( cryptoAmount: BigDecimal?, cryptoCurrency: CryptoCurrency, 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 eb9fdba3a1..09c9497fed 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 @@ -205,7 +205,7 @@ internal class SendNotificationFactory( if (currencyDeposit != null && currencyDeposit > diff) { add( SendNotification.Error.ExistentialDeposit( - BigDecimalFormatter.formatCryptoAmount( + BigDecimalFormatter.formatCryptoAmountUncapped( cryptoAmount = currencyDeposit, cryptoCurrency = cryptoCurrency, ), From 2fbbebf0903938122c9869ec3d4be690500286c3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 18:29:02 +0500 Subject: [PATCH 09/85] Updated on 2026-08-14 --- .../send/impl/presentation/viewmodel/SendViewModel.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 a25f112461..9ae90b3b93 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 @@ -24,6 +24,7 @@ import com.tangem.domain.tokens.* import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.transaction.error.GetFeeError @@ -407,7 +408,8 @@ internal class SendViewModel @Inject constructor( return if (!isMultiCurrency) { val status = getCryptoCurrencyStatusSyncUseCase(walletId).getOrNull() val address = status?.value?.networkAddress.takeIf { - status?.currency?.network?.id == cryptoCurrency.network.id + status?.currency?.network?.id == cryptoCurrency.network.id && + status.currency.network.derivationPath !is Network.DerivationPath.Custom } address?.let { AvailableWallet( @@ -418,7 +420,8 @@ internal class SendViewModel @Inject constructor( } else { val statuses = getCryptoCurrencyStatusesSyncUseCase(walletId).getOrNull() val walletCurrency = statuses?.firstOrNull { - it.currency.network.id == cryptoCurrency.network.id + it.currency.network.id == cryptoCurrency.network.id && + it.currency.network.derivationPath !is Network.DerivationPath.Custom } val address = walletCurrency?.value?.networkAddress address?.let { From 118b53e224ce5b27953c4d8beedd6918b86c50c0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 18:29:30 +0500 Subject: [PATCH 10/85] Updated on 2026-08-14 --- .../features/send/impl/presentation/viewmodel/SendViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9ae90b3b93..a349ecebf5 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 @@ -366,8 +366,8 @@ internal class SendViewModel @Inject constructor( updateNotifications() } else -> { - getWalletsAndRecent() uiState = stateFactory.getReadyState() + getWalletsAndRecent() stateRouter.showRecipient() updateNotifications() } From 77e0bd86c6b2e4ce9f7ef2a6dd7589d025d5b176 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 16:37:50 +0200 Subject: [PATCH 11/85] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 10 +++++----- .../tangem/feature/swap/ui/StateBuilder.kt | 20 +++++++++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 9f10054ca1..5d5c08ef1a 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -100,7 +100,7 @@ Нет адреса OK Основная карта - Кодовая фраза + Парольная фраза Вставить Подробнее Получить @@ -188,9 +188,9 @@ Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами Выберите провайдера Произошла ошибка. Код: %s - К сожалению обмен указанной пары через выбранного провайдера на данный момент не возможен. Попробуйте совершить обмен позже. (Код: %s) + К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) Выбранный провайдер не доступен для обмена. Попробуйте позже. (Код: %s) - В данный момент обмен не возможен. Попробуйте позже. (Код: %s) + В данный момент обмен невозможен. Попробуйте позже. (Код: %s) Курс обмена Обмен через %s Чтобы вернуть ваши деньги, посетите сайт провайдера @@ -293,7 +293,7 @@ Ошибка активации Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. - Кодовая фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. + Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. Добавить резервную карту Сканировать карту #%d Создать резервную копию @@ -483,7 +483,7 @@ Возможны задержки по транзакции Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. Лимит транзакции - Необязательное + Опционально Пожалуйста, совместите свой QR-код с квадратом, чтобы отсканировать его. Убедитесь, что вы сканируете адрес в сети %s. Последние Получатель diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index eaf4ddfa6b..d8edc6401c 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -646,6 +646,7 @@ internal class StateBuilder( } private fun getWarningForError(dataError: DataError, fromToken: CryptoCurrency): SwapWarning { + val providerErrorMessage = getProviderErrorMessage(dataError) return when (dataError) { is DataError.ExchangeTooSmallAmountError -> SwapWarning.GeneralError( notificationConfig = NotificationConfig( @@ -674,10 +675,16 @@ internal class StateBuilder( } else { resourceReference(R.string.warning_express_refresh_required_title) }, - subtitle = if (dataError is DataError.UnknownError) { - resourceReference(R.string.common_unknown_error) - } else { - resourceReference(R.string.express_error_code, wrappedList(dataError.code.toString())) + subtitle = when { + dataError is DataError.UnknownError -> { + resourceReference(R.string.common_unknown_error) + } + providerErrorMessage != null -> { + providerErrorMessage + } + else -> { + resourceReference(R.string.express_error_code, wrappedList(dataError.code.toString())) + } }, iconResId = R.drawable.img_attention_20, buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( @@ -972,7 +979,7 @@ internal class StateBuilder( return uiState.copy( alert = SwapWarning.GenericWarning( message = if (swapTransactionState is SwapTransactionState.ExpressError) { - getAlertErrorMessage(swapTransactionState.dataError) + getProviderErrorMessage(swapTransactionState.dataError) } else { null }, @@ -987,7 +994,7 @@ internal class StateBuilder( ) } - private fun getAlertErrorMessage(dataError: DataError): TextReference? { + private fun getProviderErrorMessage(dataError: DataError): TextReference? { return when (dataError) { is DataError.SwapsAreUnavailableNowError -> resourceReference( id = R.string.express_error_swap_unavailable, @@ -998,6 +1005,7 @@ internal class StateBuilder( formatArgs = wrappedList(dataError.code), ) is DataError.ExchangeProviderNotActiveError, + is DataError.ExchangeProviderNotFoundError, is DataError.ExchangeProviderNotAvailableError, is DataError.ExchangeProviderProviderInternalError, -> resourceReference( From bcbe14d5613d819820206d4682a9a70b95bed5d2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 20:29:52 +0500 Subject: [PATCH 12/85] Updated on 2026-08-14 --- .../features/send/impl/presentation/viewmodel/SendViewModel.kt | 1 + 1 file changed, 1 insertion(+) 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 a349ecebf5..7cde4f3f49 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 @@ -797,6 +797,7 @@ internal class SendViewModel @Inject constructor( override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) { uiState = amountStateFactory.getOnAmountValueChange(reducedAmount.parseBigDecimal(cryptoCurrency.decimals)) uiState = sendNotificationFactory.dismissNotificationState(clazz) + updateNotifications() } override fun onNotificationCancel(clazz: Class) { From 44188b11cd289a7280397ed98c5514ed50a7d86c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 20:32:06 +0500 Subject: [PATCH 13/85] Updated on 2026-08-14 --- .../state/confirm/SendNotificationFactory.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) 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 09c9497fed..2f597ec8ae 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 @@ -96,17 +96,13 @@ internal class SendNotificationFactory( receivedAmount: BigDecimal, ) { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider() val feePaidCryptoCurrencyStatus = feePaidCryptoCurrencyStatusProvider() val cryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val coinCryptoAmount = coinCryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val showNotification = if (cryptoCurrencyStatus.currency.id == feePaidCryptoCurrencyStatus?.currency?.id) { - receivedAmount > cryptoAmount || feeAmount > coinCryptoAmount - } else { - receivedAmount + feeAmount > cryptoAmount - } + val isCurrentNotFeePaidCurrency = cryptoCurrencyStatus.currency.id != feePaidCryptoCurrencyStatus?.currency?.id + if (isCurrentNotFeePaidCurrency) return + val showNotification = receivedAmount + feeAmount > cryptoAmount if (showNotification) { add(SendNotification.Error.TotalExceedsBalance) } From 7b91047ca19bc382f6e217ad5cadfc67de27bbf6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 18:44:26 +0100 Subject: [PATCH 14/85] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 56d6351be9..9f6bf1bcfd 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-600" +tangemBlockchainSdk = "release-app_5.9-602" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 7fc28913ae8b2d907af8a0c120b3b2d308941c6a Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 19 Apr 2024 16:17:21 +0500 Subject: [PATCH 15/85] Updated on 2026-08-14 --- .../state/confirm/SendNotificationFactory.kt | 13 ++++--------- .../presentation/state/fee/FeeCalculation.kt | 18 +++++++++++------- .../presentation/viewmodel/SendViewModel.kt | 3 ++- 3 files changed, 17 insertions(+), 17 deletions(-) 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 2f597ec8ae..e17313e475 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 @@ -20,6 +20,7 @@ import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType +import com.tangem.features.send.impl.presentation.state.fee.checkIfFeeTooHigh import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils.isDogecoin import com.tangem.utils.Provider @@ -31,7 +32,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import java.math.BigDecimal -import java.math.BigInteger @Suppress("LongParameterList") internal class SendNotificationFactory( @@ -277,13 +277,9 @@ internal class SendNotificationFactory( private fun MutableList.addTooHighNotification(feeSelectorState: FeeSelectorState) { if (feeSelectorState !is FeeSelectorState.Content) return - val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return - val highValue = multipleFees.priority.amount.value ?: return - val customAmount = feeSelectorState.customValues.firstOrNull() ?: return - val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) - val diff = (customValue / highValue).toBigInteger() - if (feeSelectorState.selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF) { - add(SendNotification.Warning.TooHigh(diff.toString())) + + checkIfFeeTooHigh(feeSelectorState) { diff -> + add(SendNotification.Warning.TooHigh(diff)) } } @@ -366,6 +362,5 @@ internal class SendNotificationFactory( companion object { private const val DOGECOIN_MINIMUM = "0.01" - internal val FEE_MAX_DIFF = BigInteger("5") } } \ No newline at end of file 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 57e567a586..31307124fd 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 @@ -1,10 +1,12 @@ package com.tangem.features.send.impl.presentation.state.fee import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory +import java.math.BigDecimal +import java.math.RoundingMode /** * Check if sending amount with fee is greater than balance @@ -32,14 +34,16 @@ internal fun checkIfFeeTooLow(state: SendUiState): Boolean { /** * Check if custom fee is too high */ -internal fun checkIfFeeTooHigh(state: SendUiState, onShow: (String) -> Unit): Boolean { - val feeSelectorState = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false +internal fun checkIfFeeTooHigh(feeSelectorState: FeeSelectorState.Content, onShow: (String) -> Unit): Boolean { val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return false val highValue = multipleFees.priority.amount.value ?: return false val customAmount = feeSelectorState.customValues.firstOrNull() ?: return false val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) - val diff = (customValue / highValue).toBigInteger() - val isShow = feeSelectorState.selectedFee == FeeType.Custom && diff > SendNotificationFactory.FEE_MAX_DIFF - if (isShow) onShow(diff.toString()) + val diff = customValue / highValue + val isShow = feeSelectorState.selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF + if (isShow) onShow(diff.parseBigDecimal(ZERO_DECIMALS, RoundingMode.HALF_UP)) return isShow -} \ No newline at end of file +} + +private val FEE_MAX_DIFF = BigDecimal("5") +private const val ZERO_DECIMALS = 0 \ No newline at end of file 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 7cde4f3f49..93300b23c2 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 @@ -526,8 +526,9 @@ internal class SendViewModel @Inject constructor( ) return true } + val feeSelectorState = uiState.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false return checkIfFeeTooHigh( - state = uiState, + feeSelectorState = feeSelectorState, onShow = { diff -> uiState = eventStateFactory.getFeeTooHighAlert( diff = diff, From 9ee6c1d9538e922f4d6a8f178ee7e956ad76fd65 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 19 Apr 2024 17:13:23 +0500 Subject: [PATCH 16/85] Updated on 2026-08-14 --- .../state/confirm/SendNotificationFactory.kt | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) 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 e17313e475..3ad03b434c 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 @@ -24,7 +24,6 @@ import com.tangem.features.send.impl.presentation.state.fee.checkIfFeeTooHigh import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils.isDogecoin import com.tangem.utils.Provider -import com.tangem.utils.isNullOrZero import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -108,23 +107,6 @@ internal class SendNotificationFactory( } } - private fun MutableList.addMinimumAmountErrorNotification( - feeAmount: BigDecimal, - receivedAmount: BigDecimal, - ) { - val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider() - - val totalAmount = feeAmount + receivedAmount - val balance = coinCryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - - if (isDogecoin(coinCryptoCurrencyStatus.currency.network.id.value)) { - val minimum = BigDecimal(DOGECOIN_MINIMUM) - if (receivedAmount < minimum || balance - totalAmount < minimum) { - add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM)) - } - } - } - // todo temporarily disabling notification // private suspend fun MutableList.addReserveAmountErrorNotification(recipientAddress: String) { // val userWalletId = userWalletProvider().walletId @@ -236,6 +218,20 @@ internal class SendNotificationFactory( } } + private fun MutableList.addMinimumAmountErrorNotification( + feeAmount: BigDecimal, + receivedAmount: BigDecimal, + ) { + val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider() + val minimum = BigDecimal(DOGECOIN_MINIMUM) + + val isDogecoin = isDogecoin(coinCryptoCurrencyStatus.currency.network.id.value) + val isExceedDustLimit = checkDustLimits(feeAmount, receivedAmount, minimum) + if (isDogecoin && isExceedDustLimit) { + add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM)) + } + } + private suspend fun MutableList.addDustWarningNotification( feeAmount: BigDecimal, receivedAmount: BigDecimal, @@ -244,18 +240,12 @@ internal class SendNotificationFactory( val dustValue = currencyChecksRepository.getDustValue( userWalletProvider().walletId, cryptoCurrencyStatus.currency.network, - ) - val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - if (dustValue != null && !balance.isNullOrZero() && receivedAmount < balance) { - val totalAmount = feeAmount + receivedAmount - val change = balance - totalAmount - val isChangeLowerThanDust = change < dustValue && change != BigDecimal.ZERO - val isShowWarning = receivedAmount < dustValue || isChangeLowerThanDust - if (isShowWarning) { - add( - SendNotification.Error.MinimumAmountError(dustValue.toPlainString()), - ) - } + ) ?: return + + if (checkDustLimits(feeAmount, receivedAmount, dustValue)) { + add( + SendNotification.Error.MinimumAmountError(dustValue.toPlainString()), + ) } } @@ -360,6 +350,16 @@ internal class SendNotificationFactory( return Blockchain.fromNetworkId(this.currency.network.backendId) == Blockchain.Arbitrum } + private fun checkDustLimits(feeAmount: BigDecimal, receivedAmount: BigDecimal, dustValue: BigDecimal): Boolean { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + + val totalAmount = feeAmount + receivedAmount + val change = balance - totalAmount + val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO + return receivedAmount < dustValue || isChangeLowerThanDust + } + companion object { private const val DOGECOIN_MINIMUM = "0.01" } From 4a9062821bb92c489135e28329f4c9d227b36f00 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 19 Apr 2024 16:53:39 +0300 Subject: [PATCH 17/85] Updated on 2026-08-14 --- .../segmentedbutton/SegmentedButton.kt | 14 +++++---- .../wallet2/ui/YourSeedPhraseScreen.kt | 2 +- .../impl/presentation/state/SendUiState.kt | 1 + .../state/amount/SendAmountStateConverter.kt | 29 +++++++++---------- .../previewdata/AmountStatePreviewData.kt | 1 + .../presentation/ui/amount/AmountButtons.kt | 2 ++ .../ui/amount/SendAmountContent.kt | 1 + 7 files changed, 27 insertions(+), 23 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt index 712fc8cb52..f3cbfca76d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt @@ -47,14 +47,15 @@ inline fun SegmentedButtons( selectedColor: Color = TangemTheme.colors.background.action, dividerColor: Color = TangemTheme.colors.stroke.primary, showIndication: Boolean = true, - selectedItem: T? = null, + initialSelectedItem: T? = null, + isEnabled: Boolean = true, crossinline buttonContent: @Composable (T) -> Unit, ) { if (config.isEmpty() || config.size == 1) return - var selected by remember { - val selectedIndex = if (selectedItem == null) 0 else config.indexOf(selectedItem) - mutableIntStateOf(selectedIndex) + var selectedIndex by remember { + val index = if (initialSelectedItem == null) 0 else config.indexOf(initialSelectedItem) + mutableIntStateOf(index) } Row( @@ -69,7 +70,7 @@ inline fun SegmentedButtons( val rightRadius = if (index == config.lastIndex) TangemTheme.dimens.radius26 else TangemTheme.dimens.radius0 val animateColor by animateColorAsState( - targetValue = if (index == selected) selectedColor else color, + targetValue = if (index == selectedIndex) selectedColor else color, label = "Segmented Button Selected Color Animation", animationSpec = spring(stiffness = Spring.StiffnessMedium), ) @@ -86,11 +87,12 @@ inline fun SegmentedButtons( ), ) .clickable( + enabled = isEnabled, interactionSource = remember { MutableInteractionSource() }, indication = if (showIndication) LocalIndication.current else null, ) { + selectedIndex = index onClick(config[index]) - selected = index }, ) { buttonContent.invoke(config[index]) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt index 94f072a0ac..609e2df784 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt @@ -93,7 +93,7 @@ private fun SegmentSeedBlock(state: SegmentSeedState, modifier: Modifier = Modif SegmentedButtons( modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing76), config = state.seedSegments, - selectedItem = state.selectedSeedType, + initialSelectedItem = state.selectedSeedType, onClick = { state.onSelectType(it) }, 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 586c55c978..eb0e05e900 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 @@ -48,6 +48,7 @@ internal sealed class SendStates { val walletBalance: TextReference, val tokenIconState: TokenIconState, val segmentedButtonConfig: PersistentList, + val isSegmentedButtonsEnabled: Boolean, val amountTextField: SendTextField.AmountField, val notifications: ImmutableList, val appCurrencyCode: String, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt index f06a20bf48..65c85ac2e6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt @@ -42,22 +42,19 @@ internal class SendAmountStateConverter( isFeeLoading = false, appCurrencyCode = appCurrency.code, subtractedFee = null, - segmentedButtonConfig = if (status.value.fiatRate.isNullOrZero()) { - persistentListOf() - } else { - persistentListOf( - SendAmountSegmentedButtonsConfig( - title = stringReference(status.currency.symbol), - iconState = iconStateConverter.convert(status), - isFiat = false, - ), - SendAmountSegmentedButtonsConfig( - title = stringReference(appCurrency.code), - iconUrl = appCurrency.iconSmallUrl, - isFiat = true, - ), - ) - }, + segmentedButtonConfig = persistentListOf( + SendAmountSegmentedButtonsConfig( + title = stringReference(status.currency.symbol), + iconState = iconStateConverter.convert(status), + isFiat = false, + ), + SendAmountSegmentedButtonsConfig( + title = stringReference(appCurrency.code), + iconUrl = appCurrency.iconSmallUrl, + isFiat = true, + ), + ), + isSegmentedButtonsEnabled = !status.value.fiatRate.isNullOrZero(), ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt index 91dbbde9bf..c301029957 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt @@ -49,6 +49,7 @@ internal object AmountStatePreviewData { isError = false, error = TextReference.EMPTY, ), + isSegmentedButtonsEnabled = true, ) val fiatAmountState = amountState.copy( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt index 00af1e90e7..26d41708cc 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt @@ -29,6 +29,7 @@ internal fun LazyListScope.buttons( segmentedButtonConfig: PersistentList, clickIntents: SendClickIntents, isMaxButtonEnabled: Boolean, + isSegmentedButtonsEnabled: Boolean, ) { item( key = AMOUNT_BUTTONS_KEY, @@ -48,6 +49,7 @@ internal fun LazyListScope.buttons( hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) clickIntents.onCurrencyChangeClick(it.isFiat) }, + isEnabled = isSegmentedButtonsEnabled, ) { SendAmountCurrencyButton(it) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index 0f5b6493e8..fb9d2df22c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt @@ -36,6 +36,7 @@ internal fun SendAmountContent( segmentedButtonConfig = amountState.segmentedButtonConfig, clickIntents = clickIntents, isMaxButtonEnabled = !amountState.isFeeLoading, + isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled, ) notifications(amountState.notifications) } From 19c80763c3b009a81bb2bc115b6310eae763fd27 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Apr 2024 10:57:22 +0300 Subject: [PATCH 18/85] Updated on 2026-08-14 --- .../components/currency/fiaticon/FiatIcon.kt | 17 +++++++-- .../CryptoCurrencyToIconStateConverter.kt | 37 +++++++++++++++---- .../state/amount/SendAmountStateConverter.kt | 9 ++++- .../presentation/ui/amount/AmountButtons.kt | 24 ++++++------ 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt index 3255b50af7..76d5c9d094 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt @@ -4,11 +4,16 @@ import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Dp import com.tangem.core.ui.R import com.tangem.core.ui.components.currency.DefaultCurrencyIcon +private const val GRAY_SCALE_SATURATION = 0f +private const val GRAY_SCALE_ALPHA = 0.4f + /** * Simple icon from network * @@ -20,16 +25,19 @@ import com.tangem.core.ui.components.currency.DefaultCurrencyIcon fun FiatIcon( url: String?, size: Dp, + isGrayscale: Boolean, modifier: Modifier = Modifier, @DrawableRes fallbackResId: Int = R.drawable.ic_shape_circle, ) { val iconData: Any = if (url.isNullOrBlank()) fallbackResId else url + val alpha = if (isGrayscale) GRAY_SCALE_ALPHA else 1f + val colorFilter = if (isGrayscale) GrayscaleColorFilter else null DefaultCurrencyIcon( iconData = iconData, size = size, - alpha = 1f, - colorFilter = null, + alpha = alpha, + colorFilter = colorFilter, errorIcon = { Image( painter = painterResource(id = fallbackResId), @@ -38,4 +46,7 @@ fun FiatIcon( }, modifier = modifier, ) -} \ No newline at end of file +} + +private val GrayscaleColorFilter: ColorFilter + get() = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(GRAY_SCALE_SATURATION) }) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt index 19703e82b9..48d6dabf99 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt @@ -20,6 +20,21 @@ class CryptoCurrencyToIconStateConverter : Converter getIconStateForCoin( + coin = currency, + isUnreachable = value.value.isError, + forceGrayscale = true, + ) + is CryptoCurrency.Token -> getIconStateForToken( + token = currency, + isErrorStatus = value.value.isError, + forceGrayscale = true, + ) + } + } + fun convert(currency: CryptoCurrency): TokenIconState { return when (currency) { is CryptoCurrency.Coin -> getIconStateForCoin(currency, isUnreachable = false) @@ -27,18 +42,26 @@ class CryptoCurrencyToIconStateConverter : Converter Date: Mon, 22 Apr 2024 14:19:13 +0300 Subject: [PATCH 19/85] Updated on 2026-08-14 --- .../converters/AnalyticsErrorConverter.kt | 2 +- .../tap/di/domain/TransactionDomainModule.kt | 12 ++++-- .../redux/middlewares/RequestFeeMiddleware.kt | 2 +- .../send/redux/middlewares/SendMiddleware.kt | 2 +- domain/demo/build.gradle.kts | 1 + .../domain}/demo/DemoTransactionSender.kt | 2 +- .../transaction/error/SendTransactionError.kt | 4 +- .../transaction/usecase/GetFeeUseCase.kt | 37 ++++++++++++++---- .../usecase/SendTransactionUseCase.kt | 38 +++++++++++++++++-- .../presentation/viewmodel/SendViewModel.kt | 2 +- .../swap/domain/di/SwapDomainModule.kt | 5 ++- 11 files changed, 82 insertions(+), 25 deletions(-) rename {app/src/main/java/com/tangem/tap/features => domain/demo/src/main/java/com/tangem/domain}/demo/DemoTransactionSender.kt (98%) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt index b8e9b2e458..4f32eee854 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt @@ -2,8 +2,8 @@ package com.tangem.tap.common.analytics.converters import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.common.core.TangemSdkError +import com.tangem.domain.demo.DemoTransactionSender import com.tangem.tap.common.analytics.events.AnalyticsParam -import com.tangem.tap.features.demo.DemoTransactionSender import com.tangem.utils.converter.Converter /** diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index 5fc760156f..bf4f00059e 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -1,7 +1,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.demo.IsDemoCardUseCase +import com.tangem.domain.demo.DemoConfig import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.transaction.usecase.CreateTransactionUseCase @@ -22,20 +22,24 @@ internal object TransactionDomainModule { @Provides @ViewModelScoped fun provideGetFeeUseCase(walletManagersFacade: WalletManagersFacade): GetFeeUseCase { - return GetFeeUseCase(walletManagersFacade) + return GetFeeUseCase( + walletManagersFacade = walletManagersFacade, + demoConfig = DemoConfig(), + ) } @Provides @ViewModelScoped fun provideSendTransactionUseCase( - isDemoCardUseCase: IsDemoCardUseCase, cardSdkConfigRepository: CardSdkConfigRepository, transactionRepository: TransactionRepository, + walletManagersFacade: WalletManagersFacade, ): SendTransactionUseCase { return SendTransactionUseCase( - isDemoCardUseCase = isDemoCardUseCase, + demoConfig = DemoConfig(), cardSdkConfigRepository = cardSdkConfigRepository, transactionRepository = transactionRepository, + walletManagersFacade = walletManagersFacade, ) } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt index 2cb96aa486..cfdcf74a19 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt @@ -7,7 +7,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.common.extensions.isZero import com.tangem.tap.common.redux.AppState -import com.tangem.tap.features.demo.DemoTransactionSender +import com.tangem.domain.demo.DemoTransactionSender import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.send.redux.AmountActionUi import com.tangem.tap.features.send.redux.FeeAction diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index dd10c08389..e85db98c7b 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -31,7 +31,7 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TangemSigner import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage -import com.tangem.tap.features.demo.DemoTransactionSender +import com.tangem.domain.demo.DemoTransactionSender import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.FeeAction.RequestFee diff --git a/domain/demo/build.gradle.kts b/domain/demo/build.gradle.kts index f0c56e2a73..a9e6de5249 100644 --- a/domain/demo/build.gradle.kts +++ b/domain/demo/build.gradle.kts @@ -10,4 +10,5 @@ android { dependencies { implementation(deps.tangem.blockchain) + implementation(deps.tangem.card.core) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoTransactionSender.kt b/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt similarity index 98% rename from app/src/main/java/com/tangem/tap/features/demo/DemoTransactionSender.kt rename to domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt index faecf5aac1..e98373f775 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoTransactionSender.kt +++ b/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.features.demo +package com.tangem.domain.demo import com.tangem.blockchain.common.* import com.tangem.blockchain.common.transaction.Fee diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt index 22b7991290..3f5f7485d4 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt @@ -4,7 +4,7 @@ import com.tangem.core.ui.extensions.TextReference sealed class SendTransactionError { - object DemoCardError : SendTransactionError() + data object DemoCardError : SendTransactionError() data class DataError(val message: String?) : SendTransactionError() @@ -12,7 +12,7 @@ sealed class SendTransactionError { data class BlockchainSdkError(val code: Int, val message: String?) : SendTransactionError() - object UserCancelledError : SendTransactionError() + data object UserCancelledError : SendTransactionError() data class CreateAccountUnderfunded(val amount: String) : SendTransactionError() diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt index ae697ceb08..2f113a02a0 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt @@ -6,10 +6,12 @@ import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Token import com.tangem.blockchain.extensions.Result +import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.demo.DemoTransactionSender import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWallet import java.math.BigDecimal /** @@ -17,23 +19,31 @@ import java.math.BigDecimal */ class GetFeeUseCase( private val walletManagersFacade: WalletManagersFacade, + private val demoConfig: DemoConfig, ) { suspend operator fun invoke( amount: BigDecimal, destination: String, - userWalletId: UserWalletId, + userWallet: UserWallet, cryptoCurrency: CryptoCurrency, ) = either { catch( block = { - val result = requireNotNull( - walletManagersFacade.getFee( - amount = convertCryptoCurrencyToAmount(cryptoCurrency, amount), + val amountData = convertCryptoCurrencyToAmount(cryptoCurrency, amount) + + val result = if (demoConfig.isDemoCardId(userWallet.scanResponse.card.cardId)) { + demoTransactionSender(userWallet, cryptoCurrency).getFee( + amount = amountData, destination = destination, - userWalletId = userWalletId, + ) + } else { + walletManagersFacade.getFee( + amount = amountData, + destination = destination, + userWalletId = userWallet.walletId, network = cryptoCurrency.network, - ), - ) { "Fee is null" } + ) ?: error("Fee is null") + } val maybeFee = when (result) { is Result.Success -> result.data @@ -47,6 +57,17 @@ class GetFeeUseCase( ) } + private suspend fun demoTransactionSender( + userWallet: UserWallet, + cryptoCurrency: CryptoCurrency, + ): DemoTransactionSender { + return DemoTransactionSender( + walletManagersFacade + .getOrCreateWalletManager(userWallet.walletId, cryptoCurrency.network) + ?: error("WalletManager is null"), + ) + } + private fun convertCryptoCurrencyToAmount(cryptoCurrency: CryptoCurrency, amount: BigDecimal) = Amount( currencySymbol = cryptoCurrency.symbol, value = amount, diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt index d2c9cbd085..379d90d4be 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt @@ -5,6 +5,7 @@ import arrow.core.left import arrow.core.right import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.extensions.SimpleResult import com.tangem.blockchain.network.ResultChecker import com.tangem.common.core.TangemSdkError @@ -12,20 +13,23 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.common.TapWorkarounds.isStart2Coin -import com.tangem.domain.demo.IsDemoCardUseCase +import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.demo.DemoTransactionSender import com.tangem.domain.tokens.model.Network import com.tangem.domain.transaction.R import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.domain.transaction.error.SendTransactionError.Companion.USER_CANCELLED_ERROR_CODE +import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWallet import com.tangem.sdk.extensions.localizedDescriptionRes import com.tangem.utils.toFormattedString class SendTransactionUseCase( - private val isDemoCardUseCase: IsDemoCardUseCase, + private val demoConfig: DemoConfig, private val cardSdkConfigRepository: CardSdkConfigRepository, private val transactionRepository: TransactionRepository, + private val walletManagersFacade: WalletManagersFacade, ) { suspend operator fun invoke( txData: TransactionData, @@ -39,8 +43,13 @@ class SendTransactionUseCase( cardSdkConfigRepository.setLinkedTerminal(false) } val sendResult = try { - if (isDemoCardUseCase(cardId = userWallet.cardId)) { - SendTransactionError.DemoCardError.left() + if (demoConfig.isDemoCardId(cardId = userWallet.cardId)) { + sendDemo( + userWallet = userWallet, + network = network, + transactionData = txData, + signer = signer, + ) } else { transactionRepository.sendTransaction( txData = txData, @@ -66,6 +75,27 @@ class SendTransactionUseCase( ) } + private suspend fun sendDemo( + userWallet: UserWallet, + network: Network, + transactionData: TransactionData, + signer: TransactionSigner, + ): Either { + val demoTransactionSender = DemoTransactionSender( + walletManagersFacade + .getOrCreateWalletManager(userWallet.walletId, network) + ?: error("WalletManager is null"), + ) + + val result = demoTransactionSender.send(transactionData = transactionData, signer = signer) + + return if (result is SimpleResult.Failure && result.error.customMessage.contains(DemoTransactionSender.ID)) { + SendTransactionError.DemoCardError.left() + } else { + result.right() + } + } + private fun handleError(result: SimpleResult.Failure): SendTransactionError { if (ResultChecker.isNetworkError(result)) { return SendTransactionError.NetworkError( 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 93300b23c2..77708a0eda 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 @@ -741,7 +741,7 @@ internal class SendViewModel @Inject constructor( return getFeeUseCase.invoke( amount = amount, destination = recipientState.addressTextField.value, - userWalletId = userWalletId, + userWallet = userWallet, cryptoCurrency = cryptoCurrency, ) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index 196d529e7b..afb2f253a4 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -126,14 +126,15 @@ class SwapDomainModule { @Provides @Singleton fun provideSendTransactionUseCase( - @SwapScope isDemoCardUseCase: IsDemoCardUseCase, cardSdkConfigRepository: CardSdkConfigRepository, transactionRepository: TransactionRepository, + walletManagersFacade: WalletManagersFacade, ): SendTransactionUseCase { return SendTransactionUseCase( - isDemoCardUseCase = isDemoCardUseCase, + demoConfig = DemoConfig(), cardSdkConfigRepository = cardSdkConfigRepository, transactionRepository = transactionRepository, + walletManagersFacade = walletManagersFacade, ) } From 4c0e92ffc6a80c035a901311cfe64f6a5db53547 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Apr 2024 18:24:23 +0300 Subject: [PATCH 20/85] Updated on 2026-08-14 --- .../ui/components/transactions/TransactionDoneTitle.kt | 8 ++++++-- .../src/main/java/com/tangem/core/ui/utils/DateUtils.kt | 2 +- .../state/recipient/SendRecipientHistoryListConverter.kt | 4 ++-- .../factory/TokenDetailsSwapTransactionsStateConverter.kt | 4 ++-- .../txhistory/TokenDetailsTxHistoryItemFlowConverter.kt | 6 +++--- .../transformers/converter/TxHistoryItemFlowConverter.kt | 6 +++--- 6 files changed, 17 insertions(+), 13 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt index 2adcbdc369..6f23701fea 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.utils.toDateFormat +import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.core.ui.utils.toTimeFormat /** @@ -47,7 +47,11 @@ fun TransactionDoneTitle(@StringRes titleRes: Int, date: Long, modifier: Modifie .padding(top = TangemTheme.dimens.spacing16), ) Text( - text = stringResource(id = R.string.send_date_format, date.toDateFormat(), date.toTimeFormat()), + text = stringResource( + id = R.string.send_date_format, + date.toTimeFormat(DateTimeFormatters.dateFormatter), + date.toTimeFormat(), + ), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, modifier = Modifier diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt index 8d596fc41f..bc2560f6e9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt @@ -11,7 +11,7 @@ import org.joda.time.format.DateTimeFormatter * If [this] timestamp is today or yesterday, returns relative date, * otherwise returns formatting date. */ -fun Long.toDateFormat(formatter: DateTimeFormatter = DateTimeFormatters.dateFormatter): String { +fun Long.toDateFormatWithTodayYesterday(formatter: DateTimeFormatter = DateTimeFormatters.dateFormatter): String { val localDate = DateTime(this, DateTimeZone.getDefault()) return if (localDate.isToday() || localDate.isYesterday()) { DateUtils.getRelativeTimeSpanString( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt index f6ccb6ba98..3b555a7e21 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt @@ -4,7 +4,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.utils.DateTimeFormatters -import com.tangem.core.ui.utils.toDateFormat +import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -81,7 +81,7 @@ internal class SendRecipientHistoryListConverter( } private fun TxHistoryItem.extractTimestamp(): TextReference { - val date = timestampInMillis.toDateFormat( + val date = timestampInMillis.toDateFormatWithTodayYesterday( formatter = DateTimeFormatters.dateDDMMYYYY, ) val time = timestampInMillis.toTimeFormat() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt index d5445e3d1a..55e6cba9b0 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt @@ -4,7 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.utils.BigDecimalFormatter -import com.tangem.core.ui.utils.toDateFormat +import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrency @@ -71,7 +71,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( txUrl = transaction.status?.txExternalUrl, txExternalId = transaction.status?.txExternalId, timestamp = TextReference.Str( - "${timestamp.toDateFormat()}, ${timestamp.toTimeFormat()}", + "${timestamp.toDateFormatWithTodayYesterday()}, ${timestamp.toTimeFormat()}", ), fiatSymbol = appCurrency.symbol, statuses = getStatuses(transaction.status?.status), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt index 9fb6df70b1..6a8bc163c7 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt @@ -4,7 +4,7 @@ import androidx.paging.* import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState -import com.tangem.core.ui.utils.toDateFormat +import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents @@ -68,7 +68,7 @@ internal class TokenDetailsTxHistoryItemFlowConverter( // Use raw timestamp to get date // If [afterDate] is the first transaction in the flow, add the group title - val afterDate = after.getTimestamp()?.toDateFormat() ?: return@insertSeparators null + val afterDate = after.getTimestamp()?.toDateFormatWithTodayYesterday() ?: return@insertSeparators null if (before is TxHistoryItemState.Title) { return@insertSeparators TxHistoryItemState.GroupTitle( title = afterDate, @@ -80,7 +80,7 @@ internal class TokenDetailsTxHistoryItemFlowConverter( * If [beforeDate] is not equals to [afterDate], then [afterDate] is first transaction in * the new group */ - val beforeDate = before.getTimestamp()?.toDateFormat() ?: return@insertSeparators null + val beforeDate = before.getTimestamp()?.toDateFormatWithTodayYesterday() ?: return@insertSeparators null return@insertSeparators if (beforeDate != afterDate) { TxHistoryItemState.GroupTitle( title = afterDate, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt index 62c49d5586..b24e902bfa 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt @@ -4,7 +4,7 @@ import androidx.paging.* import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState -import com.tangem.core.ui.utils.toDateFormat +import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.utils.converter.Converter import kotlinx.coroutines.CoroutineScope @@ -50,7 +50,7 @@ internal class TxHistoryItemFlowConverter( // Use raw timestamp to get date // If [afterDate] is the first transaction in the flow, add the group title - val afterDate = after.getTimestamp()?.toDateFormat() ?: return@insertSeparators null + val afterDate = after.getTimestamp()?.toDateFormatWithTodayYesterday() ?: return@insertSeparators null if (before is TxHistoryItemState.Title) { return@insertSeparators TxHistoryItemState.GroupTitle(afterDate, itemKey = UUID.randomUUID().toString()) } @@ -59,7 +59,7 @@ internal class TxHistoryItemFlowConverter( * If [beforeDate] is not equals to [afterDate], then [afterDate] is first transaction in * the new group */ - val beforeDate = before.getTimestamp()?.toDateFormat() ?: return@insertSeparators null + val beforeDate = before.getTimestamp()?.toDateFormatWithTodayYesterday() ?: return@insertSeparators null return@insertSeparators if (beforeDate != afterDate) { TxHistoryItemState.GroupTitle(afterDate, itemKey = UUID.randomUUID().toString()) } else { From ee3d9ff43b3e37883c5d440996c70be33652a5ac Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Apr 2024 18:24:54 +0300 Subject: [PATCH 21/85] Updated on 2026-08-14 --- .../CryptoCurrencyToIconStateConverter.kt | 14 ++++++++++---- .../state/amount/SendAmountStateConverter.kt | 10 +++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt index 48d6dabf99..fbcc6e3caa 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt @@ -20,17 +20,22 @@ class CryptoCurrencyToIconStateConverter : Converter getIconStateForCoin( coin = currency, isUnreachable = value.value.isError, - forceGrayscale = true, + forceGrayscale = forceGrayscale, ) is CryptoCurrency.Token -> getIconStateForToken( token = currency, isErrorStatus = value.value.isError, - forceGrayscale = true, + forceGrayscale = forceGrayscale, + showCustomBadge = showCustomTokenBadge, ) } } @@ -58,6 +63,7 @@ class CryptoCurrencyToIconStateConverter : Converter Date: Mon, 22 Apr 2024 18:26:20 +0300 Subject: [PATCH 22/85] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 9f6bf1bcfd..eec9a20c7b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-602" +tangemBlockchainSdk = "release-app_5.9-605" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 194b71e38b80bf65924924b889b037a5a5b76d86 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Apr 2024 16:41:37 +0100 Subject: [PATCH 23/85] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 84088baede..7da3c6d73d 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -9,7 +9,7 @@ }, { "name": "REDESIGNED_SEND_SCREEN_ENABLED", - "version": "5.9.0" + "version": "5.9.1" }, { "name": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED", From f6a3716a86cc2cec3d8e8ed0dc0729e96c77910b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Apr 2024 20:47:44 +0500 Subject: [PATCH 24/85] Updated on 2026-08-14 --- .../tangem/features/send/impl/presentation/state/SendUiState.kt | 1 - .../impl/presentation/state/amount/SendAmountStateConverter.kt | 2 -- .../presentation/state/previewdata/AmountStatePreviewData.kt | 2 -- .../send/impl/presentation/ui/amount/SendAmountContent.kt | 1 - 4 files changed, 6 deletions(-) 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 eb0e05e900..e97d90f0b3 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 @@ -50,7 +50,6 @@ internal sealed class SendStates { val segmentedButtonConfig: PersistentList, val isSegmentedButtonsEnabled: Boolean, val amountTextField: SendTextField.AmountField, - val notifications: ImmutableList, val appCurrencyCode: String, val isFeeLoading: Boolean, val subtractedFee: BigDecimal?, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt index ba3ab64dcf..1c57154019 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt @@ -39,8 +39,6 @@ internal class SendAmountStateConverter( tokenIconState = iconStateConverter.convert(status), amountTextField = sendAmountFieldConverter.convert(value), isPrimaryButtonEnabled = false, - notifications = persistentListOf(), - isFeeLoading = false, appCurrencyCode = appCurrency.code, subtractedFee = null, segmentedButtonConfig = persistentListOf( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt index c301029957..ff8d574fde 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt @@ -22,8 +22,6 @@ internal object AmountStatePreviewData { walletBalance = stringReference("123.123"), tokenIconState = TokenIconState.Loading, segmentedButtonConfig = persistentListOf(), - notifications = persistentListOf(), - isFeeLoading = false, appCurrencyCode = "usd", subtractedFee = null, amountTextField = SendTextField.AmountField( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index fb9d2df22c..a1730013c1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt @@ -38,7 +38,6 @@ internal fun SendAmountContent( isMaxButtonEnabled = !amountState.isFeeLoading, isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled, ) - notifications(amountState.notifications) } } From 5a6ea961c710d1528406545495020a425b5e0fd1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Apr 2024 20:48:13 +0500 Subject: [PATCH 25/85] Updated on 2026-08-14 --- .../impl/presentation/state/SendAlertState.kt | 10 ---------- .../presentation/state/SendEventStateFactory.kt | 17 +---------------- .../state/amount/AmountStateFactory.kt | 2 -- .../state/previewdata/SendClickIntentsStub.kt | 2 +- .../impl/presentation/ui/amount/AmountField.kt | 1 - .../presentation/ui/amount/SendAmountContent.kt | 1 - .../presentation/viewmodel/SendClickIntents.kt | 2 +- 7 files changed, 3 insertions(+), 32 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt index 6de841373c..fc7ae7daf1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt @@ -68,16 +68,6 @@ internal sealed class SendAlertState { override val confirmButtonText: TextReference = resourceReference(R.string.common_continue) } - data class FeeCoverage( - override val onConfirmClick: (() -> Unit), - ) : SendAlertState() { - override val title: TextReference? = null - override val message: TextReference = - resourceReference(id = R.string.send_alert_fee_coverage_title) - override val confirmButtonText: TextReference = - resourceReference(id = R.string.send_alert_fee_coverage_subract_text) - } - data class ReserveAmount(val amount: String) : SendAlertState() { override val title: TextReference = resourceReference(id = R.string.send_notification_invalid_reserve_amount_title, wrappedList(amount)) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt index d99b6bd0ff..616fad69f8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt @@ -48,19 +48,6 @@ internal class SendEventStateFactory( ) } - fun getFeeCoverageAlert(onConsume: () -> Unit): SendUiState { - return currentStateProvider().copy( - event = triggeredEvent( - data = SendEvent.ShowAlert( - SendAlertState.FeeCoverage( - onConfirmClick = clickIntents::onSubtractSelect, - ), - ), - onConsume = onConsume, - ), - ) - } - fun getFeeUpdatedAlert(fee: TransactionFee, onConsume: () -> Unit, onFeeNotIncreased: () -> Unit): SendUiState { val state = currentStateProvider() val feeSelector = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return state @@ -139,9 +126,7 @@ internal class SendEventStateFactory( return state.copy( event = triggeredEvent( data = SendEvent.ShowAlert( - SendAlertState.FeeUnreachableError( - onConfirmClick = { clickIntents.feeReload(true) }, - ), + SendAlertState.FeeUnreachableError(onConfirmClick = clickIntents::feeReload), ), onConsume = onConsume, ), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt index 4577927cc1..204c2f9e08 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt @@ -1,12 +1,10 @@ package com.tangem.features.send.impl.presentation.state.amount import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.state.SendNotification import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldMaxAmountConverter import com.tangem.utils.Provider -import kotlinx.collections.immutable.ImmutableList /** * Factory to produce amount state for [SendUiState] diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 92be46efa2..67a0321945 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -34,7 +34,7 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onRecipientMemoValueChange(value: String) {} - override fun feeReload(isToNextState: Boolean) {} + override fun feeReload() {} override fun onFeeSelectorClick(feeType: FeeType) {} diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt index bb5b97aa51..84dd5252c8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt @@ -22,7 +22,6 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.rememberDecimalFormat import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import kotlinx.coroutines.delay import kotlinx.coroutines.job @Composable diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index a1730013c1..e85226d60d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt @@ -12,7 +12,6 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.previewdata.AmountStatePreviewData import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub -import com.tangem.features.send.impl.presentation.ui.common.notifications import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents @Composable diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index 9db5547b20..0b2dfdf182 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -39,7 +39,7 @@ internal interface SendClickIntents { // endregion // region Fee - fun feeReload(isToNextState: Boolean = false) + fun feeReload() fun onFeeSelectorClick(feeType: FeeType) From 1d8f33cd9c2fc3e30e70e37fd666f7dad4f09004 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Apr 2024 20:48:24 +0500 Subject: [PATCH 26/85] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 17 ++++++- core/res/src/main/res/values/strings.xml | 17 ++++++- .../presentation/state/SendStateFactory.kt | 15 ------ .../impl/presentation/state/SendUiState.kt | 1 - .../state/amount/SendAmountStateConverter.kt | 1 - .../amount/SendAmountSubtractConverter.kt | 50 ------------------- .../confirm/SendConfirmStateConverter.kt | 1 - .../fields/SendAmountFieldChangeConverter.kt | 1 - .../SendAmountFieldMaxAmountConverter.kt | 1 - .../previewdata/AmountStatePreviewData.kt | 1 - .../state/previewdata/SendClickIntentsStub.kt | 2 - .../viewmodel/SendClickIntents.kt | 2 - .../presentation/viewmodel/SendViewModel.kt | 6 --- 13 files changed, 30 insertions(+), 85 deletions(-) delete mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 5d5c08ef1a..1ac0523e66 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -4,12 +4,16 @@ Валюты Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. Обратиться в поддержку + Попробовать снова Эта функция недоступна в демонстрационном режиме Причина: %s Не могу отправить транзакцию Выбранный кошелёк не поддерживает сеть %1$s Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. + Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки. + Ваши предложения отправлены + Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, или запросите поддержку. У вас возникли трудности со сканированием карты? Эта карта не предназначена для работы с этим приложением Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. @@ -24,6 +28,8 @@ Тёмная Светлая Как в системе + При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства + Системная Тема Настройки приложения Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" @@ -75,6 +81,7 @@ Создать Удалить Отключено + Отключить Готово Включить Включено @@ -107,6 +114,7 @@ Отклонить Перезагрузить Переименовать + Повторить Сохранить изменения Искать Поиск токенов @@ -130,6 +138,7 @@ Я понял Произошла ошибка. Пожалуйста, попробуйте снова. Недоступно + Предупреждение Да Адрес контракта скопирован! Доступные сети @@ -176,6 +185,7 @@ Скрывать балансы жестом переворота Эмитент Подписано + Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно. Подробности Проверьте подключение с интернетом или переключитесь на другую сеть Условия использования @@ -430,8 +440,6 @@ Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. Приготовьте свою карту Уже содержится в введенном адресе - Вычесть - Недостаточно средств для покрытия комиссии сети. Вычесть недостающую сумму для покрытия комиссии из отправляемой суммы? Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? Причина: %1$s\nКод: %2$s @@ -547,6 +555,7 @@ У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. Обмен %s не доступен. Но мы работаем над его добавлением. В данный момент продажа монеты %s недоступна. Но мы работаем над её добавлением. + Выберите адрес Сгенерировать XPUB Скрыть Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. @@ -656,6 +665,10 @@ Пожалуйста, измените сумму для обмена Возможно, данная карта - образец или подделка Ошибка проверки подлинности + Ассоциировать + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%.4f %s + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять + Ассоциируете свой токен На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. Малое количество подписей Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 04985255c2..257e2e4adc 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -4,12 +4,16 @@ Manage tokens Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. Request support + Try again This feature is disabled in Demo mode Reason: %s Can\'t send a transaction The selected does not support the %1$s network To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. Tokens in %1$s network are not supported by this card due to firmware limitation. + Thank you for your feedback. We will respond as soon as possible + Your suggestions were sent + Please try to tap the card exactly as shown in the animation or request support. Are you having difficulty scanning your card? This card is not designed to work with this app Default Fee @@ -25,6 +29,8 @@ Dark Light System default + If system is selected, the app will auto-adjust based on your device\'s system settings + System Theme App Settings To hide or show your balances, simply flip your device screen down, or switch it off in Settings @@ -74,6 +80,7 @@ Create Delete Disabled + Disconnect Done Enable Enabled @@ -106,6 +113,7 @@ Reject Reload Rename + Retry Save changes Search Search tokens @@ -129,6 +137,7 @@ I understand There was an error. Please try again. Unreachable + Warning Yes Contract address copied! Available networks @@ -175,6 +184,7 @@ Flip-to-Hide Balances Issuer Signed + If you forget the code you will lose access to your funds. Code recovery is not possible. Details Check your internet connection or switch to a different network Terms of Service @@ -425,8 +435,6 @@ Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Get your card ready! Already included in the entered address - Subtract - Not enough funds to cover the network fee. Do you want to subtract the amount required to cover the fee? The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? Reason: %1$s\nCode: %2$s @@ -548,6 +556,7 @@ You do not have funds to send. Top up your account to be able to send funds from it. %s swap is not available. But we are working on adding it. Sell of the %s coin is currently unavailable. But we are working on adding it. + Choose address Generate XPUB Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. @@ -657,6 +666,10 @@ Please change the amount to swap This card might be a production sample or counterfeit Authenticity check failed + Associate + This token must be associated with your Hedera account before you can receive it. Association fee ~%.4f %s + This token must be associated with your Hedera account before you can receive it + Associate your token Only %s signatures are left on this card. You must withdraw all of your funds. Low signature count Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. 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 0938d051f2..b31e9559be 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 @@ -14,7 +14,6 @@ import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter -import com.tangem.features.send.impl.presentation.state.amount.SendAmountSubtractConverter import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter @@ -48,12 +47,6 @@ internal class SendStateFactory( appCurrencyProvider = appCurrencyProvider, ) } - private val amountSubtractConverter by lazy(LazyThreadSafetyMode.NONE) { - SendAmountSubtractConverter( - currentStateProvider = currentStateProvider, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - ) - } private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) { SendAmountStateConverter( appCurrencyProvider = appCurrencyProvider, @@ -265,14 +258,6 @@ internal class SendStateFactory( //endregion //region send - fun onSubtractSelect(isAmountSubtractAvailable: Boolean): SendUiState { - val state = currentStateProvider() - - if (!isAmountSubtractAvailable) return state - - return amountSubtractConverter.convert(Unit) - } - fun getSendingStateUpdate(isSending: Boolean): SendUiState { val state = currentStateProvider() return state.copy( 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 e97d90f0b3..b3772f48eb 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 @@ -89,7 +89,6 @@ internal sealed class SendStates { override val isPrimaryButtonEnabled: Boolean = true, val isSending: Boolean, val isSuccess: Boolean, - val isSubtract: Boolean, val transactionDate: Long, val txUrl: String, val ignoreAmountReduce: Boolean, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt index 1c57154019..7e3199d1ff 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt @@ -40,7 +40,6 @@ internal class SendAmountStateConverter( amountTextField = sendAmountFieldConverter.convert(value), isPrimaryButtonEnabled = false, appCurrencyCode = appCurrency.code, - subtractedFee = null, segmentedButtonConfig = persistentListOf( SendAmountSegmentedButtonsConfig( title = stringReference(status.currency.symbol), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt deleted file mode 100644 index fb5d2708ad..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.amount - -import com.tangem.core.ui.utils.parseBigDecimal -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import java.math.BigDecimal - -internal class SendAmountSubtractConverter( - private val currentStateProvider: Provider, - private val cryptoCurrencyStatusProvider: Provider, -) : Converter { - override fun convert(value: Unit): SendUiState { - val state = currentStateProvider() - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val amountState = state.amountState ?: return state - val feeState = state.feeState ?: return state - val feeValue = feeState.fee?.amount?.value ?: return state - val amountTextField = amountState.amountTextField - val amountValue = amountTextField.cryptoAmount.value ?: return state - val balance = cryptoCurrencyStatus.value.amount ?: return state - val fiatRate = cryptoCurrencyStatus.value.fiatRate - val cryptoDecimals = amountTextField.cryptoAmount.decimals - val fiatDecimals = amountTextField.fiatAmount.decimals - - val feeDiff = amountState.subtractedFee?.let { feeValue.minus(it) } - ?: amountValue.minus(balance.minus(feeValue)) - val decimalCryptoValue = amountValue.minus(feeDiff) - - if (decimalCryptoValue < BigDecimal.ZERO) return state - - val decimalFiatValue = decimalCryptoValue.multiply(fiatRate) - val cryptoValue = decimalCryptoValue.parseBigDecimal(cryptoDecimals) - val fiatValue = decimalFiatValue.parseBigDecimal(fiatDecimals) - - return state.copy( - sendState = state.sendState?.copy(isSubtract = true), - amountState = amountState.copy( - subtractedFee = feeValue, - amountTextField = amountTextField.copy( - value = cryptoValue, - fiatValue = fiatValue, - cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), - fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), - ), - ), - ) - } -} \ No newline at end of file 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 d5662a10b7..e202bf5e25 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 @@ -13,7 +13,6 @@ internal class SendConfirmStateConverter( isPrimaryButtonEnabled = true, isSending = false, isSuccess = false, - isSubtract = false, transactionDate = 0L, txUrl = "", ignoreAmountReduce = false, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index 8d7daa1665..a1fc8fbe25 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -38,7 +38,6 @@ internal class SendAmountFieldChangeConverter( return state.copy( amountState = amountState.copy( isPrimaryButtonEnabled = !isExceedBalance && !isZero, - subtractedFee = null, amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt index 715d3ad4f8..b29459e757 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt @@ -34,7 +34,6 @@ internal class SendAmountFieldMaxAmountConverter( return state.copy( amountState = amountState.copy( isPrimaryButtonEnabled = true, - subtractedFee = null, amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt index ff8d574fde..cbe162c0c5 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt @@ -23,7 +23,6 @@ internal object AmountStatePreviewData { tokenIconState = TokenIconState.Loading, segmentedButtonConfig = persistentListOf(), appCurrencyCode = "usd", - subtractedFee = null, amountTextField = SendTextField.AmountField( value = "123.123123123123123123", onValueChange = {}, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 67a0321945..10a3142e61 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -40,8 +40,6 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onCustomFeeValueChange(index: Int, value: String) {} - override fun onSubtractSelect() {} - override fun onReadMoreClick() {} override fun onSendClick() {} diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index 0b2dfdf182..74e5d81128 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -45,8 +45,6 @@ internal interface SendClickIntents { fun onCustomFeeValueChange(index: Int, value: String) - fun onSubtractSelect() - fun onReadMoreClick() // endregion 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 77708a0eda..71bcfa8718 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 @@ -667,12 +667,6 @@ internal class SendViewModel @Inject constructor( updateFeeNotifications() } - override fun onSubtractSelect() { - uiState = stateFactory.onSubtractSelect(isAmountSubtractAvailable) - stateRouter.showSend() - analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount(true)) - } - override fun onReadMoreClick() { val locale = if (Locale.getDefault().language == RU_LOCALE) RU_LOCALE else EN_LOCALE val url = buildString { From be2d2fc5268772696eea623f0a219426db35386e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Apr 2024 20:48:36 +0500 Subject: [PATCH 27/85] Updated on 2026-08-14 --- .../send/impl/presentation/state/SendUiState.kt | 2 -- .../state/amount/AmountStateFactory.kt | 14 -------------- .../impl/presentation/state/fee/FeeStateFactory.kt | 3 --- .../impl/presentation/ui/SendNavigationButtons.kt | 3 +-- .../impl/presentation/ui/amount/AmountButtons.kt | 2 -- .../impl/presentation/ui/amount/AmountField.kt | 12 +----------- .../presentation/ui/amount/AmountFieldContainer.kt | 1 - .../presentation/ui/amount/SendAmountContent.kt | 1 - .../impl/presentation/viewmodel/SendViewModel.kt | 2 +- 9 files changed, 3 insertions(+), 37 deletions(-) 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 b3772f48eb..2b14283039 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 @@ -51,8 +51,6 @@ internal sealed class SendStates { val isSegmentedButtonsEnabled: Boolean, val amountTextField: SendTextField.AmountField, val appCurrencyCode: String, - val isFeeLoading: Boolean, - val subtractedFee: BigDecimal?, ) : SendStates() /** Recipient state */ diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt index 204c2f9e08..6f56b6aff3 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt @@ -41,18 +41,4 @@ internal class AmountStateFactory( } fun getOnCurrencyChangedState(isFiat: Boolean) = amountCurrencyConverter.convert(isFiat) - - fun getAmountNotificationState(notifications: ImmutableList): SendUiState { - val state = currentStateProvider() - return state.copy( - amountState = state.amountState?.copy(notifications = notifications), - ) - } - - fun getOnAmountFeeLoadingCancel(): SendUiState { - val state = currentStateProvider() - return state.copy( - amountState = state.amountState?.copy(isFeeLoading = false), - ) - } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index 5e34a54d61..a74613acd3 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -45,7 +45,6 @@ internal class FeeStateFactory( val state = currentStateProvider() val feeState = state.feeState ?: return state return state.copy( - amountState = state.amountState?.copy(isFeeLoading = true), feeState = feeState.copy( feeSelectorState = if (feeState.feeSelectorState is FeeSelectorState.Content) { feeState.feeSelectorState @@ -78,7 +77,6 @@ internal class FeeStateFactory( val fee = feeConverter.convert(updatedFeeSelectorState) return state.copy( - amountState = state.amountState?.copy(isFeeLoading = false), feeState = feeState.copy( feeSelectorState = updatedFeeSelectorState, fee = fee, @@ -90,7 +88,6 @@ internal class FeeStateFactory( fun onFeeOnErrorState(): SendUiState { val state = currentStateProvider() return state.copy( - amountState = state.amountState?.copy(isFeeLoading = false), feeState = state.feeState?.copy( feeSelectorState = FeeSelectorState.Error, ), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index fda14b8a8a..604a9ba0a6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -78,7 +78,6 @@ private fun SendNavigationButton( val isFromConfirmation = currentState.isFromConfirmation val isCorrectScreen = currentState.type == SendUiStateType.Amount || currentState.type == SendUiStateType.Fee val isSendingState = currentState.type == SendUiStateType.Send && !isSuccess && !isSending - val showProgress = uiState.amountState?.isFeeLoading == true val (buttonTextId, buttonClick) = getButtonData( currentState = currentState, @@ -120,7 +119,7 @@ private fun SendNavigationButton( if (isSendingState) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) buttonClick() }, - showProgress = showProgress, + showProgress = false, modifier = Modifier.fillMaxWidth(), colors = TangemButtonsDefaults.primaryButtonColors, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt index 4e05fe9506..340f8e20ad 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt @@ -28,7 +28,6 @@ private const val AMOUNT_BUTTONS_KEY = "amountButtonsKey" internal fun LazyListScope.buttons( segmentedButtonConfig: PersistentList, clickIntents: SendClickIntents, - isMaxButtonEnabled: Boolean, isSegmentedButtonsEnabled: Boolean, ) { item( @@ -61,7 +60,6 @@ internal fun LazyListScope.buttons( } SecondaryButton( text = stringResource(R.string.send_max_amount), - enabled = isMaxButtonEnabled, onClick = { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) clickIntents.onMaxValueClick() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt index 84dd5252c8..f92a93cdae 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt @@ -25,7 +25,7 @@ import com.tangem.features.send.impl.presentation.state.fields.SendTextField import kotlinx.coroutines.job @Composable -internal fun AmountField(sendField: SendTextField.AmountField, isEnabled: Boolean, appCurrencyCode: String) { +internal fun AmountField(sendField: SendTextField.AmountField, appCurrencyCode: String) { val decimalFormat = rememberDecimalFormat() val isFiatValue = sendField.isFiatValue val currencyCode = if (isFiatValue) appCurrencyCode else null @@ -35,15 +35,6 @@ internal fun AmountField(sendField: SendTextField.AmountField, isEnabled: Boolea sendField.cryptoAmount to sendField.value } val requester = remember { FocusRequester() } - var isEnabledProxy by remember { mutableStateOf(isEnabled) } - - // Fix animation from amount screen to summary screen ([REDACTED_TASK_KEY]) - LaunchedEffect(key1 = isEnabled) { - if (isEnabled) { - delay(timeMillis = 700) - } - isEnabledProxy = isEnabled - } AmountTextField( value = primaryValue, @@ -61,7 +52,6 @@ internal fun AmountField(sendField: SendTextField.AmountField, isEnabled: Boolea color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, ), - isEnabled = isEnabledProxy, isAutoResize = true, modifier = Modifier .focusRequester(requester) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt index d9926e3f54..eb78c8d0a1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt @@ -62,7 +62,6 @@ internal fun LazyListScope.amountField( ) AmountField( sendField = amountState.amountTextField, - isEnabled = !amountState.isFeeLoading, appCurrencyCode = amountState.appCurrencyCode, ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index e85226d60d..6806468b2b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt @@ -34,7 +34,6 @@ internal fun SendAmountContent( buttons( segmentedButtonConfig = amountState.segmentedButtonConfig, clickIntents = clickIntents, - isMaxButtonEnabled = !amountState.isFeeLoading, isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled, ) } 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 71bcfa8718..216ce22112 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 @@ -705,7 +705,7 @@ internal class SendViewModel @Inject constructor( updateFeeNotifications() }.saveIn(feeJobHolder) .invokeOnCompletion { - uiState = amountStateFactory.getOnAmountFeeLoadingCancel() + // todo } } From 34344f71b037138db89ff3963a119426f0a24d72 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Apr 2024 20:48:42 +0500 Subject: [PATCH 28/85] Updated on 2026-08-14 --- .../presentation/viewmodel/SendViewModel.kt | 50 +++---------------- 1 file changed, 7 insertions(+), 43 deletions(-) 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 216ce22112..ae1cc08d4c 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 @@ -192,7 +192,6 @@ internal class SendViewModel @Inject constructor( private var qrScannerJobHolder = JobHolder() private var sendIdleTimer = 0L - private var feeIdleTimer = 0L init { subscribeOnCurrencyStatusUpdates() @@ -488,12 +487,7 @@ internal class SendViewModel @Inject constructor( if (onFeeNext()) return } SendUiStateType.Amount -> { - if (uiState.feeState?.feeSelectorState is FeeSelectorState.Content) { - if (onFeeCoverageAlert()) return - } else { - loadFee(isToNextState = true) - return - } + loadFee() } else -> Unit } @@ -519,7 +513,6 @@ internal class SendViewModel @Inject constructor( innerRouter.openTokenDetails(userWalletId, currency) private fun onFeeNext(): Boolean { - if (onFeeCoverageAlert()) return true if (checkIfFeeTooLow(uiState)) { uiState = eventStateFactory.getFeeTooLowAlert( onConsume = { uiState = eventStateFactory.onConsumeEventState() }, @@ -538,19 +531,6 @@ internal class SendViewModel @Inject constructor( ) } - private fun onFeeCoverageAlert(): Boolean { - val isFeeCoverage = checkFeeCoverage(uiState, cryptoCurrencyStatus) - return if (isAmountSubtractAvailable && isFeeCoverage) { - uiState = eventStateFactory.getFeeCoverageAlert( - onConsume = { uiState = eventStateFactory.onConsumeEventState() }, - ) - true - } else { - analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount(false)) - false - } - } - private fun cancelFeeRequest() { viewModelScope.launch(dispatchers.main) { feeJobHolder.cancel() @@ -652,7 +632,7 @@ internal class SendViewModel @Inject constructor( // endregion // region fee - override fun feeReload(isToNextState: Boolean) = loadFee(isToNextState = isToNextState) + override fun feeReload() = loadFee() override fun onFeeSelectorClick(feeType: FeeType) { uiState = feeStateFactory.onFeeSelectedState(feeType) @@ -677,10 +657,7 @@ internal class SendViewModel @Inject constructor( innerRouter.openUrl(url) } - private fun loadFee(isToNextState: Boolean = false) { - // debouncing fee request - if (SystemClock.elapsedRealtime() - feeIdleTimer < FEE_UPDATE_DELAY) return - + private fun loadFee() { viewModelScope.launch(dispatchers.main) { val isShowStatus = uiState.feeState?.fee == null if (isShowStatus) { @@ -688,18 +665,14 @@ internal class SendViewModel @Inject constructor( } val result = callFeeUseCase()?.fold( ifRight = { - feeIdleTimer = SystemClock.elapsedRealtime() uiState = feeStateFactory.onFeeOnLoadedState(it) - if (isToNextState && !onFeeCoverageAlert()) { - stateRouter.showSend() - } }, ifLeft = { - onFeeLoadFailed(isShowStatus, isToNextState) + onFeeLoadFailed(isShowStatus) }, ) if (result == null) { - onFeeLoadFailed(isShowStatus, isToNextState) + onFeeLoadFailed(isShowStatus) } updateNotifications() updateFeeNotifications() @@ -709,15 +682,8 @@ internal class SendViewModel @Inject constructor( } } - private fun onFeeLoadFailed(isShowStatus: Boolean, isToNextState: Boolean) { - when { - isToNextState -> { - uiState = eventStateFactory.getFeeUnreachableErrorState { - uiState = eventStateFactory.onConsumeEventState() - } - } - isShowStatus -> uiState = feeStateFactory.onFeeOnErrorState() - } + private fun onFeeLoadFailed(isShowStatus: Boolean) { + if (isShowStatus) uiState = feeStateFactory.onFeeOnErrorState() } private suspend fun checkIfSubtractAvailable() { @@ -751,7 +717,6 @@ internal class SendViewModel @Inject constructor( verifyAndSendTransaction() } else { onCheckFeeUpdate() - feeIdleTimer = SystemClock.elapsedRealtime() sendIdleTimer = SystemClock.elapsedRealtime() } } @@ -921,7 +886,6 @@ internal class SendViewModel @Inject constructor( private companion object { const val CHECK_FEE_UPDATE_DELAY = 60_000L - const val FEE_UPDATE_DELAY = 10_000L const val BALANCE_UPDATE_DELAY = 10_000L const val RU_LOCALE = "ru" From 178ae9c9280a8ec779a3e90e73b25313b681b94c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Apr 2024 20:49:09 +0500 Subject: [PATCH 29/85] Updated on 2026-08-14 --- .../analytics/SendAnalyticEvents.kt | 4 ++ .../presentation/state/SendNotification.kt | 10 +-- .../state/confirm/SendNotificationFactory.kt | 56 ++++++++++------ .../presentation/state/fee/FeeCalculation.kt | 64 +++++++++++++++++-- .../presentation/viewmodel/SendViewModel.kt | 14 +++- .../com/tangem/lib/crypto/BlockchainUtils.kt | 7 ++ 6 files changed, 122 insertions(+), 33 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt index 2f48bcb034..e99471d8de 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt @@ -103,6 +103,10 @@ internal sealed class SendAnalyticEvents( params = mapOf(TOKEN to token), ) + data object NoticeFeeCoverage : SendAnalyticEvents( + event = "Notice - Network Fee Coverage", + ) + /** If error occurs during send transactions */ data class TransactionError(val token: String) : SendAnalyticEvents( event = "Error - Transaction Rejected", diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt index e59bd0e517..3334f3faaa 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt @@ -42,11 +42,6 @@ internal sealed class SendNotification(val config: NotificationConfig) { ), ) - data class ReserveAmountError(val amount: String) : Error( - title = resourceReference(R.string.send_notification_invalid_reserve_amount_title, wrappedList(amount)), - subtitle = resourceReference(R.string.send_notification_invalid_reserve_amount_text), - ) - data class TransactionLimitError( val cryptoCurrency: String, val utxoLimit: String, @@ -153,5 +148,10 @@ internal sealed class SendNotification(val config: NotificationConfig) { onClick = onRefresh, ), ) + + data object FeeCoverageNotification : Warning( + title = resourceReference(R.string.send_network_fee_warning_title), + subtitle = resourceReference(R.string.swapping_network_fee_warning_content), + ) } } \ No newline at end of file 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 3ad03b434c..3b01354982 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 @@ -18,11 +18,10 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents import com.tangem.features.send.impl.presentation.state.* -import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState -import com.tangem.features.send.impl.presentation.state.fee.FeeType -import com.tangem.features.send.impl.presentation.state.fee.checkIfFeeTooHigh +import com.tangem.features.send.impl.presentation.state.fee.* import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils.isDogecoin +import com.tangem.lib.crypto.BlockchainUtils.isTezos import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -36,11 +35,11 @@ import java.math.BigDecimal internal class SendNotificationFactory( private val cryptoCurrencyStatusProvider: Provider, private val coinCryptoCurrencyStatusProvider: Provider, - private val feePaidCryptoCurrencyStatusProvider: Provider, private val currentStateProvider: Provider, private val userWalletProvider: Provider, private val currencyChecksRepository: CurrencyChecksRepository, private val stateRouterProvider: Provider, + private val isSubtractAvailableProvider: Provider, private val clickIntents: SendClickIntents, private val analyticsEventHandler: AnalyticsEventHandler, private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, @@ -52,19 +51,33 @@ internal class SendNotificationFactory( val state = currentStateProvider() val sendState = state.sendState ?: return@map persistentListOf() val feeState = state.feeState ?: return@map persistentListOf() - val feeAmount = feeState.fee?.amount?.value ?: BigDecimal.ZERO + val balance = cryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO + val feeValue = feeState.fee?.amount?.value ?: BigDecimal.ZERO + val isFeeCoverage = checkFeeCoverage( + isSubtractAvailable = isSubtractAvailableProvider(), + balance = balance, + amountValue = amountValue, + feeValue = feeValue, + ) + val sendingAmount = calculateSubtractedAmount( + isFeeCoverage = isFeeCoverage, + cryptoCurrencyStatus = cryptoCurrencyStatusProvider(), + amountValue = amountValue, + feeValue = feeValue, + ) buildList { // errors addFeeUnreachableNotification(feeState.feeSelectorState) - addExceedBalanceNotification(feeAmount, amountValue) + addExceedBalanceNotification(feeValue, sendingAmount) addExceedsBalanceNotification(feeState.fee) - addMinimumAmountErrorNotification(feeAmount, amountValue) - addDustWarningNotification(feeAmount, amountValue) - addTransactionLimitErrorNotification(feeAmount, amountValue) + addMinimumAmountErrorNotification(feeValue, sendingAmount) + addDustWarningNotification(feeValue, sendingAmount) + addTransactionLimitErrorNotification(feeValue, sendingAmount) // warnings - addExistentialWarningNotification(feeAmount, amountValue) - addHighFeeWarningNotification(feeAmount, amountValue, sendState.ignoreAmountReduce) + addExistentialWarningNotification(feeValue, amountValue) + addFeeCoverageNotification(isFeeCoverage) + addHighFeeWarningNotification(amountValue, sendState.ignoreAmountReduce) addTooHighNotification(feeState.feeSelectorState) addTooLowNotification(feeState) }.toImmutableList() @@ -95,13 +108,11 @@ internal class SendNotificationFactory( receivedAmount: BigDecimal, ) { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val feePaidCryptoCurrencyStatus = feePaidCryptoCurrencyStatusProvider() - val cryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val isCurrentNotFeePaidCurrency = cryptoCurrencyStatus.currency.id != feePaidCryptoCurrencyStatus?.currency?.id - if (isCurrentNotFeePaidCurrency) return + if (!isSubtractAvailableProvider()) return - val showNotification = receivedAmount + feeAmount > cryptoAmount + val showNotification = receivedAmount + feeAmount > balance if (showNotification) { add(SendNotification.Error.TotalExceedsBalance) } @@ -192,16 +203,22 @@ internal class SendNotificationFactory( } } + private fun MutableList.addFeeCoverageNotification(sendingAmount: Boolean) { + if (sendingAmount) { + analyticsEventHandler.send(SendAnalyticEvents.NoticeFeeCoverage) + add(SendNotification.Warning.FeeCoverageNotification) + } + } + private fun MutableList.addHighFeeWarningNotification( - feeAmount: BigDecimal, sendAmount: BigDecimal, ignoreAmountReduce: Boolean, ) { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val isTezos = cryptoCurrencyStatus.currency.network.id.value == Blockchain.Tezos.id + val isTezos = isTezos(cryptoCurrencyStatus.currency.network.id.value) val threshold = Blockchain.Tezos.minimalAmount() - val isTotalBalance = feeAmount.plus(sendAmount) >= balance && balance > threshold + val isTotalBalance = sendAmount >= balance && balance > threshold if (!ignoreAmountReduce && isTotalBalance && isTezos) { add( SendNotification.Warning.HighFeeError( @@ -218,6 +235,7 @@ internal class SendNotificationFactory( } } + // todo remove in [REDACTED_TASK_KEY] private fun MutableList.addMinimumAmountErrorNotification( feeAmount: BigDecimal, receivedAmount: BigDecimal, 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 31307124fd..8ec4bfd4d6 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 @@ -1,21 +1,73 @@ package com.tangem.features.send.impl.presentation.state.fee +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.common.extensions.minimalAmount import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.lib.crypto.BlockchainUtils import java.math.BigDecimal import java.math.RoundingMode /** - * Check if sending amount with fee is greater than balance + * Check and calculates subtracted amount */ -internal fun checkFeeCoverage(state: SendUiState, cryptoCurrencyStatus: CryptoCurrencyStatus): Boolean { - val balance = cryptoCurrencyStatus.value.amount ?: return false - val fee = state.feeState?.fee?.amount?.value ?: return false - val amount = state.amountState?.amountTextField?.cryptoAmount?.value ?: return false - return balance < amount + fee && balance > fee +internal fun checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable: Boolean, + cryptoCurrencyStatus: CryptoCurrencyStatus, + amountValue: BigDecimal, + feeValue: BigDecimal, +): BigDecimal { + val balance = cryptoCurrencyStatus.value.amount ?: return amountValue + val isFeeCoverage = checkFeeCoverage( + isSubtractAvailable = isAmountSubtractAvailable, + balance = balance, + amountValue = amountValue, + feeValue = feeValue, + ) + return calculateSubtractedAmount( + isFeeCoverage = isFeeCoverage, + cryptoCurrencyStatus = cryptoCurrencyStatus, + amountValue = amountValue, + feeValue = feeValue, + ) +} + +/** + * Checks if sending amount with fee is greater than balance + */ +internal fun checkFeeCoverage( + isSubtractAvailable: Boolean, + balance: BigDecimal, + amountValue: BigDecimal, + feeValue: BigDecimal, +): Boolean { + if (!isSubtractAvailable) return false + return balance < amountValue + feeValue && balance > feeValue +} + +/** + * Calculates subtracted amount + */ +internal fun calculateSubtractedAmount( + isFeeCoverage: Boolean, + cryptoCurrencyStatus: CryptoCurrencyStatus, + amountValue: BigDecimal, + feeValue: BigDecimal, +): BigDecimal { + val balance = cryptoCurrencyStatus.value.amount ?: return amountValue + return if (isFeeCoverage) { + var subtractedValue = minOf(amountValue, balance.minus(feeValue)) + if (BlockchainUtils.isTezos(cryptoCurrencyStatus.currency.network.id.value)) { + val threshold = Blockchain.Tezos.minimalAmount() + subtractedValue = -threshold + } + subtractedValue + } else { + amountValue + } } /** 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 ae1cc08d4c..caec97e044 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 @@ -157,10 +157,10 @@ internal class SendViewModel @Inject constructor( private val sendNotificationFactory = SendNotificationFactory( cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus }, - feePaidCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus }, currentStateProvider = Provider { uiState }, userWalletProvider = Provider { userWallet }, stateRouterProvider = Provider { stateRouter }, + isSubtractAvailableProvider = Provider { isAmountSubtractAvailable }, currencyChecksRepository = currencyChecksRepository, clickIntents = this, analyticsEventHandler = analyticsEventHandler, @@ -757,7 +757,7 @@ internal class SendViewModel @Inject constructor( override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) { uiState = amountStateFactory.getOnAmountValueChange(reducedAmount.parseBigDecimal(cryptoCurrency.decimals)) uiState = sendNotificationFactory.dismissNotificationState(clazz) - updateNotifications() + feeReload() } override fun onNotificationCancel(clazz: Class) { @@ -770,10 +770,18 @@ internal class SendViewModel @Inject constructor( val fee = feeState.fee ?: return val memo = uiState.recipientState?.memoTextField?.value val amountValue = uiState.amountState?.amountTextField?.cryptoAmount?.value ?: return + val feeValue = fee.amount.value ?: return + + val receivingAmount = checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable = isAmountSubtractAvailable, + cryptoCurrencyStatus = cryptoCurrencyStatus, + amountValue = amountValue, + feeValue = feeValue, + ) viewModelScope.launch(dispatchers.main) { createTransactionUseCase( - amount = amountValue.convertToAmount(cryptoCurrency), + amount = receivingAmount.convertToAmount(cryptoCurrency), fee = fee, memo = memo, destination = recipient, diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index cf10f1ce54..a4492852b0 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -33,8 +33,15 @@ object BlockchainUtils { return blockchain == Blockchain.Bitcoin || blockchain == Blockchain.BitcoinTestnet } + /** If current [networkId] is Dogecoin */ fun isDogecoin(networkId: String): Boolean { val blockchain = Blockchain.fromId(networkId) return blockchain == Blockchain.Dogecoin } + + /** If current [networkId] is Tezos */ + fun isTezos(networkId: String): Boolean { + val blockchain = Blockchain.fromId(networkId) + return blockchain == Blockchain.Tezos + } } \ No newline at end of file From 0ce55566d173d9c08a9c716c4a6770537740f3ce Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Apr 2024 12:48:22 +0200 Subject: [PATCH 30/85] Updated on 2026-08-14 --- .../src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index d8edc6401c..c3fd4f4fd5 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -433,7 +433,7 @@ internal class StateBuilder( SwapWarning.NeedReserveToCreateAccount( notificationConfig = createActivateAccountNotificationConfig( status.amountToCreateAccount, - quoteModel.toTokenInfo.cryptoCurrencyStatus.currency.name, + quoteModel.toTokenInfo.cryptoCurrencyStatus.currency.symbol, ), ), ) From 2f2a8b04b5ae4da0fffa11d7f761d9607a0dc7e2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Apr 2024 18:30:51 +0800 Subject: [PATCH 31/85] Updated on 2026-08-14 --- .../java/com/tangem/feature/referral/ui/ReferralScreen.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 279154964b..74f9653422 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -27,7 +27,6 @@ import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.appbar.AppBarWithBackButton -import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.domain.models.ExpectedAward import com.tangem.feature.referral.domain.models.ExpectedAwards @@ -350,7 +349,7 @@ private fun BoxScope.ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) { modifier = Modifier.fillMaxWidth(), actionOnNewLine = true, shape = RoundedCornerShape(size = TangemTheme.dimens.radius8), - containerColor = TangemColorPalette.Black, + containerColor = TangemTheme.colors.button.primary, contentColor = TangemTheme.colors.text.primary2, actionColor = TangemTheme.colors.text.primary2, ) @@ -464,7 +463,7 @@ private fun Preview_ReferralScreen_Participant_InLightTheme() { url = "", expectedAwards = null, ), - errorSnackbar = null, + errorSnackbar = ErrorSnackbar(DemoModeException()) {}, analytics = Analytics( onAgreementClicked = {}, onCopyClicked = {}, @@ -493,7 +492,7 @@ private fun Preview_ReferralScreen_Participant_InDarkTheme() { url = "", expectedAwards = null, ), - errorSnackbar = null, + errorSnackbar = ErrorSnackbar(DemoModeException()) {}, analytics = Analytics( onAgreementClicked = {}, onCopyClicked = {}, From 37a224169cff35034f72e0a511adeef7da52a3bc Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Apr 2024 17:27:39 +0500 Subject: [PATCH 32/85] Updated on 2026-08-14 --- .../ui/components/fields/AmountTextField.kt | 4 ++ .../ui/components/fields/SimpleTextField.kt | 58 ++++++++----------- .../state/amount/AmountStateFactory.kt | 7 +++ .../amount/SendAmountCurrencyConverter.kt | 1 + ...SendAmountPastedTriggerDismissConverter.kt | 21 +++++++ .../state/fields/SendAmountFieldConverter.kt | 2 + .../SendAmountFieldMaxAmountConverter.kt | 1 + .../state/fields/SendTextField.kt | 2 + .../previewdata/AmountStatePreviewData.kt | 2 + .../state/previewdata/SendClickIntentsStub.kt | 2 + .../presentation/ui/amount/AmountField.kt | 2 + .../viewmodel/SendClickIntents.kt | 2 + .../presentation/viewmodel/SendViewModel.kt | 4 ++ 13 files changed, 75 insertions(+), 33 deletions(-) create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountPastedTriggerDismissConverter.kt 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 75836f873a..c095c2b8fd 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 @@ -64,6 +64,8 @@ fun AmountTextField( keyboardActions: KeyboardActions = KeyboardActions.Default, isEnabled: Boolean = true, isAutoResize: Boolean = false, + isValuePasted: Boolean = false, + onValuePastedTriggerDismiss: () -> Unit = {}, @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false) reduceFactor: Double = 0.9, ) { @@ -101,6 +103,8 @@ fun AmountTextField( fontSize = fontSize, textDirection = TextDirection.ContentOrLtr, ), + isValuePasted = isValuePasted, + onValuePastedTriggerDismiss = onValuePastedTriggerDismiss, color = textColor, keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index b9cba56550..238b773614 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -39,15 +39,13 @@ fun SimpleTextField( color: Color = TangemTheme.colors.text.primary1, textStyle: TextStyle = TangemTheme.typography.body2.copy(color = color), readOnly: Boolean = false, + isValuePasted: Boolean = false, + onValuePastedTriggerDismiss: () -> Unit = {}, decorationBox: (@Composable (innerTextField: @Composable () -> Unit) -> Unit)? = null, ) { + val proxyValue by remember(value) { derivedStateOf { value } } var textFieldValueState by remember { - mutableStateOf( - TextFieldValue( - text = value, - selection = getValueRange(value), - ), - ) + mutableStateOf(TextFieldValue(text = value)) } val focusRequester = remember { FocusRequester.Default } val customTextSelectionColors = TextSelectionColors( @@ -55,36 +53,28 @@ fun SimpleTextField( backgroundColor = TangemTheme.colors.text.accent.copy(alpha = 0.3f), ) - val textFieldValue = textFieldValueState.copy(text = value) - - val isSelectionChanged by remember { - derivedStateOf { - textFieldValue.selection != textFieldValueState.selection || - textFieldValue.composition != textFieldValueState.composition || - textFieldValue.text != textFieldValueState.text - } + var lastTextValue by remember(proxyValue, isValuePasted) { + textFieldValueState = textFieldValueState.copy( + text = proxyValue, + selection = if (isValuePasted) { + TextRange(proxyValue.length, proxyValue.length) + } else { + textFieldValueState.selection + }, + ) + mutableStateOf(proxyValue) } - LaunchedEffect(key1 = isSelectionChanged) { - if (isSelectionChanged) { - textFieldValueState = textFieldValue + // resets paste value cursor trigger + LaunchedEffect(key1 = isValuePasted) { + if (isValuePasted) { + onValuePastedTriggerDismiss() } } - var lastTextValue by remember(value) { - val isSelectionLastIndex = textFieldValueState.selection.end == textFieldValueState.text.lastIndex - if (textFieldValueState.text.isBlank() || isSelectionLastIndex) { - textFieldValueState = textFieldValueState.copy( - text = value, - selection = getValueRange(value), - ) - } - mutableStateOf(value) - } - CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) { BasicTextField( - value = textFieldValue, + value = textFieldValueState, onValueChange = { newTextFieldValueState -> textFieldValueState = newTextFieldValueState @@ -114,10 +104,12 @@ fun SimpleTextField( } } -private fun getValueRange(value: String) = when { - value.isEmpty() -> TextRange.Zero - else -> TextRange(value.length, value.length) -} +// private fun TextRange.getValueRange(oldValue: String, newValue: String) = when { +// newValue.isEmpty() -> TextRange.Zero +// newValue.length - oldValue.length > 1 -> TextRange(newValue.length, newValue.length) +// newValue.length - oldValue.length > 0 -> TextRange(this.start.inc(), this.end.inc()) +// else -> this +// } @Composable private fun SimpleTextPlaceholder( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt index 6f56b6aff3..3321568099 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt @@ -33,6 +33,11 @@ internal class AmountStateFactory( cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } + private val amountPasteConverter by lazy(LazyThreadSafetyMode.NONE) { + SendAmountPastedTriggerDismissConverter( + currentStateProvider = currentStateProvider, + ) + } fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value) @@ -41,4 +46,6 @@ internal class AmountStateFactory( } fun getOnCurrencyChangedState(isFiat: Boolean) = amountCurrencyConverter.convert(isFiat) + + fun getOnAmountPastedTriggerDismiss() = amountPasteConverter.convert(false) } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt index 0f95e9fad7..f6ff524e9f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt @@ -24,6 +24,7 @@ internal class SendAmountCurrencyConverter( amountState = amountState.copy( amountTextField = amountTextField.copy( isFiatValue = value, + isValuePasted = true, ), ), ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountPastedTriggerDismissConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountPastedTriggerDismissConverter.kt new file mode 100644 index 0000000000..497629bdd5 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountPastedTriggerDismissConverter.kt @@ -0,0 +1,21 @@ +package com.tangem.features.send.impl.presentation.state.amount + +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.utils.Provider +import com.tangem.utils.converter.Converter + +internal class SendAmountPastedTriggerDismissConverter( + private val currentStateProvider: Provider, +) : Converter { + override fun convert(value: Boolean): SendUiState { + val state = currentStateProvider() + val amountState = state.amountState ?: return state + return state.copy( + amountState = amountState.copy( + amountTextField = amountState.amountTextField.copy( + isValuePasted = false, + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt index 332a736cd0..f2ad4e7e71 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt @@ -55,6 +55,8 @@ internal class SendAmountFieldConverter( isError = false, error = TextReference.Res(R.string.swapping_insufficient_funds), isFiatUnavailable = fiatRate == null, + isValuePasted = false, + onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss, ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt index b29459e757..bb69b15be3 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt @@ -35,6 +35,7 @@ internal class SendAmountFieldMaxAmountConverter( amountState = amountState.copy( isPrimaryButtonEnabled = true, amountTextField = amountTextField.copy( + isValuePasted = true, value = cryptoValue, fiatValue = fiatValue, isError = false, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt index 266f9e8f6e..6cd4f8c31d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt @@ -28,6 +28,8 @@ internal sealed class SendTextField { val isFiatValue: Boolean, val fiatValue: String, val isFiatUnavailable: Boolean, + val isValuePasted: Boolean, + val onValuePastedTriggerDismiss: () -> Unit, val isError: Boolean, val error: TextReference, ) : SendTextField() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt index cbe162c0c5..d39cd3df01 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt @@ -45,6 +45,8 @@ internal object AmountStatePreviewData { isFiatUnavailable = false, isError = false, error = TextReference.EMPTY, + isValuePasted = false, + onValuePastedTriggerDismiss = {}, ), isSegmentedButtonsEnabled = true, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 10a3142e61..5235588e51 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -30,6 +30,8 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onMaxValueClick() {} + override fun onAmountPasteTriggerDismiss() {} + override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) {} override fun onRecipientMemoValueChange(value: String) {} diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt index f92a93cdae..640189da93 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt @@ -53,6 +53,8 @@ internal fun AmountField(sendField: SendTextField.AmountField, appCurrencyCode: textAlign = TextAlign.Center, ), isAutoResize = true, + isValuePasted = sendField.isValuePasted, + onValuePastedTriggerDismiss = sendField.onValuePastedTriggerDismiss, modifier = Modifier .focusRequester(requester) .padding( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index 74e5d81128..a353059047 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -30,6 +30,8 @@ internal interface SendClickIntents { fun onCurrencyChangeClick(isFiat: Boolean) fun onMaxValueClick() + + fun onAmountPasteTriggerDismiss() // endregion // region Recipient 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 caec97e044..90348ad8d5 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 @@ -551,6 +551,10 @@ internal class SendViewModel @Inject constructor( uiState = amountStateFactory.getOnMaxAmountClick() analyticsEventHandler.send(SendAnalyticEvents.MaxAmountButtonClicked) } + + override fun onAmountPasteTriggerDismiss() { + uiState = amountStateFactory.getOnAmountPastedTriggerDismiss() + } // endregion // region recipient state clicks From 09b489b175d666ea4fb01884cf7238276150857b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Apr 2024 17:03:53 +0300 Subject: [PATCH 33/85] Updated on 2026-08-14 --- .../tap/features/send/ui/SendFragment.kt | 101 +++++++++--------- .../DefaultQrScanningEventsRepository.kt | 13 ++- .../feature/qrscanning/QrScanningFragment.kt | 20 +++- .../viewmodel/BaseQrScanningClickIntents.kt | 12 +-- .../viewmodel/QrScanningClickIntents.kt | 17 ++- .../viewmodel/QrScanningViewModel.kt | 6 +- 6 files changed, 100 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 0402c5e2de..23484d916d 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -13,8 +13,8 @@ import androidx.core.view.postDelayed import androidx.core.widget.addTextChangedListener import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle -import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import arrow.core.getOrElse @@ -54,14 +54,13 @@ import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.ui.adapters.WarningMessagesAdapter import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber -import com.tangem.tap.mainScope import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentSendBinding import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber @@ -73,6 +72,7 @@ private const val EDIT_TEXT_INPUT_DEBOUNCE = 400L /** [REDACTED_AUTHOR] */ +@Suppress("LargeClass") @OptIn(FlowPreview::class) @AndroidEntryPoint class SendFragment : BaseStoreFragment(R.layout.fragment_send) { @@ -103,18 +103,25 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { lifecycle.addObserver(viewModel) sendSubscriber.initViewModel(viewModel) Analytics.send(Token.Send.ScreenOpened()) - listenToQrCode() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + subscribeToQrCodeScanner() + subscribeToTransactionExtrasFields() + subscribeToAddressField() + subscribeToAmountField() + } + } + addBackPressHandler(this) etAmountToSend = view.findViewById(R.id.etAmountToSend) initSendButtonStates() setupAddressLayout() - setupTransactionExtrasLayout() setupAmountLayout() setupFeeLayout() setupWarningMessages() @@ -149,14 +156,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { setOnFocusChangeListener { _, hasFocus -> store.dispatch(TruncateOrRestore(!hasFocus)) } - - inputtedTextAsFlow() - .debounce(EDIT_TEXT_INPUT_DEBOUNCE) - .filter { store.state.sendState.addressState.viewFieldValue.value != it } - .onEach { - store.dispatch(AddressActionUi.HandleUserInput(it)) - } - .launchIn(mainScope) } imvPaste.setOnClickListener { @@ -183,31 +182,41 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { } } - private fun listenToQrCode() { - lifecycleScope.launch { - listenToQrScanningUseCase(SourceType.SEND) - .getOrElse { emptyFlow() } - .flowWithLifecycle(this@SendFragment.lifecycle, minActiveState = Lifecycle.State.CREATED) - .collect { rawQr -> - delay(200) - - // Delayed launch is needed in order for the UI to be drawn and to process the sent events. - // If do not use the delay, then etAmount error field is not displayed when - // inserting an incorrect amount by shareUri - cryptoCurrency?.let { cryptoCurrency -> - parseQrCodeUseCase(rawQr, cryptoCurrency = cryptoCurrency).fold( - ifLeft = { - onCodeScanned(QrResult(address = rawQr)) - Timber.w(it) - }, - ifRight = { onCodeScanned(it) }, - ) - } ?: onCodeScanned(QrResult(address = rawQr)) - } - } + private fun CoroutineScope.subscribeToAddressField() = with(binding.lSendAddress) { + etAddress.inputtedTextAsFlow() + .debounce(EDIT_TEXT_INPUT_DEBOUNCE) + .filter { store.state.sendState.addressState.viewFieldValue.value != it } + .onEach { + store.dispatch(AddressActionUi.HandleUserInput(it)) + } + .launchIn(this@subscribeToAddressField) } - private fun setupTransactionExtrasLayout() = with(binding.lSendAddress) { + private fun CoroutineScope.subscribeToAmountField() { + etAmountToSend.inputtedTextAsFlow() + .debounce(EDIT_TEXT_INPUT_DEBOUNCE) + .filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() } + .onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) } + .launchIn(this) + } + + private fun CoroutineScope.subscribeToQrCodeScanner() { + listenToQrScanningUseCase(SourceType.SEND) + .getOrElse { emptyFlow() } + .onEach { rawQr -> + cryptoCurrency?.let { cryptoCurrency -> + parseQrCodeUseCase(rawQr, cryptoCurrency = cryptoCurrency).fold( + ifLeft = { + onCodeScanned(QrResult(address = rawQr)) + Timber.w(it) + }, + ifRight = { onCodeScanned(it) }, + ) + } ?: onCodeScanned(QrResult(address = rawQr)) + }.launchIn(this) + } + + private fun CoroutineScope.subscribeToTransactionExtrasFields() = with(binding.lSendAddress) { // TODO: [REDACTED_TASK_KEY] etXlmMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -216,7 +225,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.xlmMemo?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.XlmMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) etDestinationTag.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -225,7 +234,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.xrpDestinationTag?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.XrpDestinationTag.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) etBinanceMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -234,7 +243,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.binanceMemo?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.BinanceMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) etTonMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -243,7 +252,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.tonMemoState?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.TonMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) etCosmosMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -252,7 +261,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.cosmosMemoState?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.CosmosMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) etHederaMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -261,7 +270,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.hederaMemoState?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.HederaMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) etAlgorandMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -270,7 +279,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.algorandMemoState?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.AlgorandMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) } private fun onCodeScanned(parsedQr: QrResult) { @@ -343,12 +352,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { if (!hasFocus && etAmountToSend.text?.toString() == "") etAmountToSend.setText("0") } - etAmountToSend.inputtedTextAsFlow() - .debounce(EDIT_TEXT_INPUT_DEBOUNCE) - .filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() } - .onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) } - .launchIn(mainScope) - etAmountToSend.setOnImeActionListener(EditorInfo.IME_ACTION_DONE) { it.hideSoftKeyboard() it.clearFocus() diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt index cce12822c5..c4677126a6 100644 --- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt +++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt @@ -6,9 +6,9 @@ import com.tangem.domain.qrscanning.models.QrResult import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.repository.QrScanningEventsRepository import com.tangem.domain.tokens.model.CryptoCurrency -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.yield import java.math.BigDecimal import java.net.URLDecoder @@ -16,15 +16,20 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository { private data class QrScanningEvent(val type: SourceType, val qrCode: String) - private val scannedEvents = MutableSharedFlow() + private val scannedEvents = MutableSharedFlow(replay = 1) override suspend fun emitResult(type: SourceType, qrCode: String) { scannedEvents.emit(QrScanningEvent(type, qrCode)) } + @OptIn(ExperimentalCoroutinesApi::class) override fun subscribeToScanningResults(type: SourceType) = scannedEvents .filter { it.type == type } .map { it.qrCode } + .onEach { + yield() // if we have more than one sub, we must allow them to collect emitted value + scannedEvents.resetReplayCache() + } override fun parseQrCode(qrCode: String, cryptoCurrency: CryptoCurrency): QrResult { val withoutSchema = stripSchema(qrCode, cryptoCurrency) diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt index 577380dc6b..f232473da8 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt @@ -4,6 +4,7 @@ import android.Manifest import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle +import android.view.View import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -15,6 +16,8 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.mlkit.vision.common.InputImage import com.tangem.core.ui.res.TangemTheme @@ -25,6 +28,8 @@ import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter import com.tangem.feature.qrscanning.presentation.QrScanningContent import com.tangem.feature.qrscanning.viewmodel.QrScanningViewModel import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import javax.inject.Inject @@ -69,11 +74,24 @@ internal class QrScanningFragment : ComposeFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewModel.setRouter(innerRouter, galleryLauncher) + viewModel.setRouter(innerRouter) cameraExecutor = Executors.newSingleThreadExecutor() requestCameraPermission() } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.launchGalleryEvent + .collect { + galleryLauncher.launch(it.imageFilter) + delay(timeMillis = 2000) + } + } + } + } + override fun onResume() { super.onResume() checkPermissionGranted() diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt index a9c37e465c..2ff968422a 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt @@ -1,6 +1,5 @@ package com.tangem.feature.qrscanning.viewmodel -import androidx.activity.result.ActivityResultLauncher import com.tangem.domain.qrscanning.models.SourceType import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter import kotlinx.coroutines.CoroutineScope @@ -11,23 +10,14 @@ internal open class BaseQrScanningClickIntents { protected val router: QrScanningInnerRouter get() = _router protected val viewModelScope: CoroutineScope get() = _viewModelScope protected val source: SourceType get() = _source - protected val galleryLauncher: ActivityResultLauncher get() = _galleryLauncher private var _router: QrScanningInnerRouter by Delegates.notNull() private var _viewModelScope: CoroutineScope by Delegates.notNull() private var _source: SourceType by Delegates.notNull() - private var _galleryLauncher: ActivityResultLauncher by Delegates.notNull() - - open fun initialize( - router: QrScanningInnerRouter, - source: SourceType, - galleryLauncher: ActivityResultLauncher, - coroutineScope: CoroutineScope, - ) { + open fun initialize(router: QrScanningInnerRouter, source: SourceType, coroutineScope: CoroutineScope) { _router = router _viewModelScope = coroutineScope _source = source - _galleryLauncher = galleryLauncher } } \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt index 64d837b7f4..075894e1c6 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt @@ -5,11 +5,16 @@ import com.tangem.feature.qrscanning.presentation.QrScanningStateController import com.tangem.feature.qrscanning.presentation.transformers.DismissBottomSheetTransformer import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch import javax.inject.Inject interface QrScanningClickIntents { + val launchGallery: SharedFlow + fun onBackClick() fun onQrScanned(qrCode: String) @@ -17,6 +22,11 @@ interface QrScanningClickIntents { fun onGalleryClicked() } +@JvmInline +value class GalleryRequest( + val imageFilter: String, +) + @ViewModelScoped internal class QrScanningClickIntentsImplementor @Inject constructor( private val stateHolder: QrScanningStateController, @@ -26,6 +36,11 @@ internal class QrScanningClickIntentsImplementor @Inject constructor( private var isScanned = false + override val launchGallery = MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_LATEST, + ) + override fun onBackClick() = router.popBackStack() override fun onQrScanned(qrCode: String) { @@ -41,7 +56,7 @@ internal class QrScanningClickIntentsImplementor @Inject constructor( } override fun onGalleryClicked() { - galleryLauncher.launch(GALLERY_IMAGE_FILTER) + launchGallery.tryEmit(GalleryRequest(imageFilter = GALLERY_IMAGE_FILTER)) if (stateHolder.value.bottomSheetConfig != null) { stateHolder.update(DismissBottomSheetTransformer()) } diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt index 4320634f4d..cca4825f93 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt @@ -1,6 +1,5 @@ package com.tangem.feature.qrscanning.viewmodel -import androidx.activity.result.ActivityResultLauncher import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -14,6 +13,7 @@ import com.tangem.feature.qrscanning.presentation.transformers.DismissBottomShee import com.tangem.feature.qrscanning.presentation.transformers.InitializeQrScanningStateTransformer import com.tangem.feature.qrscanning.presentation.transformers.ShowCameraDeniedBottomSheetTransformer import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @@ -28,12 +28,12 @@ internal class QrScanningViewModel @Inject constructor( private val network: String? = savedStateHandle[NETWORK_KEY] val uiState: StateFlow = stateHolder.uiState + val launchGalleryEvent: SharedFlow = clickIntents.launchGallery - fun setRouter(router: QrScanningInnerRouter, galleryLauncher: ActivityResultLauncher) { + fun setRouter(router: QrScanningInnerRouter) { clickIntents.initialize( router = router, source = source, - galleryLauncher = galleryLauncher, coroutineScope = viewModelScope, ) stateHolder.update(InitializeQrScanningStateTransformer(clickIntents, source, network)) From 585a213d985821bc817d65b9eced3be354f89f42 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Apr 2024 17:28:10 +0500 Subject: [PATCH 34/85] Updated on 2026-08-14 --- .../tangem/core/ui/components/fields/SimpleTextField.kt | 7 ------- .../presentation/state/fields/SendAmountFieldConverter.kt | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index 238b773614..e4737af0c1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -104,13 +104,6 @@ fun SimpleTextField( } } -// private fun TextRange.getValueRange(oldValue: String, newValue: String) = when { -// newValue.isEmpty() -> TextRange.Zero -// newValue.length - oldValue.length > 1 -> TextRange(newValue.length, newValue.length) -// newValue.length - oldValue.length > 0 -> TextRange(this.start.inc(), this.end.inc()) -// else -> this -// } - @Composable private fun SimpleTextPlaceholder( placeholder: TextReference?, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt index f2ad4e7e71..2070b8c633 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt @@ -53,7 +53,7 @@ internal class SendAmountFieldConverter( cryptoAmount = cryptoAmount, fiatAmount = getAppCurrencyAmount(fiatDecimal, appCurrencyProvider()), isError = false, - error = TextReference.Res(R.string.swapping_insufficient_funds), + error = TextReference.Res(R.string.send_validation_amount_exceeds_balance), isFiatUnavailable = fiatRate == null, isValuePasted = false, onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss, From 0aa76e2aaa42b032b2ddb585645aada34307b1a2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Apr 2024 13:17:15 +0300 Subject: [PATCH 35/85] Updated on 2026-08-14 --- .../components/inputrow/InputRowRecipient.kt | 15 ++++++- .../presentation/ui/SendNavigationButtons.kt | 45 ++++++++++++------- .../send/impl/presentation/ui/SendScreen.kt | 45 +++++++++++++------ .../ui/amount/SendAmountContent.kt | 1 + .../ui/fee/SendSpeedAndFeeContent.kt | 4 +- .../ui/recipient/SendRecipientContent.kt | 4 +- .../impl/presentation/ui/send/SendContent.kt | 9 ++-- .../presentation/viewmodel/SendViewModel.kt | 4 +- 8 files changed, 84 insertions(+), 43 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt index c16d459a12..41bbc546b7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt @@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment.Companion.CenterEnd import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier @@ -23,6 +23,7 @@ import com.tangem.core.ui.components.inputrow.inner.PasteButton import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme +import kotlinx.coroutines.delay /** * [Input Row Recipient](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-826&mode=design&t=IQ5lBJEkFGU4WSvi-4) @@ -115,8 +116,18 @@ fun InputRowRecipient( @Composable private fun RowScope.InputIcon(isLoading: Boolean, value: String) { + var isLoadingProxy by remember { mutableStateOf(isLoading) } + + // Do not show the progress indicator, which will disappear quickly + LaunchedEffect(key1 = isLoading) { + if (isLoading) { + delay(timeMillis = 500) + } + isLoadingProxy = isLoading + } + AnimatedContent( - targetState = isLoading, + targetState = isLoadingProxy, label = "Indicator Show Change", modifier = Modifier .align(CenterVertically) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index 604a9ba0a6..bf72bb774a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -7,7 +7,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -29,6 +29,7 @@ import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.SendUiStateType +import kotlinx.coroutines.delay @Composable internal fun SendNavigationButtons( @@ -40,6 +41,7 @@ internal fun SendNavigationButtons( val isSuccess = sendState.isSuccess val isSendingState = currentState.type == SendUiStateType.Send && !isSuccess val isSentState = currentState.type == SendUiStateType.Send && isSuccess + Column( modifier = modifier .padding( @@ -91,25 +93,26 @@ private fun SendNavigationButton( } else { TangemButtonIconPosition.None } - Row( - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), - modifier = modifier, - ) { + + Row(modifier = modifier) { AnimatedVisibility( visible = !isEditingDisabled && isCorrectScreen && !isFromConfirmation, enter = expandHorizontally(expandFrom = Alignment.End), exit = shrinkHorizontally(shrinkTowards = Alignment.End), ) { - Icon( - painter = painterResource(R.drawable.ic_back_24), - tint = TangemTheme.colors.icon.primary1, - contentDescription = null, - modifier = Modifier - .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) - .background(TangemTheme.colors.button.secondary) - .clickable { uiState.clickIntents.onPrevClick() } - .padding(TangemTheme.dimens.spacing12), - ) + Row { + Icon( + painter = painterResource(R.drawable.ic_back_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + modifier = Modifier + .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) + .background(TangemTheme.colors.button.secondary) + .clickable { uiState.clickIntents.onPrevClick() } + .padding(TangemTheme.dimens.spacing12), + ) + SpacerW12() + } } TangemButton( text = stringResource(buttonTextId), @@ -128,8 +131,18 @@ private fun SendNavigationButton( @Composable private fun SendingText(uiState: SendUiState, isVisible: Boolean, modifier: Modifier = Modifier) { + var isVisibleProxy by remember { mutableStateOf(isVisible) } + + // text appearance delay for smooth screen transitions + LaunchedEffect(key1 = isVisible) { + if (isVisible) { + delay(timeMillis = 400) + } + isVisibleProxy = isVisible + } + AnimatedVisibility( - visible = isVisible, + visible = isVisibleProxy, modifier = modifier, enter = slideInVertically().plus(fadeIn()), exit = slideOutVertically().plus(fadeOut()), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index caf0a2f4f2..ffa3bf5d06 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -1,10 +1,8 @@ package com.tangem.features.send.impl.presentation.ui import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.* import androidx.compose.animation.core.tween -import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.SnackbarHostState @@ -24,6 +22,8 @@ import com.tangem.features.send.impl.presentation.ui.amount.SendAmountContent import com.tangem.features.send.impl.presentation.ui.fee.SendSpeedAndFeeContent import com.tangem.features.send.impl.presentation.ui.recipient.SendRecipientContent import com.tangem.features.send.impl.presentation.ui.send.SendContent +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.* @Composable internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) { @@ -88,27 +88,46 @@ internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) ) } +@OptIn(ExperimentalAnimationApi::class) @Composable private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentScreen, modifier: Modifier = Modifier) { - var lastState by remember { mutableIntStateOf(currentState.type.ordinal) } - val direction = remember(currentState.type.ordinal) { - if (lastState < currentState.type.ordinal) { - AnimatedContentTransitionScope.SlideDirection.Start - } else { - AnimatedContentTransitionScope.SlideDirection.End - } + var currentStateProxy by remember { mutableStateOf(currentState) } + var isTransitionAnimationRunning by remember { mutableStateOf(false) } + + // Prevent quick screen changes to avoid some of the transition animation distortions + LaunchedEffect(currentState) { + snapshotFlow { isTransitionAnimationRunning } + .withIndex() + .map { (index, running) -> + if (running && index != 0) { + delay(timeMillis = 200) + } + running + } + .first { !it } + + currentStateProxy = currentState } + // Restrict pressing the back button while screen transition is running to avoid most of the animation distortions + BackHandler(enabled = isTransitionAnimationRunning) {} + // Box is needed to fix animation with resizing of AnimatedContent - Box(modifier = modifier) { + Box(modifier = modifier.fillMaxSize()) { AnimatedContent( - targetState = currentState, + targetState = currentStateProxy, label = "Send Scree Navigation", transitionSpec = { - lastState = currentState.type.ordinal + val direction = if (initialState.type.ordinal < targetState.type.ordinal) { + AnimatedContentTransitionScope.SlideDirection.Start + } else { + AnimatedContentTransitionScope.SlideDirection.End + } + slideIntoContainer(towards = direction, animationSpec = tween()) .togetherWith(slideOutOfContainer(towards = direction, animationSpec = tween())) }, ) { state -> + isTransitionAnimationRunning = transition.targetState != transition.currentState when (state.type) { SendUiStateType.Amount -> SendAmountContent( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index 6806468b2b..44b5a0244d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt @@ -21,6 +21,7 @@ internal fun SendAmountContent( clickIntents: SendClickIntents, ) { if (amountState == null) return + // Do not put fillMaxSize() in here LazyColumn( modifier = Modifier .padding( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt index 88f4a37a85..4b6919dd3e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt @@ -2,7 +2,6 @@ package com.tangem.features.send.impl.presentation.ui.fee import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -27,8 +26,7 @@ internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: S val isCustomSelected = feeSendState?.selectedFee == FeeType.Custom val hasNotifications = notifications.isNotEmpty() LazyColumn( - modifier = Modifier - .fillMaxSize() + modifier = Modifier // Do not put fillMaxSize() in here .background(TangemTheme.colors.background.tertiary) .padding( start = TangemTheme.dimens.spacing16, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index f728ff97cb..e37a0ced3d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -4,7 +4,6 @@ import androidx.annotation.StringRes import androidx.compose.animation.* import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -48,8 +47,7 @@ internal fun SendRecipientContent( val isValidating by remember(uiState.isValidating) { derivedStateOf { uiState.isValidating } } val isError by remember(address.isError) { derivedStateOf { address.isError } } LazyColumn( - modifier = Modifier - .fillMaxSize() + modifier = Modifier // Do not put fillMaxSize() in here .background(TangemTheme.colors.background.tertiary) .padding( start = TangemTheme.dimens.spacing16, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt index 78809756c4..6a6cc0550d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt @@ -4,7 +4,10 @@ import androidx.compose.animation.* import androidx.compose.animation.core.MutableTransitionState import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.material3.Icon @@ -33,9 +36,7 @@ private const val TAP_HELP_ANIMATION_DELAY = 500L internal fun SendContent(uiState: SendUiState) { val sendState = uiState.sendState ?: return LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = TangemTheme.dimens.spacing16), + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), ) { blocks(uiState) tapHelp(isDisplay = sendState.showTapHelp) 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 90348ad8d5..d1aed8da05 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 @@ -577,7 +577,7 @@ internal class SendViewModel @Inject constructor( } override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) { - viewModelScope.launch(dispatchers.main) { + viewModelScope.launch { if (!checkIfXrpAddressValue(value)) { uiState = stateFactory.onRecipientAddressValueChange(value) uiState = stateFactory.getOnRecipientAddressValidationStarted() @@ -590,7 +590,7 @@ internal class SendViewModel @Inject constructor( } override fun onRecipientMemoValueChange(value: String) { - viewModelScope.launch(dispatchers.main) { + viewModelScope.launch { if (!checkIfXrpAddressValue(value)) { uiState = stateFactory.getOnRecipientMemoValueChange(value) uiState = stateFactory.getOnRecipientAddressValidationStarted() From c5c28eca82f68563e49e8b2b796970c7ab609b25 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Apr 2024 10:57:38 +0100 Subject: [PATCH 36/85] Updated on 2026-08-14 --- .../tap/domain/tasks/product/ResetToFactorySettingsTask.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt index 09a32da708..f06f2f801a 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt @@ -33,7 +33,9 @@ class ResetToFactorySettingsTask( } private fun resetBackup(session: CardSession, callback: (result: CompletionResult) -> Unit) { - if (session.environment.card?.backupStatus == Card.BackupStatus.NoBackup) { + if (session.environment.card?.backupStatus == null || + session.environment.card?.backupStatus == Card.BackupStatus.NoBackup + ) { callback(CompletionResult.Success(session.environment.card!!)) return } From ddb167daf72a91f1ba1dddd0680a366d7b15ca4c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Apr 2024 15:08:37 +0300 Subject: [PATCH 37/85] Updated on 2026-08-14 --- .../tap/features/send/ui/SendFragment.kt | 40 ------------- .../tap/features/send/ui/SendViewModel.kt | 54 ++++++++++++++++++ .../DefaultQrScanningEventsRepository.kt | 9 +-- .../send/impl/presentation/SendFragment.kt | 31 ---------- .../presentation/viewmodel/SendViewModel.kt | 56 +++++++++++-------- 5 files changed, 88 insertions(+), 102 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 23484d916d..fd6d7105d3 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -17,7 +17,6 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import arrow.core.getOrElse import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.textfield.TextInputEditText import com.tangem.Message @@ -25,14 +24,11 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.qrscanning.models.QrResult import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase import com.tangem.domain.tokens.legacy.TradeCryptoAction -import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.feature.qrscanning.QrScanningRouter -import com.tangem.features.send.api.navigation.SendRouter.Companion.CRYPTO_CURRENCY_KEY import com.tangem.sdk.extensions.hideSoftKeyboard import com.tangem.tap.common.KeyboardObserver import com.tangem.tap.common.analytics.events.Token @@ -63,7 +59,6 @@ import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import timber.log.Timber import java.text.DecimalFormatSymbols import javax.inject.Inject @@ -87,9 +82,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { private val sendSubscriber = SendStateSubscriber(this) private lateinit var keyboardObserver: KeyboardObserver - private val cryptoCurrency: CryptoCurrency? - get() = arguments?.getParcelable(CRYPTO_CURRENCY_KEY) - val binding: FragmentSendBinding by viewBinding(FragmentSendBinding::bind) @Inject @@ -109,7 +101,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - subscribeToQrCodeScanner() subscribeToTransactionExtrasFields() subscribeToAddressField() subscribeToAmountField() @@ -200,22 +191,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { .launchIn(this) } - private fun CoroutineScope.subscribeToQrCodeScanner() { - listenToQrScanningUseCase(SourceType.SEND) - .getOrElse { emptyFlow() } - .onEach { rawQr -> - cryptoCurrency?.let { cryptoCurrency -> - parseQrCodeUseCase(rawQr, cryptoCurrency = cryptoCurrency).fold( - ifLeft = { - onCodeScanned(QrResult(address = rawQr)) - Timber.w(it) - }, - ifRight = { onCodeScanned(it) }, - ) - } ?: onCodeScanned(QrResult(address = rawQr)) - }.launchIn(this) - } - private fun CoroutineScope.subscribeToTransactionExtrasFields() = with(binding.lSendAddress) { // TODO: [REDACTED_TASK_KEY] etXlmMemo.inputtedTextAsFlow() @@ -282,21 +257,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { .launchIn(this@subscribeToTransactionExtrasFields) } - private fun onCodeScanned(parsedQr: QrResult) { - if (parsedQr.address.isEmpty()) return - - store.dispatch( - PasteAddress( - data = parsedQr.address, - sourceType = Token.Send.AddressEntered.SourceType.QRCode, - ), - ) - parsedQr.amount?.let { amount -> - store.dispatchOnMain(AmountAction.SetAmount(amount, isUserInput = false)) - } - store.dispatch(TruncateOrRestore(!binding.lSendAddress.etAddress.isFocused)) - } - private fun setupAmountLayout() { store.dispatch(SetMainCurrency(restoreMainCurrency())) store.dispatch(ReceiptAction.RefreshReceipt) diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt index de5e810b88..9f59533686 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt @@ -1,7 +1,11 @@ package com.tangem.tap.features.send.ui import androidx.lifecycle.* +import arrow.core.getOrElse import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase +import com.tangem.domain.qrscanning.models.SourceType +import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase +import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase import com.tangem.domain.tokens.FetchPendingTransactionsUseCase import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrency @@ -9,12 +13,16 @@ import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.features.send.api.navigation.SendRouter +import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.di.DelayedWork +import com.tangem.tap.features.send.redux.AddressActionUi import com.tangem.tap.features.send.redux.AmountAction import com.tangem.tap.proxy.AppStateHolder +import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -31,10 +39,19 @@ internal class SendViewModel @Inject constructor( private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase, + private val listenToQrScanningUseCase: ListenToQrScanningUseCase, + private val parseQrCodeUseCase: ParseQrCodeUseCase, @DelayedWork private val coroutineScope: CoroutineScope, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver { + init { + listenToQrScanningUseCase(SourceType.SEND) + .getOrElse { emptyFlow() } + .onEach(::onQRCodeScanned) + .launchIn(viewModelScope) + } + private val cryptoCurrency: CryptoCurrency? = savedStateHandle[SendRouter.CRYPTO_CURRENCY_KEY] override fun onCreate(owner: LifecycleOwner) { @@ -79,6 +96,43 @@ internal class SendViewModel @Inject constructor( ) } + private fun onQRCodeScanned(qrScanResult: String) { + if (cryptoCurrency != null) { + parseQrCodeUseCase(qrScanResult, cryptoCurrency).fold( + ifRight = { parsedCode -> + store.dispatch( + AddressActionUi.PasteAddress( + data = parsedCode.address, + sourceType = Token.Send.AddressEntered.SourceType.QRCode, + ), + ) + parsedCode.amount?.let { amount -> + store.dispatch(AmountAction.SetAmount(amount, isUserInput = false)) + } + // parsedCode.memo?.let { } + }, + ifLeft = { + store.dispatch( + AddressActionUi.PasteAddress( + data = qrScanResult, + sourceType = Token.Send.AddressEntered.SourceType.QRCode, + ), + ) + Timber.w(it) + }, + ) + } else { + store.dispatch( + AddressActionUi.PasteAddress( + data = qrScanResult, + sourceType = Token.Send.AddressEntered.SourceType.QRCode, + ), + ) + } + + store.dispatch(AddressActionUi.TruncateOrRestore(truncate = true)) + } + companion object { private const val UPDATE_BALANCE_DELAY_MILLIS = 11000L private const val TAG = "SendViewModel" diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt index c4677126a6..ea61431f29 100644 --- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt +++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt @@ -6,9 +6,7 @@ import com.tangem.domain.qrscanning.models.QrResult import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.repository.QrScanningEventsRepository import com.tangem.domain.tokens.model.CryptoCurrency -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* -import kotlinx.coroutines.yield import java.math.BigDecimal import java.net.URLDecoder @@ -16,20 +14,15 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository { private data class QrScanningEvent(val type: SourceType, val qrCode: String) - private val scannedEvents = MutableSharedFlow(replay = 1) + private val scannedEvents = MutableSharedFlow() override suspend fun emitResult(type: SourceType, qrCode: String) { scannedEvents.emit(QrScanningEvent(type, qrCode)) } - @OptIn(ExperimentalCoroutinesApi::class) override fun subscribeToScanningResults(type: SourceType) = scannedEvents .filter { it.type == type } .map { it.qrCode } - .onEach { - yield() // if we have more than one sub, we must allow them to collect emitted value - scannedEvents.resetReplayCache() - } override fun parseQrCode(qrCode: String, cryptoCurrency: CryptoCurrency): QrResult { val withoutSchema = stripSchema(qrCode, cryptoCurrency) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt index 64e8f1e992..da17befee3 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt @@ -4,27 +4,18 @@ import android.os.Bundle import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.flowWithLifecycle -import androidx.lifecycle.lifecycleScope -import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder -import com.tangem.domain.qrscanning.models.SourceType -import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.send.impl.navigation.InnerSendRouter import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.ui.SendScreen import com.tangem.features.send.impl.presentation.viewmodel.SendViewModel import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.launch import java.lang.ref.WeakReference import javax.inject.Inject @@ -40,9 +31,6 @@ internal class SendFragment : ComposeFragment() { @Inject lateinit var router: SendRouter - @Inject - lateinit var listenToQrScanningUseCase: ListenToQrScanningUseCase - @Inject lateinit var analyticsEventsHandler: AnalyticsEventHandler @@ -65,7 +53,6 @@ internal class SendFragment : ComposeFragment() { analyticsEventsHandler = analyticsEventsHandler, ), ) - listenToQrCode() } @Composable @@ -83,25 +70,7 @@ internal class SendFragment : ComposeFragment() { super.onDestroy() } - private fun listenToQrCode() { - lifecycleScope.launch { - listenToQrScanningUseCase(SourceType.SEND) - .getOrElse { emptyFlow() } - .flowWithLifecycle(this@SendFragment.lifecycle, minActiveState = Lifecycle.State.CREATED) - .collect { - delay(QR_SCAN_DELAY) - - // Delayed launch is needed in order for the UI to be drawn and to process the sent events. - // If do not use the delay, then error field is not displayed when - // inserting an incorrect amount by shareUri - viewModel.onQrCodeScanned(it) - } - } - } - companion object { - private const val QR_SCAN_DELAY = 200L - /** Create send fragment instance */ fun create(): SendFragment = SendFragment() } 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 d1aed8da05..4fd4121ab7 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 @@ -15,6 +15,8 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.qrscanning.models.SourceType +import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase import com.tangem.domain.redux.LegacyAction import com.tangem.domain.redux.ReduxStateHolder @@ -92,6 +94,7 @@ internal class SendViewModel @Inject constructor( private val isSendTapHelpEnabledUseCase: IsSendTapHelpEnabledUseCase, private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, + private val listenToQrScanningUseCase: ListenToQrScanningUseCase, currencyChecksRepository: CurrencyChecksRepository, isFeeApproximateUseCase: IsFeeApproximateUseCase, validateWalletMemoUseCase: ValidateWalletMemoUseCase, @@ -189,11 +192,11 @@ internal class SendViewModel @Inject constructor( private var memoValidationJobHolder = JobHolder() private var sendNotificationsJobHolder = JobHolder() private var feeNotificationsJobHolder = JobHolder() - private var qrScannerJobHolder = JobHolder() private var sendIdleTimer = 0L init { + subscribeOnQRScannerResult() subscribeOnCurrencyStatusUpdates() subscribeOnBalanceHidden() getTapHelpPreviewAvailability() @@ -215,6 +218,13 @@ internal class SendViewModel @Inject constructor( this.stateRouter = stateRouter } + private fun subscribeOnQRScannerResult() { + listenToQrScanningUseCase(SourceType.SEND) + .getOrElse { emptyFlow() } + .onEach(::onQrCodeScanned) + .launchIn(viewModelScope) + } + private fun subscribeOnCurrencyStatusUpdates() { viewModelScope.launch(dispatchers.main) { getUserWalletUseCase(userWalletId).fold( @@ -536,6 +546,23 @@ internal class SendViewModel @Inject constructor( feeJobHolder.cancel() } } + + private fun onQrCodeScanned(address: String) { + parseQrCodeUseCase(address, cryptoCurrency).fold( + ifRight = { parsedCode -> + onRecipientAddressValueChange(parsedCode.address, EnterAddressSource.QRCode) + parsedCode.amount?.let { + onAmountValueChange(it.parseBigDecimal(decimals = cryptoCurrency.decimals)) + } + parsedCode.memo?.let { onRecipientMemoValueChange(it) } + }, + ifLeft = { + onRecipientAddressValueChange(address, EnterAddressSource.QRCode) + Timber.w(it) + }, + ) + } + // endregion // region amount state clicks @@ -555,26 +582,9 @@ internal class SendViewModel @Inject constructor( override fun onAmountPasteTriggerDismiss() { uiState = amountStateFactory.getOnAmountPastedTriggerDismiss() } - // endregion +// endregion - // region recipient state clicks - fun onQrCodeScanned(address: String) { - viewModelScope.launch(dispatchers.main) { - parseQrCodeUseCase(address, cryptoCurrency).fold( - ifRight = { parsedCode -> - onRecipientAddressValueChange(parsedCode.address, EnterAddressSource.QRCode) - parsedCode.amount?.let { - onAmountValueChange(it.parseBigDecimal(decimals = cryptoCurrency.decimals)) - } - parsedCode.memo?.let { onRecipientMemoValueChange(it) } - }, - ifLeft = { - onRecipientAddressValueChange(address, EnterAddressSource.QRCode) - Timber.w(it) - }, - ) - }.saveIn(qrScannerJobHolder) - } +// region recipient state clicks override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) { viewModelScope.launch { @@ -633,7 +643,7 @@ internal class SendViewModel @Inject constructor( val isRecent = type == EnterAddressSource.RecentAddress if (isRecent && isValidAddress) onNextClick() } - // endregion +// endregion // region fee override fun feeReload() = loadFee() @@ -709,7 +719,7 @@ internal class SendViewModel @Inject constructor( cryptoCurrency = cryptoCurrency, ) } - // endregion +// endregion // region send state clicks override fun onSendClick() { @@ -894,7 +904,7 @@ internal class SendViewModel @Inject constructor( } uiState = stateFactory.getHiddenTapHelpState() } - // endregion +// endregion private companion object { const val CHECK_FEE_UPDATE_DELAY = 60_000L From 9d281dbaf6c0943c2acf4520c15d1e5924feea81 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Apr 2024 13:15:31 +0100 Subject: [PATCH 38/85] Updated on 2026-08-14 --- .../java/com/tangem/domain/common/extensions/Blockchain.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 36caa85c7f..dc608f1ae4 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -331,4 +331,8 @@ private val excludedBlockchains = listOf( Blockchain.Nexa, Blockchain.NexaTestnet, Blockchain.Radiant, + Blockchain.Manta, + Blockchain.MantaTestnet, + Blockchain.Mantle, + Blockchain.MantleTestnet, ) \ No newline at end of file From 85703c82236162b6d7c6f9ec045991edced7ac20 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Apr 2024 11:29:24 +0100 Subject: [PATCH 39/85] Updated on 2026-08-14 --- .../state/transformers/converter/TxHistoryItemStateConverter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt index d3fd7a6127..d31a165d9a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt @@ -35,7 +35,7 @@ internal class TxHistoryItemStateConverter( title = item.extractTitle(), subtitle = item.extractSubtitle(), timestamp = item.timestampInMillis, - onClick = { clickIntents.onVisaTransactionClick(item.txHash) }, + onClick = { clickIntents.onTransactionClick(item.txHash) }, ) } From 8b15662f33e9f952480b141ce5d51b3068178cdd Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Apr 2024 14:44:37 +0500 Subject: [PATCH 40/85] Updated on 2026-08-14 --- .../features/send/impl/presentation/viewmodel/SendViewModel.kt | 1 + 1 file changed, 1 insertion(+) 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 4fd4121ab7..1eba5568fa 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 @@ -680,6 +680,7 @@ internal class SendViewModel @Inject constructor( val result = callFeeUseCase()?.fold( ifRight = { uiState = feeStateFactory.onFeeOnLoadedState(it) + sendIdleTimer = SystemClock.elapsedRealtime() }, ifLeft = { onFeeLoadFailed(isShowStatus) From 54d38c6bf01fe55bde64f7046de0fcf1041584c8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Apr 2024 14:45:13 +0500 Subject: [PATCH 41/85] Updated on 2026-08-14 --- .../impl/presentation/state/SendStateFactory.kt | 13 ++++++++++--- .../send/impl/presentation/state/SendUiState.kt | 2 +- .../state/confirm/SendConfirmStateConverter.kt | 2 +- .../presentation/state/fee/FeeStateFactory.kt | 9 +++++++++ .../presentation/viewmodel/SendViewModel.kt | 17 ++++++++--------- gradle/dependencies.toml | 2 +- 6 files changed, 30 insertions(+), 15 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 b31e9559be..c8d7369fbd 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 @@ -15,6 +15,7 @@ import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter +import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientHistoryListConverter @@ -263,7 +264,7 @@ internal class SendStateFactory( return state.copy( sendState = state.sendState?.copy( isSending = isSending, - isPrimaryButtonEnabled = !isSending, + isPrimaryButtonEnabled = isPrimaryButtonEnabled(state, state.sendState.notifications), ), ) } @@ -285,10 +286,9 @@ internal class SendStateFactory( fun getSendNotificationState(notifications: ImmutableList): SendUiState { val state = currentStateProvider() val sendState = state.sendState ?: return state - val hasErrorNotifications = notifications.any { it is SendNotification.Error } return state.copy( sendState = sendState.copy( - isPrimaryButtonEnabled = !hasErrorNotifications, + isPrimaryButtonEnabled = isPrimaryButtonEnabled(state, notifications), notifications = notifications, showTapHelp = sendState.showTapHelp && notifications.isEmpty(), ), @@ -302,5 +302,12 @@ internal class SendStateFactory( sendState = sendState.copy(showTapHelp = false), ) } + + private fun isPrimaryButtonEnabled(state: SendUiState, notifications: ImmutableList): Boolean { + val sendState = state.sendState ?: return false + val feeState = state.feeState ?: return false + val hasErrorNotifications = notifications.any { it is SendNotification.Error } + return !hasErrorNotifications && !sendState.isSending && feeState.feeSelectorState is FeeSelectorState.Content + } //endregion } \ No newline at end of file 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 2b14283039..043f45837f 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 @@ -84,7 +84,7 @@ internal sealed class SendStates { @Stable data class SendState( override val type: SendUiStateType = SendUiStateType.Send, - override val isPrimaryButtonEnabled: Boolean = true, + override val isPrimaryButtonEnabled: Boolean = false, val isSending: Boolean, val isSuccess: Boolean, val transactionDate: Long, 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 e202bf5e25..7935da04c8 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 @@ -10,7 +10,7 @@ internal class SendConfirmStateConverter( ) : Converter { override fun convert(value: Unit): SendStates.SendState { return SendStates.SendState( - isPrimaryButtonEnabled = true, + isPrimaryButtonEnabled = false, isSending = false, isSuccess = false, transactionDate = 0L, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index a74613acd3..b88d51c2a9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -45,6 +45,9 @@ internal class FeeStateFactory( val state = currentStateProvider() val feeState = state.feeState ?: return state return state.copy( + sendState = state.sendState?.copy( + isPrimaryButtonEnabled = false, + ), feeState = feeState.copy( feeSelectorState = if (feeState.feeSelectorState is FeeSelectorState.Content) { feeState.feeSelectorState @@ -77,6 +80,9 @@ internal class FeeStateFactory( val fee = feeConverter.convert(updatedFeeSelectorState) return state.copy( + sendState = state.sendState?.copy( + isPrimaryButtonEnabled = true, + ), feeState = feeState.copy( feeSelectorState = updatedFeeSelectorState, fee = fee, @@ -91,6 +97,9 @@ internal class FeeStateFactory( feeState = state.feeState?.copy( feeSelectorState = FeeSelectorState.Error, ), + sendState = state.sendState?.copy( + isPrimaryButtonEnabled = false, + ), ) } 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 1eba5568fa..94620f127c 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 @@ -692,9 +692,6 @@ internal class SendViewModel @Inject constructor( updateNotifications() updateFeeNotifications() }.saveIn(feeJobHolder) - .invokeOnCompletion { - // todo - } } private fun onFeeLoadFailed(isShowStatus: Boolean) { @@ -713,12 +710,14 @@ internal class SendViewModel @Inject constructor( val recipientState = uiState.recipientState ?: return null val amount = amountState.amountTextField.cryptoAmount.value ?: return null - return getFeeUseCase.invoke( - amount = amount, - destination = recipientState.addressTextField.value, - userWallet = userWallet, - cryptoCurrency = cryptoCurrency, - ) + return feeCryptoCurrencyStatus?.let { feeCurrencyStatus -> + getFeeUseCase.invoke( + amount = amount, + destination = recipientState.addressTextField.value, + userWallet = userWallet, + cryptoCurrency = feeCurrencyStatus.currency, + ) + } } // endregion diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index eec9a20c7b..603776f9f0 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-605" +tangemBlockchainSdk = "release-app_5.9-608" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From e5c732e19dbb57a596f0ec479d274f7f3d8db3ec Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Apr 2024 19:39:32 +0300 Subject: [PATCH 42/85] Updated on 2026-08-14 --- .../impl/data/TangemApiTokensPagingSource.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt index acf336dac3..f036004238 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt @@ -5,6 +5,7 @@ import androidx.paging.PagingState import com.tangem.blockchain.common.Blockchain import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.domain.common.extensions.isSupportedInApp import com.tangem.domain.common.extensions.supportedBlockchains import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.common.util.cardTypesResolver @@ -21,16 +22,25 @@ import com.tangem.utils.coroutines.runCatching * @property dispatchers coroutine dispatchers provider * @property getSelectedWalletSyncUseCase use case that returns selected wallet * @property searchText search text + * @property needFilterExcluded filter networks that are not supported in the app */ internal class TangemApiTokensPagingSource( private val api: TangemTechApi, private val dispatchers: CoroutineDispatcherProvider, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val searchText: String?, - needFilterExcluded: Boolean = false, + private val needFilterExcluded: Boolean = false, ) : PagingSource() { private val coinsResponseConverter = CoinsResponseConverter(needFilterExcluded) + + private val allAvailableBlockchains by lazy { + Blockchain.entries + .filter { + it.isTestnet().not() && (needFilterExcluded.not() || it.isSupportedInApp()) + } + } + override fun getRefreshKey(state: PagingState): Int? { return state.anchorPosition?.let { anchorPosition -> state.closestPageToPosition(anchorPosition)?.prevKey?.plus(other = 1) @@ -43,7 +53,7 @@ internal class TangemApiTokensPagingSource( return runCatching(dispatchers.io) { val supportedBlockchains = getSelectedWalletSyncUseCase().fold( - ifLeft = { Blockchain.entries }, + ifLeft = { allAvailableBlockchains }, ifRight = { it.scanResponse.card.supportedBlockchains(it.scanResponse.cardTypesResolver) }, ) From adaa72f359d491ea0e7c659bdffa5178be6f8c64 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 14:12:17 +0500 Subject: [PATCH 43/85] Updated on 2026-08-14 --- .../impl/presentation/state/SendUiState.kt | 56 +++++++++- .../impl/presentation/state/StateRouter.kt | 47 ++++++-- .../state/common/SendSyncEditConverter.kt | 26 +++++ .../state/previewdata/SendClickIntentsStub.kt | 2 +- .../send/impl/presentation/ui/SendScreen.kt | 103 +++++++++++------- .../viewmodel/SendClickIntents.kt | 2 +- 6 files changed, 184 insertions(+), 52 deletions(-) create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/common/SendSyncEditConverter.kt 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 043f45837f..23453c4b4d 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 @@ -28,9 +28,58 @@ internal data class SendUiState( val recipientState: SendStates.RecipientState? = null, val feeState: SendStates.FeeState? = null, val sendState: SendStates.SendState? = null, + val editAmountState: SendStates.AmountState? = null, + val editRecipientState: SendStates.RecipientState? = null, + val editFeeState: SendStates.FeeState? = null, val isBalanceHidden: Boolean, val event: StateEvent, -) +) { + + fun getAmountState(isEditState: Boolean): SendStates.AmountState? { + return if (isEditState) { + editAmountState + } else { + amountState + } + } + + fun getRecipientState(isEditState: Boolean): SendStates.RecipientState? { + return if (isEditState) { + editRecipientState + } else { + recipientState + } + } + + fun getFeeState(isEditState: Boolean): SendStates.FeeState? { + return if (isEditState) { + editFeeState + } else { + feeState + } + } + + fun copyWrapped( + isEditState: Boolean, + amountState: SendStates.AmountState? = this.amountState, + feeState: SendStates.FeeState? = this.feeState, + recipientState: SendStates.RecipientState? = this.recipientState, + sendState: SendStates.SendState? = this.sendState, + ): SendUiState = if (isEditState) { + copy( + editAmountState = amountState, + editFeeState = feeState, + editRecipientState = recipientState, + ) + } else { + copy( + amountState = amountState, + feeState = feeState, + recipientState = recipientState, + sendState = sendState, + ) + } +} @Stable internal sealed class SendStates { @@ -105,6 +154,9 @@ enum class SendUiStateType { None, Recipient, Amount, - Send, Fee, + Send, + EditAmount, + EditRecipient, + EditFee, } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt index 079dbfdb6f..5f2201052b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt @@ -18,6 +18,9 @@ internal class StateRouter( val currentState: StateFlow get() = mutableCurrentState + val isEditState: Boolean + get() = currentState.value.isFromConfirmation + fun clear() { mutableCurrentState.update { getInitState() } } @@ -35,21 +38,29 @@ internal class StateRouter( else -> popBackStack() } else -> when (type) { - SendUiStateType.Amount -> continueToSend(::showRecipient) + SendUiStateType.Amount -> showRecipient() SendUiStateType.Fee -> showSend() - SendUiStateType.Send -> continueToSend(::showAmount) - else -> continueToSend(::popBackStack) + SendUiStateType.Send -> showAmount() + SendUiStateType.Recipient -> popBackStack() + SendUiStateType.EditAmount -> showSend() + SendUiStateType.EditRecipient -> showSend() + SendUiStateType.EditFee -> showSend() + else -> popBackStack() } } } fun onNextClick() { when (currentState.value.type) { - SendUiStateType.Recipient -> continueToSend(::showAmount) + SendUiStateType.Recipient -> showAmount() SendUiStateType.Amount, SendUiStateType.Fee, + SendUiStateType.EditAmount, + SendUiStateType.EditRecipient, + SendUiStateType.EditFee, -> showSend() SendUiStateType.Send -> onBackClick() + else -> popBackStack() } } @@ -67,17 +78,35 @@ internal class StateRouter( fun showAmount(isFromConfirmation: Boolean = false) { analyticsEventsHandler.send(SendAnalyticEvents.AmountScreenOpened) - mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Amount, isFromConfirmation) } + mutableCurrentState.update { + if (isFromConfirmation) { + SendUiCurrentScreen(SendUiStateType.EditAmount, true) + } else { + SendUiCurrentScreen(SendUiStateType.Amount, false) + } + } } fun showRecipient(isFromConfirmation: Boolean = false) { analyticsEventsHandler.send(SendAnalyticEvents.AddressScreenOpened) - mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Recipient, isFromConfirmation) } + mutableCurrentState.update { + if (isFromConfirmation) { + SendUiCurrentScreen(SendUiStateType.EditRecipient, true) + } else { + SendUiCurrentScreen(SendUiStateType.Recipient, false) + } + } } fun showFee(isFromConfirmation: Boolean = false) { analyticsEventsHandler.send(SendAnalyticEvents.FeeScreenOpened) - mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Fee, isFromConfirmation) } + mutableCurrentState.update { + if (isFromConfirmation) { + SendUiCurrentScreen(SendUiStateType.EditFee, true) + } else { + SendUiCurrentScreen(SendUiStateType.Fee, false) + } + } } fun showSend() { @@ -85,10 +114,6 @@ internal class StateRouter( mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Send, isFromConfirmation = false) } } - private fun continueToSend(show: () -> Unit) { - if (currentState.value.isFromConfirmation) showSend() else show() - } - private fun getInitState() = if (isEditingDisabled) { SendUiCurrentScreen( type = SendUiStateType.None, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/common/SendSyncEditConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/common/SendSyncEditConverter.kt new file mode 100644 index 0000000000..f0abbba036 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/common/SendSyncEditConverter.kt @@ -0,0 +1,26 @@ +package com.tangem.features.send.impl.presentation.state.common + +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.utils.Provider +import com.tangem.utils.converter.Converter + +internal class SendSyncEditConverter( + private val currentStateProvider: Provider, +) : Converter { + override fun convert(value: Boolean): SendUiState { + val state = currentStateProvider() + return if (value) { + state.copy( + amountState = state.editAmountState, + feeState = state.editFeeState, + recipientState = state.editRecipientState, + ) + } else { + state.copy( + editAmountState = state.amountState, + editRecipientState = state.recipientState, + editFeeState = state.feeState, + ) + } + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 5235588e51..0a113ea474 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -14,7 +14,7 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onBackClick() {} - override fun onNextClick() {} + override fun onNextClick(isFromEdit: Boolean) {} override fun onPrevClick() {} diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index ffa3bf5d06..b289867885 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -1,8 +1,11 @@ package com.tangem.features.send.impl.presentation.ui import androidx.activity.compose.BackHandler -import androidx.compose.animation.* +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.tween +import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.SnackbarHostState @@ -23,12 +26,13 @@ import com.tangem.features.send.impl.presentation.ui.fee.SendSpeedAndFeeContent import com.tangem.features.send.impl.presentation.ui.recipient.SendRecipientContent import com.tangem.features.send.impl.presentation.ui.send.SendContent import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.withIndex @Composable internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) { val snackbarHostState = remember { SnackbarHostState() } - val sendState = uiState.sendState ?: return BackHandler { uiState.clickIntents.onBackClick() } Column( modifier = Modifier @@ -38,38 +42,9 @@ internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) .background(color = TangemTheme.colors.background.tertiary), horizontalAlignment = Alignment.CenterHorizontally, ) { - val titleRes = when (currentState.type) { - SendUiStateType.Amount -> resourceReference(R.string.send_amount_label) - SendUiStateType.Recipient -> resourceReference(R.string.send_recipient_label) - SendUiStateType.Fee -> resourceReference(R.string.common_fee_selector_title) - SendUiStateType.Send -> if (!sendState.isSuccess) { - resourceReference(R.string.send_summary_title, wrappedList(uiState.cryptoCurrencyName)) - } else { - null - } - else -> null - } - val isSending = currentState.type == SendUiStateType.Send && !uiState.sendState.isSuccess - val subtitleRes = if (isSending) { - uiState.amountState?.walletName - } else { - null - } - val iconRes = if (currentState.type == SendUiStateType.Recipient) { - R.drawable.ic_qrcode_scan_24 - } else { - null - } - - AppBarWithBackButtonAndIcon( - text = titleRes?.resolveReference(), - subtitle = subtitleRes, - onBackClick = uiState.clickIntents::popBackStack, - onIconClick = uiState.clickIntents::onQrCodeScanClick, - backIconRes = R.drawable.ic_close_24, - iconRes = iconRes, - backgroundColor = TangemTheme.colors.background.tertiary, - modifier = Modifier.height(TangemTheme.dimens.size56), + SendAppBar( + uiState = uiState, + currentState = currentState, ) SendScreenContent( uiState = uiState, @@ -88,6 +63,50 @@ internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) ) } +@Composable +private fun SendAppBar(uiState: SendUiState, currentState: SendUiCurrentScreen) { + val (titleRes, subtitleRes) = when (currentState.type) { + SendUiStateType.Amount, + SendUiStateType.EditAmount, + -> resourceReference(R.string.send_amount_label) to null + SendUiStateType.Recipient, + SendUiStateType.EditRecipient, + -> resourceReference(R.string.send_recipient_label) to null + SendUiStateType.Fee, + SendUiStateType.EditFee, + -> resourceReference(R.string.common_fee_selector_title) to null + SendUiStateType.Send -> if (uiState.sendState?.isSuccess == false) { + resourceReference(R.string.send_summary_title, wrappedList(uiState.cryptoCurrencyName)) to + uiState.amountState?.walletName + } else { + null to null + } + else -> null to null + } + val iconRes = if (currentState.type == SendUiStateType.Recipient) { + R.drawable.ic_qrcode_scan_24 + } else { + null + } + val (backIcon, backClick) = when (currentState.type) { + SendUiStateType.EditAmount, + SendUiStateType.EditFee, + SendUiStateType.EditRecipient, + -> R.drawable.ic_back_24 to uiState.clickIntents::onBackClick + else -> R.drawable.ic_close_24 to uiState.clickIntents::popBackStack + } + AppBarWithBackButtonAndIcon( + text = titleRes?.resolveReference(), + subtitle = subtitleRes, + onBackClick = backClick, + onIconClick = uiState.clickIntents::onQrCodeScanClick, + backIconRes = backIcon, + iconRes = iconRes, + backgroundColor = TangemTheme.colors.background.tertiary, + modifier = Modifier.height(TangemTheme.dimens.size56), + ) +} + @OptIn(ExperimentalAnimationApi::class) @Composable private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentScreen, modifier: Modifier = Modifier) { @@ -135,13 +154,23 @@ private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentS isBalanceHiding = uiState.isBalanceHidden, clickIntents = uiState.clickIntents, ) + SendUiStateType.EditAmount -> SendAmountContent( + amountState = uiState.editAmountState, + isBalanceHiding = uiState.isBalanceHidden, + clickIntents = uiState.clickIntents, + ) SendUiStateType.Recipient -> SendRecipientContent( uiState = uiState.recipientState, clickIntents = uiState.clickIntents, isBalanceHidden = uiState.isBalanceHidden, ) - SendUiStateType.Fee -> SendSpeedAndFeeContent( - state = uiState.feeState, + SendUiStateType.EditRecipient -> SendRecipientContent( + uiState = uiState.editRecipientState, + clickIntents = uiState.clickIntents, + isBalanceHidden = uiState.isBalanceHidden, + ) + SendUiStateType.EditFee -> SendSpeedAndFeeContent( + state = uiState.editFeeState, clickIntents = uiState.clickIntents, ) SendUiStateType.Send -> SendContent(uiState) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index a353059047..f0715cd50a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -14,7 +14,7 @@ internal interface SendClickIntents { fun onBackClick() - fun onNextClick() + fun onNextClick(isFromEdit: Boolean = false) fun onPrevClick() From 94623dd8eb529e89b54a17ec581e2cb337af5473 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 14:12:43 +0500 Subject: [PATCH 44/85] Updated on 2026-08-14 --- .../utils/SendOnNextScreenAnalyticSender.kt | 10 ++-- .../state/SendEventStateFactory.kt | 4 +- .../presentation/state/SendStateFactory.kt | 52 +++++++++++++------ .../state/amount/AmountStateFactory.kt | 6 +++ .../amount/SendAmountCurrencyConverter.kt | 8 ++- ...SendAmountPastedTriggerDismissConverter.kt | 8 ++- .../state/confirm/SendNotificationFactory.kt | 9 ++-- .../presentation/state/fee/FeeCalculation.kt | 4 +- .../state/fee/FeeNotificationFactory.kt | 9 ++-- .../presentation/state/fee/FeeStateFactory.kt | 44 ++++++++++------ .../fields/SendAmountFieldChangeConverter.kt | 14 +++-- .../SendAmountFieldMaxAmountConverter.kt | 8 ++- .../presentation/ui/SendNavigationButtons.kt | 45 ++++++++++------ .../presentation/viewmodel/SendViewModel.kt | 41 ++++++++++----- 14 files changed, 176 insertions(+), 86 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt index 6ca1759174..6fefbc87b8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt @@ -5,18 +5,21 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.features.send.impl.presentation.analytics.SelectedCurrencyType import com.tangem.features.send.impl.presentation.analytics.SelectedFeeType import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents +import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.SendUiStateType +import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType +import com.tangem.utils.Provider internal class SendOnNextScreenAnalyticSender( + private val stateRouterProvider: Provider, private val analyticsEventHandler: AnalyticsEventHandler, ) { fun send(prevScreen: SendUiStateType, state: SendUiState) { when (prevScreen) { SendUiStateType.Fee -> { - val feeState = state.feeState ?: return + val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content feeSelectorState?.selectedFee?.let { selectedFee -> val isCustomFeeEdited = feeState.fee?.amount?.value != feeSelectorState.fees.normal.amount.value @@ -27,7 +30,8 @@ internal class SendOnNextScreenAnalyticSender( } } SendUiStateType.Amount -> { - val isFiatSelected = state.amountState?.amountTextField?.isFiatValue ?: return + val amountState = state.getAmountState(stateRouterProvider().isEditState) ?: return + val isFiatSelected = amountState.amountTextField.isFiatValue val selectedCurrency = if (!isFiatSelected) { SelectedCurrencyType.Token } else { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt index 616fad69f8..852339a7ed 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt @@ -20,6 +20,7 @@ import java.math.BigDecimal * @param feeStateFactory [FeeStateFactory] */ internal class SendEventStateFactory( + private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, private val clickIntents: SendClickIntents, @@ -50,7 +51,8 @@ internal class SendEventStateFactory( fun getFeeUpdatedAlert(fee: TransactionFee, onConsume: () -> Unit, onFeeNotIncreased: () -> Unit): SendUiState { val state = currentStateProvider() - val feeSelector = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return state + val feeState = state.getFeeState(stateRouterProvider().isEditState) + val feeSelector = feeState?.feeSelectorState as? FeeSelectorState.Content ?: return state val newFee = when (fee) { is TransactionFee.Single -> fee.normal is TransactionFee.Choosable -> { 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 c8d7369fbd..a2b722b614 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 @@ -14,6 +14,7 @@ import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter +import com.tangem.features.send.impl.presentation.state.common.SendSyncEditConverter import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter @@ -31,6 +32,7 @@ import timber.log.Timber @Suppress("LongParameterList") internal class SendStateFactory( private val clickIntents: SendClickIntents, + private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val userWalletProvider: Provider, private val appCurrencyProvider: Provider, @@ -82,7 +84,9 @@ internal class SendStateFactory( cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } - + private val sendSyncEditConverter by lazy(LazyThreadSafetyMode.NONE) { + SendSyncEditConverter(currentStateProvider = currentStateProvider) + } // region UI states fun getInitialState(): SendUiState = SendUiState( clickIntents = clickIntents, @@ -117,6 +121,8 @@ internal class SendStateFactory( ) } + fun syncEditStates(isFromEdit: Boolean) = sendSyncEditConverter.convert(isFromEdit) + fun getOnHideBalanceState(isBalanceHidden: Boolean): SendUiState { return currentStateProvider().copy(isBalanceHidden = isBalanceHidden) } @@ -143,8 +149,10 @@ internal class SendStateFactory( fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false): SendUiState { val state = currentStateProvider() - val recipientState = state.recipientState ?: return state - return state.copy( + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, recipientState = recipientState.copy( addressTextField = recipientState.addressTextField.copy(value = value), memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress), @@ -155,7 +163,8 @@ internal class SendStateFactory( fun getOnRecipientAddressValidState(value: String, isValidAddress: Boolean): SendUiState { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val state = currentStateProvider() - val recipientState = state.recipientState ?: return state + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state val isValidMemo = validateWalletMemoUseCase( memo = recipientState.memoTextField?.value.orEmpty(), @@ -167,7 +176,8 @@ internal class SendStateFactory( val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses ?.any { it.value == value } ?: true - return state.copy( + return state.copyWrapped( + isEditState = isEditState, recipientState = recipientState.copy( isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet, isValidating = false, @@ -185,16 +195,20 @@ internal class SendStateFactory( fun getOnRecipientAddressValidationStarted(): SendUiState { val state = currentStateProvider() - val recipientState = state.recipientState ?: return state - return state.copy( + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, recipientState = recipientState.copy(isValidating = true), ) } fun getOnRecipientMemoValueChange(value: String): SendUiState { val state = currentStateProvider() - val recipientState = state.recipientState ?: return state - return state.copy( + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, recipientState = recipientState.copy( memoTextField = recipientState.memoTextField?.copy(value = value), ), @@ -204,7 +218,8 @@ internal class SendStateFactory( fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val state = currentStateProvider() - val recipientState = state.recipientState ?: return state + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state val isValidMemo = validateWalletMemoUseCase( memo = value, @@ -216,7 +231,8 @@ internal class SendStateFactory( val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses ?.any { it.value == value } ?: true - return state.copy( + return state.copyWrapped( + isEditState = isEditState, recipientState = recipientState.copy( isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet, isValidating = false, @@ -230,8 +246,10 @@ internal class SendStateFactory( fun getOnXAddressMemoState(): SendUiState { val state = currentStateProvider() - val recipientState = state.recipientState ?: return state - return state.copy( + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, recipientState = recipientState.copy( memoTextField = recipientState.memoTextField?.copy( value = "", @@ -243,9 +261,11 @@ internal class SendStateFactory( fun getHiddenRecentListState(isAddressInWallet: Boolean, isValidAddress: Boolean): SendUiState { val state = currentStateProvider() - val recipientState = state.recipientState ?: return state + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state val isNotValid = isAddressInWallet || !isValidAddress - return state.copy( + return state.copyWrapped( + isEditState = isEditState, recipientState = recipientState.copy( recent = recipientState.recent.map { recent -> recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY)) @@ -305,7 +325,7 @@ internal class SendStateFactory( private fun isPrimaryButtonEnabled(state: SendUiState, notifications: ImmutableList): Boolean { val sendState = state.sendState ?: return false - val feeState = state.feeState ?: return false + val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return false val hasErrorNotifications = notifications.any { it is SendNotification.Error } return !hasErrorNotifications && !sendState.isSending && feeState.feeSelectorState is FeeSelectorState.Content } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt index 3321568099..e91f30356f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt @@ -2,6 +2,7 @@ package com.tangem.features.send.impl.presentation.state.amount import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldMaxAmountConverter import com.tangem.utils.Provider @@ -10,18 +11,21 @@ import com.tangem.utils.Provider * Factory to produce amount state for [SendUiState] */ internal class AmountStateFactory( + private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, ) { private val amountFieldChangeConverter by lazy(LazyThreadSafetyMode.NONE) { SendAmountFieldChangeConverter( + stateRouterProvider = stateRouterProvider, currentStateProvider = currentStateProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } private val amountFieldMaxAmountConverter by lazy(LazyThreadSafetyMode.NONE) { SendAmountFieldMaxAmountConverter( + stateRouterProvider = stateRouterProvider, currentStateProvider = currentStateProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) @@ -29,12 +33,14 @@ internal class AmountStateFactory( private val amountCurrencyConverter by lazy(LazyThreadSafetyMode.NONE) { SendAmountCurrencyConverter( + stateRouterProvider = stateRouterProvider, currentStateProvider = currentStateProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } private val amountPasteConverter by lazy(LazyThreadSafetyMode.NONE) { SendAmountPastedTriggerDismissConverter( + stateRouterProvider = stateRouterProvider, currentStateProvider = currentStateProvider, ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt index f6ff524e9f..4817557ac6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt @@ -2,17 +2,20 @@ package com.tangem.features.send.impl.presentation.state.amount import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import com.tangem.utils.isNullOrZero internal class SendAmountCurrencyConverter( + private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, ) : Converter { override fun convert(value: Boolean): SendUiState { val state = currentStateProvider() - val amountState = state.amountState ?: return state + val isEditState = stateRouterProvider().isEditState + val amountState = state.getAmountState(isEditState) ?: return state val amountTextField = amountState.amountTextField val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() @@ -20,7 +23,8 @@ internal class SendAmountCurrencyConverter( return if (amountTextField.isFiatValue == value && !isValidFiatRate) { state } else { - return state.copy( + return state.copyWrapped( + isEditState = isEditState, amountState = amountState.copy( amountTextField = amountTextField.copy( isFiatValue = value, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountPastedTriggerDismissConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountPastedTriggerDismissConverter.kt index 497629bdd5..093b1b6a8e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountPastedTriggerDismissConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountPastedTriggerDismissConverter.kt @@ -1,16 +1,20 @@ package com.tangem.features.send.impl.presentation.state.amount import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.utils.Provider import com.tangem.utils.converter.Converter internal class SendAmountPastedTriggerDismissConverter( + private val stateRouterProvider: Provider, private val currentStateProvider: Provider, ) : Converter { override fun convert(value: Boolean): SendUiState { val state = currentStateProvider() - val amountState = state.amountState ?: return state - return state.copy( + val isEditState = stateRouterProvider().isEditState + val amountState = state.getAmountState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, amountState = amountState.copy( amountTextField = amountState.amountTextField.copy( isValuePasted = false, 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 3b01354982..cd1e28de72 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 @@ -49,10 +49,13 @@ internal class SendNotificationFactory( .filter { it.type == SendUiStateType.Send } .map { val state = currentStateProvider() - val sendState = state.sendState ?: return@map persistentListOf() - val feeState = state.feeState ?: return@map persistentListOf() + val isEditState = stateRouterProvider().isEditState val balance = cryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO - val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO + val sendState = state.sendState ?: return@map persistentListOf() + val feeState = state.getFeeState(isEditState) ?: return@map persistentListOf() + val amountState = state.getAmountState(isEditState) ?: return@map persistentListOf() + + val amountValue = amountState.amountTextField.cryptoAmount.value ?: BigDecimal.ZERO val feeValue = feeState.fee?.amount?.value ?: BigDecimal.ZERO val isFeeCoverage = checkFeeCoverage( isSubtractAvailable = isSubtractAvailableProvider(), 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 8ec4bfd4d6..43e7da9e42 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 @@ -6,7 +6,6 @@ import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.common.extensions.minimalAmount import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.lib.crypto.BlockchainUtils import java.math.BigDecimal import java.math.RoundingMode @@ -73,8 +72,7 @@ internal fun calculateSubtractedAmount( /** * Check if custom fee is too low */ -internal fun checkIfFeeTooLow(state: SendUiState): Boolean { - val feeSelectorState = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false +internal fun checkIfFeeTooLow(feeSelectorState: FeeSelectorState.Content): Boolean { val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return false val minimumValue = multipleFees.minimum.amount.value ?: return false val customAmount = feeSelectorState.customValues.firstOrNull() ?: return false diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt index 92189a24ef..229568a930 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt @@ -1,9 +1,6 @@ package com.tangem.features.send.impl.presentation.state.fee -import com.tangem.features.send.impl.presentation.state.SendNotification -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.SendUiStateType -import com.tangem.features.send.impl.presentation.state.StateRouter +import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.persistentListOf @@ -19,10 +16,10 @@ internal class FeeNotificationFactory( ) { fun create() = stateRouterProvider().currentState - .filter { it.type == SendUiStateType.Fee } + .filter { it.type == SendUiStateType.Fee || it.type == SendUiStateType.EditFee } .map { val state = currentStateProvider() - val feeState = state.feeState ?: return@map persistentListOf() + val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return@map persistentListOf() buildList { addFeeUnreachableNotification(feeState.feeSelectorState) }.toImmutableList() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index b88d51c2a9..e5683fcdc7 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -7,9 +7,7 @@ import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase -import com.tangem.features.send.impl.presentation.state.SendNotification -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList @@ -20,6 +18,7 @@ import kotlinx.collections.immutable.persistentListOf */ internal class FeeStateFactory( private val clickIntents: SendClickIntents, + private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, private val appCurrencyProvider: Provider, @@ -43,8 +42,10 @@ internal class FeeStateFactory( fun onFeeOnLoadingState(): SendUiState { val state = currentStateProvider() - val feeState = state.feeState ?: return state - return state.copy( + val isEditState = stateRouterProvider().isEditState + val feeState = state.getFeeState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, sendState = state.sendState?.copy( isPrimaryButtonEnabled = false, ), @@ -62,7 +63,8 @@ internal class FeeStateFactory( fun onFeeOnLoadedState(fees: TransactionFee): SendUiState { val state = currentStateProvider() - val feeState = state.feeState ?: return state + val isEditState = stateRouterProvider().isEditState + val feeState = state.getFeeState(isEditState) ?: return state val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content val isCustomWasSelected = if (feeState.isCustomSelected) { @@ -79,7 +81,8 @@ internal class FeeStateFactory( ) val fee = feeConverter.convert(updatedFeeSelectorState) - return state.copy( + return state.copyWrapped( + isEditState = isEditState, sendState = state.sendState?.copy( isPrimaryButtonEnabled = true, ), @@ -93,8 +96,10 @@ internal class FeeStateFactory( fun onFeeOnErrorState(): SendUiState { val state = currentStateProvider() - return state.copy( - feeState = state.feeState?.copy( + val isEditState = stateRouterProvider().isEditState + return state.copyWrapped( + isEditState = isEditState, + feeState = state.getFeeState(isEditState)?.copy( feeSelectorState = FeeSelectorState.Error, ), sendState = state.sendState?.copy( @@ -105,13 +110,15 @@ internal class FeeStateFactory( fun onFeeSelectedState(feeType: FeeType): SendUiState { val state = currentStateProvider() - val feeState = state.feeState ?: return state + val isEditState = stateRouterProvider().isEditState + val feeState = state.getFeeState(isEditState) ?: return state val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state val updatedFeeSelectorState = feeSelectorState.copy(selectedFee = feeType) val fee = feeConverter.convert(updatedFeeSelectorState) val isCustomFeeWasSelected = feeState.isCustomSelected || updatedFeeSelectorState.selectedFee == FeeType.Custom - return state.copy( + return state.copyWrapped( + isEditState = isEditState, feeState = feeState.copy( fee = fee, isCustomSelected = isCustomFeeWasSelected, @@ -122,12 +129,14 @@ internal class FeeStateFactory( fun onCustomFeeValueChange(index: Int, value: String): SendUiState { val state = currentStateProvider() - val feeState = state.feeState ?: return state + val isEditState = stateRouterProvider().isEditState + val feeState = state.getFeeState(isEditState) ?: return state val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state val updatedFeeSelectorState = customFeeFieldConverter.onValueChange(feeSelectorState, index, value) val fee = feeConverter.convert(updatedFeeSelectorState) - return state.copy( + return state.copyWrapped( + isEditState = isEditState, feeState = feeState.copy( feeSelectorState = updatedFeeSelectorState, fee = fee, @@ -137,10 +146,13 @@ internal class FeeStateFactory( fun getFeeNotificationState(notifications: ImmutableList): SendUiState { val state = currentStateProvider() - return state.copy( - feeState = state.feeState?.copy( + val isEditState = stateRouterProvider().isEditState + val feeState = state.getFeeState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, + feeState = feeState.copy( notifications = notifications, - isPrimaryButtonEnabled = isPrimaryButtonEnabled(state.feeState, notifications), + isPrimaryButtonEnabled = isPrimaryButtonEnabled(feeState, notifications), ), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index a1fc8fbe25..04c375714e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -8,18 +8,21 @@ import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import java.math.BigDecimal import java.math.RoundingMode internal class SendAmountFieldChangeConverter( + private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, ) : Converter { override fun convert(value: String): SendUiState { val state = currentStateProvider() - val amountState = state.amountState ?: return state + val isEditState = stateRouterProvider().isEditState + val amountState = state.getAmountState(isEditState) ?: return state val amountTextField = amountState.amountTextField if (value.isEmpty()) return state.emptyState() @@ -35,7 +38,8 @@ internal class SendAmountFieldChangeConverter( val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue val isExceedBalance = checkValue.checkExceedBalance(amountTextField) val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isZero() else decimalCryptoValue.isZero() - return state.copy( + return state.copyWrapped( + isEditState = isEditState, amountState = amountState.copy( isPrimaryButtonEnabled = !isExceedBalance && !isZero, amountTextField = amountTextField.copy( @@ -75,9 +79,11 @@ internal class SendAmountFieldChangeConverter( } private fun SendUiState.emptyState(): SendUiState { - if (amountState == null) return this + val isEditState = stateRouterProvider().isEditState + val amountState = getAmountState(isEditState) ?: return this val amountTextField = amountState.amountTextField - return copy( + return copyWrapped( + isEditState = isEditState, amountState = amountState.copy( isPrimaryButtonEnabled = false, amountTextField = amountTextField.copy( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt index bb69b15be3..7d4dfe3b45 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt @@ -6,11 +6,13 @@ import androidx.compose.ui.text.input.KeyboardType import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import com.tangem.utils.isNullOrZero internal class SendAmountFieldMaxAmountConverter( + private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, ) : Converter { @@ -18,7 +20,8 @@ internal class SendAmountFieldMaxAmountConverter( override fun convert(value: Unit): SendUiState { val state = currentStateProvider() val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val amountState = state.amountState ?: return state + val isEditState = stateRouterProvider().isEditState + val amountState = state.getAmountState(isEditState) ?: return state val amountTextField = amountState.amountTextField val cryptoDecimals = amountTextField.cryptoAmount.decimals @@ -31,7 +34,8 @@ internal class SendAmountFieldMaxAmountConverter( val isDoneActionEnabled = !decimalCryptoValue.isNullOrZero() val cryptoValue = decimalCryptoValue?.parseBigDecimal(cryptoDecimals).orEmpty() val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals).orEmpty() - return state.copy( + return state.copyWrapped( + isEditState = isEditState, amountState = amountState.copy( isPrimaryButtonEnabled = true, amountTextField = amountTextField.copy( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index bf72bb774a..ace8e220f8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -3,7 +3,10 @@ package com.tangem.features.send.impl.presentation.ui import androidx.compose.animation.* import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -50,7 +53,11 @@ internal fun SendNavigationButtons( bottom = TangemTheme.dimens.spacing16, ), ) { - SendingText(uiState = uiState, isVisible = isSendingState) + SendingText( + uiState = uiState, + isEditState = currentState.isFromConfirmation, + isVisible = isSendingState, + ) SendDoneButtons( txUrl = sendState.txUrl, onExploreClick = uiState.clickIntents::onExploreClick, @@ -130,7 +137,12 @@ private fun SendNavigationButton( } @Composable -private fun SendingText(uiState: SendUiState, isVisible: Boolean, modifier: Modifier = Modifier) { +private fun SendingText( + uiState: SendUiState, + isEditState: Boolean, + isVisible: Boolean, + modifier: Modifier = Modifier, +) { var isVisibleProxy by remember { mutableStateOf(isVisible) } // text appearance delay for smooth screen transitions @@ -148,8 +160,8 @@ private fun SendingText(uiState: SendUiState, isVisible: Boolean, modifier: Modi exit = slideOutVertically().plus(fadeOut()), label = "Animate show sending state text", ) { - val amountState = uiState.amountState - val feeState = uiState.feeState + val amountState = uiState.getAmountState(isEditState) + val feeState = uiState.getFeeState(isEditState) val fiatRate = feeState?.rate val fiatAmount = amountState?.amountTextField?.fiatAmount val feeFiat = fiatRate?.let { feeState.fee?.amount?.value?.multiply(it) } @@ -230,11 +242,11 @@ private fun getButtonData( SendUiStateType.Amount, SendUiStateType.Recipient, SendUiStateType.Fee, - -> if (currentState.isFromConfirmation) { - R.string.common_continue to uiState.clickIntents::onNextClick - } else { - R.string.common_next to uiState.clickIntents::onNextClick - } + -> R.string.common_next to { uiState.clickIntents.onNextClick() } + SendUiStateType.EditFee, + SendUiStateType.EditAmount, + SendUiStateType.EditRecipient, + -> R.string.common_continue to { uiState.clickIntents.onNextClick(isFromEdit = true) } SendUiStateType.Send -> when { isSuccess -> R.string.common_close isSending -> R.string.send_sending @@ -245,10 +257,13 @@ private fun getButtonData( private fun isButtonEnabled(currentState: SendUiCurrentScreen, uiState: SendUiState): Boolean { return when (currentState.type) { - SendUiStateType.Amount -> uiState.amountState?.isPrimaryButtonEnabled ?: false - SendUiStateType.Recipient -> uiState.recipientState?.isPrimaryButtonEnabled ?: false - SendUiStateType.Fee -> uiState.feeState?.isPrimaryButtonEnabled ?: false - SendUiStateType.Send -> uiState.sendState?.isPrimaryButtonEnabled ?: false + SendUiStateType.Amount -> uiState.amountState?.isPrimaryButtonEnabled + SendUiStateType.Recipient -> uiState.recipientState?.isPrimaryButtonEnabled + SendUiStateType.Fee -> uiState.feeState?.isPrimaryButtonEnabled + SendUiStateType.Send -> uiState.sendState?.isPrimaryButtonEnabled + SendUiStateType.EditAmount -> uiState.editAmountState?.isPrimaryButtonEnabled + SendUiStateType.EditRecipient -> uiState.editRecipientState?.isPrimaryButtonEnabled + SendUiStateType.EditFee -> uiState.editFeeState?.isPrimaryButtonEnabled else -> true - } + } ?: false } \ No newline at end of file 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 94620f127c..1d678b777e 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 @@ -122,6 +122,7 @@ internal class SendViewModel @Inject constructor( private val stateFactory = SendStateFactory( clickIntents = this, + stateRouterProvider = Provider { stateRouter }, currentStateProvider = Provider { uiState }, userWalletProvider = Provider { userWallet }, appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), @@ -132,12 +133,14 @@ internal class SendViewModel @Inject constructor( ) private val amountStateFactory = AmountStateFactory( + stateRouterProvider = Provider { stateRouter }, currentStateProvider = Provider { uiState }, cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, ) private val feeStateFactory = FeeStateFactory( clickIntents = this, + stateRouterProvider = Provider { stateRouter }, currentStateProvider = Provider { uiState }, feeCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus }, appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), @@ -146,6 +149,7 @@ internal class SendViewModel @Inject constructor( private val eventStateFactory = SendEventStateFactory( clickIntents = this, + stateRouterProvider = Provider { stateRouter }, currentStateProvider = Provider { uiState }, cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, feeStateFactory = feeStateFactory, @@ -171,7 +175,10 @@ internal class SendViewModel @Inject constructor( ) private val sendOnNextScreenAnalyticSender by lazy(LazyThreadSafetyMode.NONE) { - SendOnNextScreenAnalyticSender(analyticsEventHandler) + SendOnNextScreenAnalyticSender( + stateRouterProvider = Provider { stateRouter }, + analyticsEventHandler = analyticsEventHandler, + ) } // todo convert to StateFlow @@ -454,7 +461,9 @@ internal class SendViewModel @Inject constructor( stateRouter.currentState .onEach { when (it.type) { - SendUiStateType.Fee -> loadFee() + SendUiStateType.Fee, + SendUiStateType.EditFee, + -> loadFee() SendUiStateType.Send -> sendIdleTimer = SystemClock.elapsedRealtime() else -> Unit } @@ -489,16 +498,17 @@ internal class SendViewModel @Inject constructor( stateRouter.onBackClick(isSuccess = uiState.sendState?.isSuccess == true) } - override fun onNextClick() { + override fun onNextClick(isFromEdit: Boolean) { val currentState = stateRouter.currentState.value + uiState = stateFactory.syncEditStates(isFromEdit = isFromEdit) sendOnNextScreenAnalyticSender.send(currentState.type, uiState) when (currentState.type) { - SendUiStateType.Fee -> { - if (onFeeNext()) return - } - SendUiStateType.Amount -> { - loadFee() - } + SendUiStateType.Fee, + SendUiStateType.EditFee, + -> if (onFeeNext()) return + SendUiStateType.Amount, + SendUiStateType.EditAmount, + -> loadFee() else -> Unit } @@ -523,13 +533,14 @@ internal class SendViewModel @Inject constructor( innerRouter.openTokenDetails(userWalletId, currency) private fun onFeeNext(): Boolean { - if (checkIfFeeTooLow(uiState)) { + val feeState = uiState.getFeeState(stateRouter.isEditState) + val feeSelectorState = feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false + if (checkIfFeeTooLow(feeSelectorState)) { uiState = eventStateFactory.getFeeTooLowAlert( onConsume = { uiState = eventStateFactory.onConsumeEventState() }, ) return true } - val feeSelectorState = uiState.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false return checkIfFeeTooHigh( feeSelectorState = feeSelectorState, onShow = { diff -> @@ -706,8 +717,9 @@ internal class SendViewModel @Inject constructor( } private suspend fun callFeeUseCase(): Either? { - val amountState = uiState.amountState ?: return null - val recipientState = uiState.recipientState ?: return null + val isFromConfirmation = stateRouter.currentState.value.isFromConfirmation + val amountState = uiState.getAmountState(isFromConfirmation) ?: return null + val recipientState = uiState.getRecipientState(isFromConfirmation) ?: return null val amount = amountState.amountTextField.cryptoAmount.value ?: return null return feeCryptoCurrencyStatus?.let { feeCurrencyStatus -> @@ -736,12 +748,14 @@ internal class SendViewModel @Inject constructor( } override fun showAmount() { + uiState = stateFactory.syncEditStates(isFromEdit = false) stateRouter.showAmount(isFromConfirmation = true) setNeverToShowTapHelp() analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Amount)) } override fun showRecipient() { + uiState = stateFactory.syncEditStates(isFromEdit = false) stateRouter.showRecipient(isFromConfirmation = true) uiState = stateFactory.getHiddenTapHelpState() setNeverToShowTapHelp() @@ -749,6 +763,7 @@ internal class SendViewModel @Inject constructor( } override fun showFee() { + uiState = stateFactory.syncEditStates(isFromEdit = false) stateRouter.showFee(isFromConfirmation = true) setNeverToShowTapHelp() analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Fee)) From 636b63cd3a6d3fcb5a69ca65d0d9cbeffd37544a Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 21:22:27 +0800 Subject: [PATCH 45/85] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 7da3c6d73d..cf1fb0dc1d 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -17,7 +17,7 @@ }, { "name": "LOCAL_USER_LOGS_ENABLED", - "version": "5.8.0" + "version": "5.10.0" }, { "name": "GENERATE_XPUB_ENABLED", From 66168b3177a2ee91d34cf9aec591c11822ac73d5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 15:38:02 +0100 Subject: [PATCH 46/85] Updated on 2026-08-14 --- .../wallet/redux/middlewares/TradeCryptoMiddleware.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index aa15f116e8..39a0168df7 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -223,6 +223,10 @@ object TradeCryptoMiddleware { } private fun handleSendCoin(action: TradeCryptoAction.SendCoin) { + if (action.transactionInfo?.tag != null) { + // avoid open old send if memo exists + return + } val cryptoStatus = action.coinStatus val currency = cryptoStatus.currency val blockchain = Blockchain.fromId(currency.network.id.value) From 5b50e044cd1e74d8de9aafc6583f8a275c0e72e1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 19:53:21 +0500 Subject: [PATCH 47/85] Updated on 2026-08-14 --- .../ui/components/currency/tokenicon/TokenIcon.kt | 12 ++++++++++-- .../impl/presentation/ui/amount/AmountButtons.kt | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIcon.kt index 07d299f583..9ad7d42cc8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIcon.kt @@ -27,9 +27,15 @@ private const val NORMAL_ALPHA = 1f * @param state cryptocurrency icon config * @param modifier component modifier * @param shouldDisplayNetwork specifies whether to display network badge + * @param shouldDisplayCustom specifies whether to display custom badge */ @Composable -fun TokenIcon(state: TokenIconState, modifier: Modifier = Modifier, shouldDisplayNetwork: Boolean = true) { +fun TokenIcon( + state: TokenIconState, + modifier: Modifier = Modifier, + shouldDisplayNetwork: Boolean = true, + shouldDisplayCustom: Boolean = true, +) { BaseContainer(modifier = modifier) { val iconModifier = Modifier .align(Alignment.Center) @@ -46,6 +52,7 @@ fun TokenIcon(state: TokenIconState, modifier: Modifier = Modifier, shouldDispla icon = state, modifier = iconModifier, shouldDisplayNetwork = shouldDisplayNetwork, + shouldDisplayCustom = shouldDisplayCustom, ) } } @@ -76,6 +83,7 @@ private fun BoxScope.ContentIconContainer( icon: TokenIconState, modifier: Modifier = Modifier, shouldDisplayNetwork: Boolean = true, + shouldDisplayCustom: Boolean = true, ) { val networkBadgeOffset = TangemTheme.dimens.spacing4 val (alpha, colorFilter) = remember(icon.isGrayscale) { @@ -104,7 +112,7 @@ private fun BoxScope.ContentIconContainer( ) } - if (icon.showCustomBadge) { + if (icon.showCustomBadge && shouldDisplayCustom) { CustomBadge(modifier = Modifier.align(Alignment.BottomEnd)) } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt index 340f8e20ad..c4348c54f9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt @@ -96,6 +96,7 @@ private fun SendAmountCurrencyButton(button: SendAmountSegmentedButtonsConfig, i TokenIcon( state = button.iconState, shouldDisplayNetwork = false, + shouldDisplayCustom = false, modifier = Modifier .size(TangemTheme.dimens.size18), ) From bd4cfb134dc496915fac1c7befbd9f56d389feab Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 19:54:08 +0500 Subject: [PATCH 48/85] Updated on 2026-08-14 --- .../tangem/core/ui/components/fields/SimpleTextField.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index e4737af0c1..577485da0d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -45,7 +45,12 @@ fun SimpleTextField( ) { val proxyValue by remember(value) { derivedStateOf { value } } var textFieldValueState by remember { - mutableStateOf(TextFieldValue(text = value)) + mutableStateOf( + TextFieldValue( + text = value, + selection = TextRange(value.length, value.length), + ), + ) } val focusRequester = remember { FocusRequester.Default } val customTextSelectionColors = TextSelectionColors( From 6acb6006fced9bf572b270cdd763b1740d2fb8e4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 19:54:45 +0500 Subject: [PATCH 49/85] Updated on 2026-08-14 --- .../appbar/AppBarWithBackButtonAndIconContent.kt | 4 ++-- .../send/impl/presentation/state/SendStateFactory.kt | 1 + .../state/amount/SendAmountCurrencyConverter.kt | 8 ++++++++ .../send/impl/presentation/state/fee/FeeConverter.kt | 4 ++++ .../send/impl/presentation/state/fee/FeeStateFactory.kt | 7 ++++++- .../presentation/state/fee/SendFeeCustomFieldConverter.kt | 4 ++++ .../state/fee/custom/BitcoinCustomFeeConverter.kt | 6 +++++- .../state/fee/custom/EthereumCustomFeeConverter.kt | 6 +++++- .../presentation/state/fields/SendAmountFieldConverter.kt | 6 +++++- 9 files changed, 40 insertions(+), 6 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt index 09b6158abb..c65bf02622 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt @@ -63,7 +63,8 @@ fun AppBarWithBackButtonAndIconContent( ) Column( verticalArrangement = Arrangement.Center, - modifier = Modifier.weight(1f), + modifier = Modifier.weight(1f) + .animateContentSize(), ) { AnimatedVisibility( visible = !text.isNullOrBlank(), @@ -89,7 +90,6 @@ fun AppBarWithBackButtonAndIconContent( color = TangemTheme.colors.text.secondary, maxLines = 1, style = TangemTheme.typography.caption2, - modifier = Modifier.animateContentSize(), ) } } 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 a2b722b614..32b43ad33a 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 @@ -46,6 +46,7 @@ internal class SendStateFactory( private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) { SendAmountFieldConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, appCurrencyProvider = appCurrencyProvider, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt index 4817557ac6..369918da01 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt @@ -1,5 +1,8 @@ package com.tangem.features.send.impl.presentation.state.amount +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.StateRouter @@ -20,6 +23,7 @@ internal class SendAmountCurrencyConverter( val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val isValidFiatRate = cryptoCurrencyStatus.value.fiatRate.isNullOrZero() + val isDoneActionEnabled = amountState.isPrimaryButtonEnabled return if (amountTextField.isFiatValue == value && !isValidFiatRate) { state } else { @@ -29,6 +33,10 @@ internal class SendAmountCurrencyConverter( amountTextField = amountTextField.copy( isFiatValue = value, isValuePasted = true, + keyboardOptions = KeyboardOptions( + imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None, + keyboardType = KeyboardType.Number, + ), ), ), ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt index 1fa99ee291..5109aa17b9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt @@ -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.StateRouter 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 @@ -13,6 +14,7 @@ import com.tangem.utils.converter.Converter internal class FeeConverter( private val clickIntents: SendClickIntents, + private val stateRouterProvider: Provider, private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, ) : Converter { @@ -20,6 +22,7 @@ internal class FeeConverter( private val ethereumCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { EthereumCustomFeeConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, appCurrencyProvider = appCurrencyProvider, feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, ) @@ -28,6 +31,7 @@ internal class FeeConverter( private val bitcoinCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { BitcoinCustomFeeConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, appCurrencyProvider = appCurrencyProvider, feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index e5683fcdc7..e933b89477 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -7,7 +7,10 @@ import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase -import com.tangem.features.send.impl.presentation.state.* +import com.tangem.features.send.impl.presentation.state.SendNotification +import com.tangem.features.send.impl.presentation.state.SendStates +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList @@ -27,6 +30,7 @@ internal class FeeStateFactory( private val customFeeFieldConverter by lazy(LazyThreadSafetyMode.NONE) { SendFeeCustomFieldConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, appCurrencyProvider = appCurrencyProvider, feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, ) @@ -35,6 +39,7 @@ internal class FeeStateFactory( val feeConverter by lazy(LazyThreadSafetyMode.NONE) { FeeConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, appCurrencyProvider = appCurrencyProvider, feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt index c9534ea97f..5fd2a4060d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt @@ -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.StateRouter 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 @@ -14,6 +15,7 @@ import kotlinx.collections.immutable.persistentListOf internal class SendFeeCustomFieldConverter( private val clickIntents: SendClickIntents, + private val stateRouterProvider: Provider, private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, ) : Converter> { @@ -21,6 +23,7 @@ internal class SendFeeCustomFieldConverter( private val ethereumCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { EthereumCustomFeeConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, appCurrencyProvider = appCurrencyProvider, feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, ) @@ -29,6 +32,7 @@ internal class SendFeeCustomFieldConverter( private val bitcoinCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { BitcoinCustomFeeConverter( clickIntents = clickIntents, + stateRouterProvider = stateRouterProvider, appCurrencyProvider = appCurrencyProvider, feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt index 8226a67504..5594d8d917 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt @@ -15,6 +15,7 @@ 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.StateRouter import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils.isBitcoin @@ -27,6 +28,7 @@ import java.math.RoundingMode internal class BitcoinCustomFeeConverter( private val clickIntents: SendClickIntents, + private val stateRouterProvider: Provider, private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, ) : CustomFeeConverter { @@ -66,7 +68,9 @@ internal class BitcoinCustomFeeConverter( imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done, keyboardType = KeyboardType.Number, ), - keyboardActions = KeyboardActions(), + keyboardActions = KeyboardActions( + onDone = { clickIntents.onNextClick(stateRouterProvider().isEditState) }, + ), ), ) } else { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt index 4851533b49..9428dbdfc0 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt @@ -15,6 +15,7 @@ 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.StateRouter import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider @@ -26,6 +27,7 @@ import java.math.RoundingMode internal class EthereumCustomFeeConverter( private val clickIntents: SendClickIntents, + private val stateRouterProvider: Provider, private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, ) : CustomFeeConverter { @@ -71,7 +73,9 @@ internal class EthereumCustomFeeConverter( imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done, keyboardType = KeyboardType.Number, ), - keyboardActions = KeyboardActions(onDone = { clickIntents.onNextClick() }), + keyboardActions = KeyboardActions( + onDone = { clickIntents.onNextClick(stateRouterProvider().isEditState) }, + ), ), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt index 2070b8c633..14f7f4b660 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt @@ -14,6 +14,7 @@ import com.tangem.domain.tokens.model.AmountType import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.convertToAmount import com.tangem.features.send.impl.R +import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter @@ -23,6 +24,7 @@ private const val FIAT_DECIMALS = 2 internal class SendAmountFieldConverter( private val clickIntents: SendClickIntents, + private val stateRouterProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, private val appCurrencyProvider: Provider, ) : Converter { @@ -48,7 +50,9 @@ internal class SendAmountFieldConverter( imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None, keyboardType = KeyboardType.Number, ), - keyboardActions = KeyboardActions(onDone = { clickIntents.onNextClick() }), + keyboardActions = KeyboardActions( + onDone = { clickIntents.onNextClick(stateRouterProvider().isEditState) }, + ), isFiatValue = false, cryptoAmount = cryptoAmount, fiatAmount = getAppCurrencyAmount(fiatDecimal, appCurrencyProvider()), From 429f9ea3efb27116b8be650632fa4eb691bb6ec6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 15:54:42 +0100 Subject: [PATCH 50/85] Updated on 2026-08-14 --- .../com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index eded0146fc..91430442e1 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -197,12 +197,6 @@ class GetCurrencyWarningsUseCase( coinCurrency = coinStatus.currency, ) } - feePaidCurrency is FeePaidCurrency.SameCurrency && tokenStatus.value.amount.isZero() -> { - CryptoCurrencyWarning.BalanceNotEnoughForFee( - tokenCurrency = tokenStatus.currency, - coinCurrency = coinStatus.currency, - ) - } feePaidCurrency is FeePaidCurrency.Token -> { val feePaidTokenBalance = feePaidCurrency.balance val amount = tokenStatus.value.amount ?: return null From f5cb300604bd79fb81417b37ce1d76508a1bef0d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Apr 2024 13:24:12 +0500 Subject: [PATCH 51/85] Updated on 2026-08-14 --- .../ui/components/currency/tokenicon/TokenIcon.kt | 12 ++---------- .../components/currency/tokenicon/TokenIconState.kt | 11 +++++------ .../converter/CryptoCurrencyToIconStateConverter.kt | 5 ++++- .../impl/presentation/ui/amount/AmountButtons.kt | 1 - 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIcon.kt index 9ad7d42cc8..07d299f583 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIcon.kt @@ -27,15 +27,9 @@ private const val NORMAL_ALPHA = 1f * @param state cryptocurrency icon config * @param modifier component modifier * @param shouldDisplayNetwork specifies whether to display network badge - * @param shouldDisplayCustom specifies whether to display custom badge */ @Composable -fun TokenIcon( - state: TokenIconState, - modifier: Modifier = Modifier, - shouldDisplayNetwork: Boolean = true, - shouldDisplayCustom: Boolean = true, -) { +fun TokenIcon(state: TokenIconState, modifier: Modifier = Modifier, shouldDisplayNetwork: Boolean = true) { BaseContainer(modifier = modifier) { val iconModifier = Modifier .align(Alignment.Center) @@ -52,7 +46,6 @@ fun TokenIcon( icon = state, modifier = iconModifier, shouldDisplayNetwork = shouldDisplayNetwork, - shouldDisplayCustom = shouldDisplayCustom, ) } } @@ -83,7 +76,6 @@ private fun BoxScope.ContentIconContainer( icon: TokenIconState, modifier: Modifier = Modifier, shouldDisplayNetwork: Boolean = true, - shouldDisplayCustom: Boolean = true, ) { val networkBadgeOffset = TangemTheme.dimens.spacing4 val (alpha, colorFilter) = remember(icon.isGrayscale) { @@ -112,7 +104,7 @@ private fun BoxScope.ContentIconContainer( ) } - if (icon.showCustomBadge && shouldDisplayCustom) { + if (icon.showCustomBadge) { CustomBadge(modifier = Modifier.align(Alignment.BottomEnd)) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIconState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIconState.kt index f51adb19b3..0f7ff350de 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIconState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/TokenIconState.kt @@ -60,24 +60,23 @@ sealed class TokenIconState { * @property background The background color to be used for the icon. * @property networkBadgeIconResId The drawable resource ID for the network badge. * @property isGrayscale Specifies whether to show the icon in grayscale. + * @property showCustomBadge Specifies whether to show the custom token badge. */ data class CustomTokenIcon( val tint: Color, val background: Color, @DrawableRes override val networkBadgeIconResId: Int, override val isGrayscale: Boolean, - ) : TokenIconState() { + override val showCustomBadge: Boolean = true, + ) : TokenIconState() - override val showCustomBadge: Boolean = true - } - - object Loading : TokenIconState() { + data object Loading : TokenIconState() { override val isGrayscale: Boolean = false override val showCustomBadge: Boolean = false override val networkBadgeIconResId: Int? = null } - object Locked : TokenIconState() { + data object Locked : TokenIconState() { override val isGrayscale: Boolean = false override val showCustomBadge: Boolean = false override val networkBadgeIconResId: Int? = null diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt index fbcc6e3caa..3e62e98992 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt @@ -30,6 +30,7 @@ class CryptoCurrencyToIconStateConverter : Converter getIconStateForToken( token = currency, @@ -50,13 +51,14 @@ class CryptoCurrencyToIconStateConverter : Converter Date: Thu, 25 Apr 2024 19:30:47 +0500 Subject: [PATCH 52/85] Updated on 2026-08-14 --- .../state/confirm/SendNotificationFactory.kt | 21 ------------------- gradle/dependencies.toml | 2 +- 2 files changed, 1 insertion(+), 22 deletions(-) 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 3b01354982..9fbd121d79 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 @@ -20,7 +20,6 @@ import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.state.fee.* import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents -import com.tangem.lib.crypto.BlockchainUtils.isDogecoin import com.tangem.lib.crypto.BlockchainUtils.isTezos import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList @@ -71,7 +70,6 @@ internal class SendNotificationFactory( addFeeUnreachableNotification(feeState.feeSelectorState) addExceedBalanceNotification(feeValue, sendingAmount) addExceedsBalanceNotification(feeState.fee) - addMinimumAmountErrorNotification(feeValue, sendingAmount) addDustWarningNotification(feeValue, sendingAmount) addTransactionLimitErrorNotification(feeValue, sendingAmount) // warnings @@ -235,21 +233,6 @@ internal class SendNotificationFactory( } } - // todo remove in [REDACTED_TASK_KEY] - private fun MutableList.addMinimumAmountErrorNotification( - feeAmount: BigDecimal, - receivedAmount: BigDecimal, - ) { - val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider() - val minimum = BigDecimal(DOGECOIN_MINIMUM) - - val isDogecoin = isDogecoin(coinCryptoCurrencyStatus.currency.network.id.value) - val isExceedDustLimit = checkDustLimits(feeAmount, receivedAmount, minimum) - if (isDogecoin && isExceedDustLimit) { - add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM)) - } - } - private suspend fun MutableList.addDustWarningNotification( feeAmount: BigDecimal, receivedAmount: BigDecimal, @@ -377,8 +360,4 @@ internal class SendNotificationFactory( val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO return receivedAmount < dustValue || isChangeLowerThanDust } - - companion object { - private const val DOGECOIN_MINIMUM = "0.01" - } } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 603776f9f0..aafc6fdb14 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-608" +tangemBlockchainSdk = "release-app_5.9.1-610" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 20402fcbdea44a006dd1d19949baf1a6ca13ea7d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Apr 2024 15:14:27 +0500 Subject: [PATCH 53/85] Updated on 2026-08-14 --- .../analytics/SendAnalyticEvents.kt | 15 +++++++++ ...cSender.kt => SendScreenAnalyticSender.kt} | 32 +++++++++++++++++-- .../state/previewdata/SendClickIntentsStub.kt | 2 ++ .../send/impl/presentation/ui/SendScreen.kt | 10 +++--- .../viewmodel/SendClickIntents.kt | 2 ++ .../presentation/viewmodel/SendViewModel.kt | 20 +++++++++--- 6 files changed, 70 insertions(+), 11 deletions(-) rename features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/{SendOnNextScreenAnalyticSender.kt => SendScreenAnalyticSender.kt} (69%) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt index e99471d8de..feca8a0786 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt @@ -16,6 +16,20 @@ internal sealed class SendAnalyticEvents( params: Map = mapOf(), ) : AnalyticsEvent(category = "Token / Send", event = event, params = params) { + /** Close button clicked */ + data class CloseButtonClicked( + val source: SendScreenSource, + val isFromSummary: Boolean, + val isValid: Boolean, + ) : SendAnalyticEvents( + event = "Button - Close", + params = mapOf( + SOURCE to source.name, + "FromSummary" to if (isFromSummary) "Yes" else "No", + "isValid" to if (isValid) "Yes" else "No", + ), + ) + // region Address /** Recipient address screen opened */ data object AddressScreenOpened : SendAnalyticEvents(event = "Address Screen Opened") @@ -119,6 +133,7 @@ internal enum class SendScreenSource { Address, Amount, Fee, + Confirm, } internal enum class EnterAddressSource { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt similarity index 69% rename from features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt rename to features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt index 6fefbc87b8..27c93b9743 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt @@ -5,15 +5,17 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.features.send.impl.presentation.analytics.SelectedCurrencyType import com.tangem.features.send.impl.presentation.analytics.SelectedFeeType import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents -import com.tangem.features.send.impl.presentation.state.* +import com.tangem.features.send.impl.presentation.analytics.SendScreenSource import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.SendUiStateType import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.utils.Provider -internal class SendOnNextScreenAnalyticSender( +internal class SendScreenAnalyticSender( private val stateRouterProvider: Provider, + private val currentStateProvider: Provider, private val analyticsEventHandler: AnalyticsEventHandler, ) { fun send(prevScreen: SendUiStateType, state: SendUiState) { @@ -45,6 +47,32 @@ internal class SendOnNextScreenAnalyticSender( } } + fun sendOnClose() { + val routerState = stateRouterProvider().currentState.value + val state = currentStateProvider() + + val (source, isValid) = when (routerState.type) { + SendUiStateType.Recipient, + SendUiStateType.EditRecipient, + -> SendScreenSource.Address to (state.editRecipientState?.isPrimaryButtonEnabled ?: false) + SendUiStateType.Amount, + SendUiStateType.EditAmount, + -> SendScreenSource.Amount to (state.editAmountState?.isPrimaryButtonEnabled ?: false) + SendUiStateType.Fee, + SendUiStateType.EditFee, + -> SendScreenSource.Fee to (state.editFeeState?.isPrimaryButtonEnabled ?: false) + else -> SendScreenSource.Confirm to true + } + + analyticsEventHandler.send( + SendAnalyticEvents.CloseButtonClicked( + source = source, + isFromSummary = routerState.isFromConfirmation, + isValid = isValid, + ), + ) + } + private fun sendSelectedFeeAnalytics(feeSelectorState: FeeSelectorState.Content) { val type = when (feeSelectorState.fees) { is TransactionFee.Single -> SelectedFeeType.Fixed diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 0a113ea474..84a2a7b90d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -14,6 +14,8 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onBackClick() {} + override fun onCloseClick() {} + override fun onNextClick(isFromEdit: Boolean) {} override fun onPrevClick() {} diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index b289867885..5c7eba689d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.withIndex @Composable internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) { val snackbarHostState = remember { SnackbarHostState() } - BackHandler { uiState.clickIntents.onBackClick() } + BackHandler(onBack = uiState.clickIntents::onBackClick) Column( modifier = Modifier .fillMaxSize() @@ -88,17 +88,17 @@ private fun SendAppBar(uiState: SendUiState, currentState: SendUiCurrentScreen) } else { null } - val (backIcon, backClick) = when (currentState.type) { + val backIcon = when (currentState.type) { SendUiStateType.EditAmount, SendUiStateType.EditFee, SendUiStateType.EditRecipient, - -> R.drawable.ic_back_24 to uiState.clickIntents::onBackClick - else -> R.drawable.ic_close_24 to uiState.clickIntents::popBackStack + -> R.drawable.ic_back_24 + else -> R.drawable.ic_close_24 } AppBarWithBackButtonAndIcon( text = titleRes?.resolveReference(), subtitle = subtitleRes, - onBackClick = backClick, + onBackClick = uiState.clickIntents::onCloseClick, onIconClick = uiState.clickIntents::onQrCodeScanClick, backIconRes = backIcon, iconRes = iconRes, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index f0715cd50a..76a22a3b24 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -14,6 +14,8 @@ internal interface SendClickIntents { fun onBackClick() + fun onCloseClick() + fun onNextClick(isFromEdit: Boolean = false) fun onPrevClick() 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 1d678b777e..3212417a3e 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 @@ -47,7 +47,7 @@ import com.tangem.features.send.impl.navigation.InnerSendRouter import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents import com.tangem.features.send.impl.presentation.analytics.SendScreenSource -import com.tangem.features.send.impl.presentation.analytics.utils.SendOnNextScreenAnalyticSender +import com.tangem.features.send.impl.presentation.analytics.utils.SendScreenAnalyticSender import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory @@ -174,9 +174,10 @@ internal class SendViewModel @Inject constructor( getBalanceNotEnoughForFeeWarningUseCase = getBalanceNotEnoughForFeeWarningUseCase, ) - private val sendOnNextScreenAnalyticSender by lazy(LazyThreadSafetyMode.NONE) { - SendOnNextScreenAnalyticSender( + private val sendScreenAnalyticSender by lazy(LazyThreadSafetyMode.NONE) { + SendScreenAnalyticSender( stateRouterProvider = Provider { stateRouter }, + currentStateProvider = Provider { uiState }, analyticsEventHandler = analyticsEventHandler, ) } @@ -498,10 +499,21 @@ internal class SendViewModel @Inject constructor( stateRouter.onBackClick(isSuccess = uiState.sendState?.isSuccess == true) } + override fun onCloseClick() { + sendScreenAnalyticSender.sendOnClose() + when (stateRouter.currentState.value.type) { + SendUiStateType.EditAmount, + SendUiStateType.EditFee, + SendUiStateType.EditRecipient, + -> onBackClick() + else -> popBackStack() + } + } + override fun onNextClick(isFromEdit: Boolean) { val currentState = stateRouter.currentState.value uiState = stateFactory.syncEditStates(isFromEdit = isFromEdit) - sendOnNextScreenAnalyticSender.send(currentState.type, uiState) + sendScreenAnalyticSender.send(currentState.type, uiState) when (currentState.type) { SendUiStateType.Fee, SendUiStateType.EditFee, From 52306efb20206afdf2c859d5a210df00392abea1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 15:15:08 +0300 Subject: [PATCH 54/85] Updated on 2026-08-14 --- app/src/debug/res/xml/network_security_config.xml | 4 ++-- app/src/external/res/xml/network_security_config.xml | 5 +++-- app/src/internal/res/xml/network_security_config.xml | 4 ++-- app/src/main/res/xml/network_security_config.xml | 7 ++----- .../connection/RealInternetConnectionManager.kt | 10 +++++++++- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/src/debug/res/xml/network_security_config.xml b/app/src/debug/res/xml/network_security_config.xml index 894912ac61..52c44ac992 100644 --- a/app/src/debug/res/xml/network_security_config.xml +++ b/app/src/debug/res/xml/network_security_config.xml @@ -1,9 +1,9 @@ - + - + diff --git a/app/src/external/res/xml/network_security_config.xml b/app/src/external/res/xml/network_security_config.xml index 894912ac61..d49c0f70ba 100644 --- a/app/src/external/res/xml/network_security_config.xml +++ b/app/src/external/res/xml/network_security_config.xml @@ -1,9 +1,10 @@ - + - + + diff --git a/app/src/internal/res/xml/network_security_config.xml b/app/src/internal/res/xml/network_security_config.xml index 894912ac61..52c44ac992 100644 --- a/app/src/internal/res/xml/network_security_config.xml +++ b/app/src/internal/res/xml/network_security_config.xml @@ -1,9 +1,9 @@ - + - + diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 22e9f8653e..683208ffda 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,11 +1,8 @@ - - - api.tangem.com + - - + diff --git a/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt b/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt index 8ca86a2587..7a3279d188 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.reactive.asFlow +private const val PING_SERVER = "https://clients3.google.com/generate_204" private const val PING_INTERVAL = 5_000 internal class RealInternetConnectionManager : NetworkConnectionManager { @@ -24,7 +25,12 @@ internal class RealInternetConnectionManager : NetworkConnectionManager { private val initialNetworkResult: Boolean by lazy { ReactiveNetwork - .checkInternetConnectivity() + .checkInternetConnectivity( + InternetObservingSettings.builder() + .host(PING_SERVER) + .port(443) + .build(), + ) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .blockingGet() @@ -33,6 +39,8 @@ internal class RealInternetConnectionManager : NetworkConnectionManager { override val isOnlineFlow: StateFlow = ReactiveNetwork .observeInternetConnectivity( InternetObservingSettings.builder() + .host(PING_SERVER) + .port(443) .interval(PING_INTERVAL) .build(), ) From 9afc2422e66ada231b046105609b6f9728d501f7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Apr 2024 17:22:57 +0500 Subject: [PATCH 55/85] Updated on 2026-08-14 --- .../presentation/state/SendStateFactory.kt | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 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 32b43ad33a..caab9c9dc0 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 @@ -285,7 +285,11 @@ internal class SendStateFactory( return state.copy( sendState = state.sendState?.copy( isSending = isSending, - isPrimaryButtonEnabled = isPrimaryButtonEnabled(state, state.sendState.notifications), + isPrimaryButtonEnabled = isPrimaryButtonEnabled( + state = state, + isSending = isSending, + notifications = state.sendState.notifications, + ), ), ) } @@ -309,7 +313,11 @@ internal class SendStateFactory( val sendState = state.sendState ?: return state return state.copy( sendState = sendState.copy( - isPrimaryButtonEnabled = isPrimaryButtonEnabled(state, notifications), + isPrimaryButtonEnabled = isPrimaryButtonEnabled( + state = state, + isSending = sendState.isSending, + notifications = notifications, + ), notifications = notifications, showTapHelp = sendState.showTapHelp && notifications.isEmpty(), ), @@ -324,11 +332,14 @@ internal class SendStateFactory( ) } - private fun isPrimaryButtonEnabled(state: SendUiState, notifications: ImmutableList): Boolean { - val sendState = state.sendState ?: return false + private fun isPrimaryButtonEnabled( + state: SendUiState, + isSending: Boolean, + notifications: ImmutableList, + ): Boolean { val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return false val hasErrorNotifications = notifications.any { it is SendNotification.Error } - return !hasErrorNotifications && !sendState.isSending && feeState.feeSelectorState is FeeSelectorState.Content + return !hasErrorNotifications && !isSending && feeState.feeSelectorState is FeeSelectorState.Content } //endregion } \ No newline at end of file From 2687d7fd5a7ad3d516331e457189836e050cc637 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Apr 2024 22:00:02 +0200 Subject: [PATCH 56/85] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 603776f9f0..5bdbdc3921 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-608" +tangemBlockchainSdk = "release-app_5.9-612" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 3ddf7b4e1102d5b96a6169dca3310849364b3032 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Apr 2024 22:24:27 +0200 Subject: [PATCH 57/85] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 33 ++++++++------------- core/res/src/main/res/values/strings.xml | 33 ++++++++------------- 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 1ac0523e66..a11d54cc42 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -4,16 +4,12 @@ Валюты Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. Обратиться в поддержку - Попробовать снова Эта функция недоступна в демонстрационном режиме Причина: %s Не могу отправить транзакцию Выбранный кошелёк не поддерживает сеть %1$s Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. - Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки. - Ваши предложения отправлены - Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, или запросите поддержку. У вас возникли трудности со сканированием карты? Эта карта не предназначена для работы с этим приложением Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. @@ -28,8 +24,6 @@ Тёмная Светлая Как в системе - При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства - Системная Тема Настройки приложения Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" @@ -58,9 +52,9 @@ Тип безопасности Настройки карты Ввиду особенностей сети Cardano при транзакции токена %1$s помимо комиссии сети будет списано %2$s - Для совершения транзакции %1$s, вам необходимо внести немного %2$s (%3$s), чтобы покрыть комиссию сети и минимальное значение ADA для отправки. + Чтобы совершить транзакцию %1$s, вы должны внести некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA. (рекомендуется баланс в размере 5 ADA) Недостаточно ADA для отправки токена - Вывод всего баланса ADA невозможен при наличии средств на токенах сети Cardano. Сначала выведите средства на ваших токенах. + Сеть Cardano не позволяет вам оставлять на своем балансе сумму ниже минимального значения %1$s, если у вас есть токены. Пожалуйста, убедитесь, что у вас осталось немного ADA. Принять Доступ запрещен Применить @@ -81,7 +75,6 @@ Создать Удалить Отключено - Отключить Готово Включить Включено @@ -114,7 +107,6 @@ Отклонить Перезагрузить Переименовать - Повторить Сохранить изменения Искать Поиск токенов @@ -138,7 +130,6 @@ Я понял Произошла ошибка. Пожалуйста, попробуйте снова. Недоступно - Предупреждение Да Адрес контракта скопирован! Доступные сети @@ -185,7 +176,6 @@ Скрывать балансы жестом переворота Эмитент Подписано - Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно. Подробности Проверьте подключение с интернетом или переключитесь на другую сеть Условия использования @@ -199,7 +189,7 @@ Выберите провайдера Произошла ошибка. Код: %s К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) - Выбранный провайдер не доступен для обмена. Попробуйте позже. (Код: %s) + Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s) В данный момент обмен невозможен. Попробуйте позже. (Код: %s) Курс обмена Обмен через %s @@ -551,11 +541,13 @@ Балансы показаны Отменить Выбранная операция в данный момент недоступна. Попробуйте позже. - В данный момент покупка монеты %s недоступна. Но мы работаем над её добавлением. - У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. - Обмен %s не доступен. Но мы работаем над его добавлением. - В данный момент продажа монеты %s недоступна. Но мы работаем над её добавлением. - Выберите адрес + В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями. + У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. + У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. + В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. + Продажа средств станет доступной после завершения транзакции(-ий) в сети %s + Отправка средств станет доступной после завершения транзакции(-ий) в сети %s + В данный момент продажа %s недоступна. Следите за нашими обновлениями. Сгенерировать XPUB Скрыть Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. @@ -666,9 +658,10 @@ Возможно, данная карта - образец или подделка Ошибка проверки подлинности Ассоциировать - Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%.4f %s + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять Ассоциируете свой токен + Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. Малое количество подписей Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. @@ -688,10 +681,10 @@ Карта уже подписывала транзакции Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше Нравится Tangem? + Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его Необходима плата за аренду сети %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. Недостаточно %1$s для оплаты комиссии сети - Отправка средств станет доступной после завершения транзакции(-ий) в сети %s Отправка средств станет доступной после завершения транзакции %s Транзакция в обработке Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 257e2e4adc..f271106776 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -4,16 +4,12 @@ Manage tokens Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. Request support - Try again This feature is disabled in Demo mode Reason: %s Can\'t send a transaction The selected does not support the %1$s network To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. Tokens in %1$s network are not supported by this card due to firmware limitation. - Thank you for your feedback. We will respond as soon as possible - Your suggestions were sent - Please try to tap the card exactly as shown in the animation or request support. Are you having difficulty scanning your card? This card is not designed to work with this app Default Fee @@ -29,8 +25,6 @@ Dark Light System default - If system is selected, the app will auto-adjust based on your device\'s system settings - System Theme App Settings To hide or show your balances, simply flip your device screen down, or switch it off in Settings @@ -56,10 +50,10 @@ Reset to Factory Settings Security Mode Card Settings - Due to the peculiarities of the Cardano network, when transacting the %1$s token, in addition to the network commission, %2$s will be charged - To make a %1$s transaction, you must deposit some %2$s (%3$s) to cover the network fee and minimum ADA value + Due to the peculiarities of the Cardano network, when transacting the %1$s token, in addition to the network commission, %2$s will be charged + To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value. (5 ADA balance recommended) Insufficient ADA to token transfer - Withdrawal of the entire ADA balance is not possible if funds are available in Cardano network tokens. First, withdraw funds on your tokens. + The Cardano network does not allow you to leave an amount on your balance below the minimum value %1$s if you have tokens. Please ensure that you have some ADA remaining. Accept Access denied Apply @@ -80,7 +74,6 @@ Create Delete Disabled - Disconnect Done Enable Enabled @@ -113,7 +106,6 @@ Reject Reload Rename - Retry Save changes Search Search tokens @@ -137,7 +129,6 @@ I understand There was an error. Please try again. Unreachable - Warning Yes Contract address copied! Available networks @@ -184,7 +175,6 @@ Flip-to-Hide Balances Issuer Signed - If you forget the code you will lose access to your funds. Code recovery is not possible. Details Check your internet connection or switch to a different network Terms of Service @@ -552,11 +542,13 @@ Balances shown Undo This operation is currently unavailable. Please try again later. - The purchase of the %s is currently unavailable. But we are working on adding it. - You do not have funds to send. Top up your account to be able to send funds from it. - %s swap is not available. But we are working on adding it. - Sell of the %s coin is currently unavailable. But we are working on adding it. - Choose address + Buying %s is not available at the moment. Please check our updates. + You do not have funds to sell. Top up your account to be able to sell funds from it. + You do not have funds to send. Top up your account to be able to send funds from it. + Swapping %s is not available at the moment. Please check our updates. + Selling funds will be available once the pending transaction(s) in network %s is complete + Sending funds will be available once the pending transaction(s) in network %s is complete + Selling %s is not available at the moment. Please check our updates. Generate XPUB Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. @@ -667,9 +659,10 @@ This card might be a production sample or counterfeit Authenticity check failed Associate - This token must be associated with your Hedera account before you can receive it. Association fee ~%.4f %s + This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s This token must be associated with your Hedera account before you can receive it Associate your token + Hot enough %s. Top up your Hedera account to associate this token Only %s signatures are left on this card. You must withdraw all of your funds. Low signature count Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. @@ -687,10 +680,10 @@ Card has already signed transactions Your review keeps us motivated to make Tangem Wallet even better Enjoying Tangem? + You must associate your token before receiving tokens Network rent fee required %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. Insufficient %1$s to cover network fee - Sending funds will be available once the pending transaction(s) in network %s is complete Sending funds will be available once the %s transaction is complete Transaction pending The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction. From a9ee84a116160b52670c39718eb20964640ccd61 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Apr 2024 22:49:34 +0200 Subject: [PATCH 58/85] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 33 +++++++++++++-------- core/res/src/main/res/values/strings.xml | 33 +++++++++++++-------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index a11d54cc42..1ac0523e66 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -4,12 +4,16 @@ Валюты Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. Обратиться в поддержку + Попробовать снова Эта функция недоступна в демонстрационном режиме Причина: %s Не могу отправить транзакцию Выбранный кошелёк не поддерживает сеть %1$s Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. + Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки. + Ваши предложения отправлены + Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, или запросите поддержку. У вас возникли трудности со сканированием карты? Эта карта не предназначена для работы с этим приложением Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. @@ -24,6 +28,8 @@ Тёмная Светлая Как в системе + При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства + Системная Тема Настройки приложения Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" @@ -52,9 +58,9 @@ Тип безопасности Настройки карты Ввиду особенностей сети Cardano при транзакции токена %1$s помимо комиссии сети будет списано %2$s - Чтобы совершить транзакцию %1$s, вы должны внести некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA. (рекомендуется баланс в размере 5 ADA) + Для совершения транзакции %1$s, вам необходимо внести немного %2$s (%3$s), чтобы покрыть комиссию сети и минимальное значение ADA для отправки. Недостаточно ADA для отправки токена - Сеть Cardano не позволяет вам оставлять на своем балансе сумму ниже минимального значения %1$s, если у вас есть токены. Пожалуйста, убедитесь, что у вас осталось немного ADA. + Вывод всего баланса ADA невозможен при наличии средств на токенах сети Cardano. Сначала выведите средства на ваших токенах. Принять Доступ запрещен Применить @@ -75,6 +81,7 @@ Создать Удалить Отключено + Отключить Готово Включить Включено @@ -107,6 +114,7 @@ Отклонить Перезагрузить Переименовать + Повторить Сохранить изменения Искать Поиск токенов @@ -130,6 +138,7 @@ Я понял Произошла ошибка. Пожалуйста, попробуйте снова. Недоступно + Предупреждение Да Адрес контракта скопирован! Доступные сети @@ -176,6 +185,7 @@ Скрывать балансы жестом переворота Эмитент Подписано + Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно. Подробности Проверьте подключение с интернетом или переключитесь на другую сеть Условия использования @@ -189,7 +199,7 @@ Выберите провайдера Произошла ошибка. Код: %s К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) - Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s) + Выбранный провайдер не доступен для обмена. Попробуйте позже. (Код: %s) В данный момент обмен невозможен. Попробуйте позже. (Код: %s) Курс обмена Обмен через %s @@ -541,13 +551,11 @@ Балансы показаны Отменить Выбранная операция в данный момент недоступна. Попробуйте позже. - В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями. - У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. - У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. - В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. - Продажа средств станет доступной после завершения транзакции(-ий) в сети %s - Отправка средств станет доступной после завершения транзакции(-ий) в сети %s - В данный момент продажа %s недоступна. Следите за нашими обновлениями. + В данный момент покупка монеты %s недоступна. Но мы работаем над её добавлением. + У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. + Обмен %s не доступен. Но мы работаем над его добавлением. + В данный момент продажа монеты %s недоступна. Но мы работаем над её добавлением. + Выберите адрес Сгенерировать XPUB Скрыть Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. @@ -658,10 +666,9 @@ Возможно, данная карта - образец или подделка Ошибка проверки подлинности Ассоциировать - Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%.4f %s Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять Ассоциируете свой токен - Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. Малое количество подписей Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. @@ -681,10 +688,10 @@ Карта уже подписывала транзакции Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше Нравится Tangem? - Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его Необходима плата за аренду сети %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. Недостаточно %1$s для оплаты комиссии сети + Отправка средств станет доступной после завершения транзакции(-ий) в сети %s Отправка средств станет доступной после завершения транзакции %s Транзакция в обработке Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index f271106776..257e2e4adc 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -4,12 +4,16 @@ Manage tokens Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. Request support + Try again This feature is disabled in Demo mode Reason: %s Can\'t send a transaction The selected does not support the %1$s network To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. Tokens in %1$s network are not supported by this card due to firmware limitation. + Thank you for your feedback. We will respond as soon as possible + Your suggestions were sent + Please try to tap the card exactly as shown in the animation or request support. Are you having difficulty scanning your card? This card is not designed to work with this app Default Fee @@ -25,6 +29,8 @@ Dark Light System default + If system is selected, the app will auto-adjust based on your device\'s system settings + System Theme App Settings To hide or show your balances, simply flip your device screen down, or switch it off in Settings @@ -50,10 +56,10 @@ Reset to Factory Settings Security Mode Card Settings - Due to the peculiarities of the Cardano network, when transacting the %1$s token, in addition to the network commission, %2$s will be charged - To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value. (5 ADA balance recommended) + Due to the peculiarities of the Cardano network, when transacting the %1$s token, in addition to the network commission, %2$s will be charged + To make a %1$s transaction, you must deposit some %2$s (%3$s) to cover the network fee and minimum ADA value Insufficient ADA to token transfer - The Cardano network does not allow you to leave an amount on your balance below the minimum value %1$s if you have tokens. Please ensure that you have some ADA remaining. + Withdrawal of the entire ADA balance is not possible if funds are available in Cardano network tokens. First, withdraw funds on your tokens. Accept Access denied Apply @@ -74,6 +80,7 @@ Create Delete Disabled + Disconnect Done Enable Enabled @@ -106,6 +113,7 @@ Reject Reload Rename + Retry Save changes Search Search tokens @@ -129,6 +137,7 @@ I understand There was an error. Please try again. Unreachable + Warning Yes Contract address copied! Available networks @@ -175,6 +184,7 @@ Flip-to-Hide Balances Issuer Signed + If you forget the code you will lose access to your funds. Code recovery is not possible. Details Check your internet connection or switch to a different network Terms of Service @@ -542,13 +552,11 @@ Balances shown Undo This operation is currently unavailable. Please try again later. - Buying %s is not available at the moment. Please check our updates. - You do not have funds to sell. Top up your account to be able to sell funds from it. - You do not have funds to send. Top up your account to be able to send funds from it. - Swapping %s is not available at the moment. Please check our updates. - Selling funds will be available once the pending transaction(s) in network %s is complete - Sending funds will be available once the pending transaction(s) in network %s is complete - Selling %s is not available at the moment. Please check our updates. + The purchase of the %s is currently unavailable. But we are working on adding it. + You do not have funds to send. Top up your account to be able to send funds from it. + %s swap is not available. But we are working on adding it. + Sell of the %s coin is currently unavailable. But we are working on adding it. + Choose address Generate XPUB Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. @@ -659,10 +667,9 @@ This card might be a production sample or counterfeit Authenticity check failed Associate - This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s + This token must be associated with your Hedera account before you can receive it. Association fee ~%.4f %s This token must be associated with your Hedera account before you can receive it Associate your token - Hot enough %s. Top up your Hedera account to associate this token Only %s signatures are left on this card. You must withdraw all of your funds. Low signature count Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. @@ -680,10 +687,10 @@ Card has already signed transactions Your review keeps us motivated to make Tangem Wallet even better Enjoying Tangem? - You must associate your token before receiving tokens Network rent fee required %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. Insufficient %1$s to cover network fee + Sending funds will be available once the pending transaction(s) in network %s is complete Sending funds will be available once the %s transaction is complete Transaction pending The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction. From 65dd90ca664e69d4da91cbae4010818d8766e3e8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Apr 2024 22:51:55 +0200 Subject: [PATCH 59/85] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 1ac0523e66..298dffbcda 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -199,7 +199,7 @@ Выберите провайдера Произошла ошибка. Код: %s К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) - Выбранный провайдер не доступен для обмена. Попробуйте позже. (Код: %s) + Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s) В данный момент обмен невозможен. Попробуйте позже. (Код: %s) Курс обмена Обмен через %s From be6caf0e4dc134c0e9ce1aafbb75f16cc3413e5d Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 12:26:22 +0500 Subject: [PATCH 60/85] Updated on 2026-08-14 --- .../tangem/features/send/impl/presentation/state/SendUiState.kt | 1 + .../presentation/state/amount/SendAmountCurrencyConverter.kt | 1 + .../impl/presentation/state/amount/SendAmountStateConverter.kt | 1 + .../presentation/state/previewdata/AmountStatePreviewData.kt | 1 + .../features/send/impl/presentation/ui/amount/AmountButtons.kt | 2 ++ .../send/impl/presentation/ui/amount/SendAmountContent.kt | 1 + 6 files changed, 7 insertions(+) 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 23453c4b4d..75d8e642e2 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 @@ -97,6 +97,7 @@ internal sealed class SendStates { val walletBalance: TextReference, val tokenIconState: TokenIconState, val segmentedButtonConfig: PersistentList, + val selectedButton: Int, val isSegmentedButtonsEnabled: Boolean, val amountTextField: SendTextField.AmountField, val appCurrencyCode: String, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt index 369918da01..fa31b63ea0 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt @@ -38,6 +38,7 @@ internal class SendAmountCurrencyConverter( keyboardType = KeyboardType.Number, ), ), + selectedButton = amountState.segmentedButtonConfig.indexOfFirst { it.isFiat == value }, ), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt index 7e3199d1ff..6afdc1ada8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt @@ -57,6 +57,7 @@ internal class SendAmountStateConverter( ), ), isSegmentedButtonsEnabled = !noFeeRate, + selectedButton = 0, ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt index d39cd3df01..e1685ad0cf 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt @@ -49,6 +49,7 @@ internal object AmountStatePreviewData { onValuePastedTriggerDismiss = {}, ), isSegmentedButtonsEnabled = true, + selectedButton = 0, ) val fiatAmountState = amountState.copy( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt index 340f8e20ad..1b6315e1ea 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt @@ -29,6 +29,7 @@ internal fun LazyListScope.buttons( segmentedButtonConfig: PersistentList, clickIntents: SendClickIntents, isSegmentedButtonsEnabled: Boolean, + selectedButton: Int, ) { item( key = AMOUNT_BUTTONS_KEY, @@ -48,6 +49,7 @@ internal fun LazyListScope.buttons( hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) clickIntents.onCurrencyChangeClick(it.isFiat) }, + initialSelectedItem = segmentedButtonConfig.getOrNull(selectedButton), isEnabled = isSegmentedButtonsEnabled, ) { SendAmountCurrencyButton( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index 44b5a0244d..01d23d4edb 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt @@ -36,6 +36,7 @@ internal fun SendAmountContent( segmentedButtonConfig = amountState.segmentedButtonConfig, clickIntents = clickIntents, isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled, + selectedButton = amountState.selectedButton, ) } } From fdf7c6eef10bb9ef0aa4d7998eccce40a8f4d7c2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 12:45:30 +0500 Subject: [PATCH 61/85] Updated on 2026-08-14 --- .../ui/components/rows/SelectorRowItem.kt | 30 ++++---- .../presentation/state/fee/FeeCalculation.kt | 8 +++ .../fee/custom/BitcoinCustomFeeConverter.kt | 49 ++++++------- .../fee/custom/EthereumCustomFeeConverter.kt | 69 ++++++++++--------- .../fields/SendAmountFieldChangeConverter.kt | 26 ++++--- .../state/fields/SendAmountFieldConverter.kt | 17 +++-- .../ui/fee/SendSpeedSelectorItem.kt | 2 +- .../impl/presentation/ui/send/AmountBlock.kt | 4 +- .../impl/presentation/ui/send/FeeBlock.kt | 2 +- .../impl/presentation/utils/FormatterUtils.kt | 6 +- 10 files changed, 118 insertions(+), 95 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt index bedec66060..b919363574 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt @@ -73,7 +73,7 @@ fun SelectorRowItem( color = TangemTheme.colors.text.primary1, modifier = Modifier.padding(start = TangemTheme.dimens.spacing8), ) - if (preDot != null && postDot != null) { + if (preDot != null) { SelectorValueContent( preDot = preDot, postDot = postDot, @@ -97,7 +97,7 @@ fun SelectorRowItem( @Composable private fun RowScope.SelectorValueContent( preDot: TextReference, - postDot: TextReference, + postDot: TextReference?, ellipsizeOffset: Int? = null, ) { val ellipsis = if (ellipsizeOffset == null) { @@ -115,18 +115,20 @@ private fun RowScope.SelectorValueContent( .weight(1f) .padding(start = TangemTheme.dimens.spacing4), ) - Text( - text = "•", - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Center, - modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing4), - ) - Text( - text = postDot.resolveReference(), - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.tertiary, - ) + if (postDot != null) { + Text( + text = "•", + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing4), + ) + Text( + text = postDot.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + ) + } } @Preview 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 43e7da9e42..5376e57084 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 @@ -2,6 +2,7 @@ package com.tangem.features.send.impl.presentation.state.fee import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.common.extensions.isZero import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.common.extensions.minimalAmount @@ -95,5 +96,12 @@ internal fun checkIfFeeTooHigh(feeSelectorState: FeeSelectorState.Content, onSho return isShow } +/** + * Checks if fee exceeds fee paid currency balance + */ +fun checkExceedBalance(feeBalance: BigDecimal?, feeAmount: BigDecimal?): Boolean { + return feeAmount == null || feeBalance == null || feeAmount.isZero() || feeAmount > feeBalance +} + private val FEE_MAX_DIFF = BigDecimal("5") private const val ZERO_DECIMALS = 0 \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt index 5594d8d917..a2a13e987a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt @@ -5,18 +5,16 @@ 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.StateRouter +import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import com.tangem.features.send.impl.presentation.utils.getFiatReference import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils.isBitcoin import com.tangem.utils.Provider @@ -35,6 +33,7 @@ internal class BitcoinCustomFeeConverter( override fun convert(value: Fee.Bitcoin): ImmutableList { val feeValue = value.amount.value + val feeCurrency = feeCryptoCurrencyStatusProvider()?.value val network = feeCryptoCurrencyStatusProvider()?.currency?.network?.id?.value return if (network != null && isBitcoin(network)) { persistentListOf( @@ -49,7 +48,11 @@ internal class BitcoinCustomFeeConverter( ), title = resourceReference(R.string.send_max_fee), footer = resourceReference(R.string.send_max_fee_footer), - label = getFeeFormatted(feeValue), + label = getFiatReference( + rate = feeCurrency?.fiatRate, + value = feeValue, + appCurrency = appCurrencyProvider(), + ), keyboardActions = KeyboardActions(), isReadonly = true, ), @@ -65,7 +68,15 @@ internal class BitcoinCustomFeeConverter( footer = resourceReference(R.string.send_satoshi_per_byte_text), onValueChange = { clickIntents.onCustomFeeValueChange(FEE_SATOSHI_INDEX, it) }, keyboardOptions = KeyboardOptions( - imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done, + imeAction = if (checkExceedBalance( + feeBalance = feeCurrency?.amount, + feeAmount = feeValue, + ) + ) { + ImeAction.None + } else { + ImeAction.Done + }, keyboardType = KeyboardType.Number, ), keyboardActions = KeyboardActions( @@ -104,7 +115,11 @@ internal class BitcoinCustomFeeConverter( FEE_AMOUNT_INDEX, this[FEE_AMOUNT_INDEX].copy( value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT_INDEX].decimals), - label = getFeeFormatted(newFeeAmount), + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmount, + appCurrency = appCurrencyProvider(), + ), ), ) set(index, this[index].copy(value = value)) @@ -112,26 +127,6 @@ internal class BitcoinCustomFeeConverter( }.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( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt index 9428dbdfc0..6bed843250 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt @@ -5,24 +5,21 @@ 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.StateRouter +import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import com.tangem.features.send.impl.presentation.utils.getFiatReference 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 EthereumCustomFeeConverter( @@ -34,6 +31,7 @@ internal class EthereumCustomFeeConverter( override fun convert(value: Fee.Ethereum): ImmutableList { val feeValue = value.amount.value + val feeCurrency = feeCryptoCurrencyStatusProvider()?.value return persistentListOf( SendTextField.CustomFee( value = feeValue?.parseBigDecimal(value.amount.decimals).orEmpty(), @@ -46,7 +44,11 @@ internal class EthereumCustomFeeConverter( ), title = resourceReference(R.string.send_max_fee), footer = resourceReference(R.string.send_max_fee_footer), - label = getFeeFormatted(feeValue), + label = getFiatReference( + rate = feeCurrency?.fiatRate, + value = feeValue, + appCurrency = appCurrencyProvider(), + ), keyboardActions = KeyboardActions(), ), SendTextField.CustomFee( @@ -70,7 +72,16 @@ internal class EthereumCustomFeeConverter( footer = resourceReference(R.string.send_gas_limit_footer), onValueChange = { clickIntents.onCustomFeeValueChange(GAS_LIMIT, it) }, keyboardOptions = KeyboardOptions( - imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done, + imeAction = if ( + checkExceedBalance( + feeBalance = feeCurrency?.amount, + feeAmount = feeValue, + ) + ) { + ImeAction.None + } else { + ImeAction.Done + }, keyboardType = KeyboardType.Number, ), keyboardActions = KeyboardActions( @@ -106,26 +117,6 @@ internal class EthereumCustomFeeConverter( }.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 MutableList.setEmpty(index: Int) { set(index, this[index].copy(value = "")) } @@ -144,7 +135,11 @@ internal class EthereumCustomFeeConverter( index, this[index].copy( value = value, - label = getFeeFormatted(newFeeAmountDecimal), + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmountDecimal, + appCurrency = appCurrencyProvider(), + ), ), ) } @@ -163,7 +158,11 @@ internal class EthereumCustomFeeConverter( FEE_AMOUNT, this[FEE_AMOUNT].copy( value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), - label = getFeeFormatted(newFeeAmount), + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmount, + appCurrency = appCurrencyProvider(), + ), ), ) set(index, this[index].copy(value = value)) @@ -183,15 +182,23 @@ internal class EthereumCustomFeeConverter( FEE_AMOUNT, this[FEE_AMOUNT].copy( value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), - label = getFeeFormatted(newFeeAmount), + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmount, + appCurrency = appCurrencyProvider(), + ), ), ) + val isNotExceedBalance = checkExceedBalance( + feeBalance = feeCryptoCurrencyStatusProvider()?.value?.amount, + feeAmount = newFeeAmount, + ) set( index, this[index].copy( value = value, keyboardOptions = KeyboardOptions( - imeAction = if (!checkExceedBalance(newFeeAmount)) ImeAction.None else ImeAction.Done, + imeAction = if (!isNotExceedBalance) ImeAction.None else ImeAction.Done, keyboardType = KeyboardType.Number, ), ), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index 04c375714e..86d87bf3cc 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -11,6 +11,7 @@ import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.utils.Provider import com.tangem.utils.converter.Converter +import com.tangem.utils.isNullOrZero import java.math.BigDecimal import java.math.RoundingMode @@ -31,13 +32,15 @@ internal class SendAmountFieldChangeConverter( val trimmedValue = value.trim() val cryptoValue = trimmedValue.getCryptoValue(amountTextField.isFiatValue, cryptoDecimals) - val fiatValue = trimmedValue.getFiatValue(amountTextField.isFiatValue, fiatDecimals) val decimalCryptoValue = cryptoValue.parseToBigDecimal(cryptoDecimals) - val decimalFiatValue = fiatValue.parseToBigDecimal(fiatDecimals) + val (fiatValue, decimalFiatValue) = trimmedValue.getFiatValue( + isFiatValue = amountTextField.isFiatValue, + decimals = fiatDecimals, + ) val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue val isExceedBalance = checkValue.checkExceedBalance(amountTextField) - val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isZero() else decimalCryptoValue.isZero() + val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else decimalCryptoValue.isZero() return state.copyWrapped( isEditState = isEditState, amountState = amountState.copy( @@ -68,13 +71,18 @@ internal class SendAmountFieldChangeConverter( } } - private fun String.getFiatValue(isFiatValue: Boolean, decimals: Int): String { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val fiatRate = cryptoCurrencyStatus.value.fiatRate - return if (!isFiatValue && fiatRate != null) { - parseToBigDecimal(decimals).multiply(fiatRate).parseBigDecimal(decimals) + private fun String.getFiatValue(isFiatValue: Boolean, decimals: Int): Pair { + val fiatRate = cryptoCurrencyStatusProvider().value.fiatRate + return if (fiatRate != null) { + val fiatValue = if (!isFiatValue) { + parseToBigDecimal(decimals).multiply(fiatRate).parseBigDecimal(decimals) + } else { + this + } + val decimalFiatValue = fiatValue.parseToBigDecimal(decimals) + fiatValue to decimalFiatValue } else { - this + "" to null } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt index 14f7f4b660..8bf30c0b57 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt @@ -18,6 +18,7 @@ import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter +import com.tangem.utils.isNullOrZero import java.math.BigDecimal private const val FIAT_DECIMALS = 2 @@ -34,12 +35,14 @@ internal class SendAmountFieldConverter( val cryptoDecimal = value.toBigDecimalOrDefault() val cryptoAmount = cryptoDecimal.convertToAmount(cryptoCurrencyStatus.currency) val fiatRate = cryptoCurrencyStatus.value.fiatRate - val (fiatValue, fiatDecimal) = if (value.isEmpty()) { - "" to BigDecimal.ZERO - } else { - val fiatDecimal = fiatRate?.multiply(cryptoDecimal) ?: BigDecimal.ZERO - val fiatValue = fiatDecimal.parseBigDecimal(FIAT_DECIMALS) - fiatValue to fiatDecimal + val (fiatValue, fiatDecimal) = when { + fiatRate.isNullOrZero() -> "" to null + value.isEmpty() -> "" to BigDecimal.ZERO + else -> { + val fiatDecimal = fiatRate?.multiply(cryptoDecimal) + val fiatValue = fiatDecimal?.parseBigDecimal(FIAT_DECIMALS).orEmpty() + fiatValue to fiatDecimal + } } val isDoneActionEnabled = !cryptoDecimal.isZero() return SendTextField.AmountField( @@ -64,7 +67,7 @@ internal class SendAmountFieldConverter( ) } - private fun getAppCurrencyAmount(fiatValue: BigDecimal, appCurrency: AppCurrency) = Amount( + private fun getAppCurrencyAmount(fiatValue: BigDecimal?, appCurrency: AppCurrency) = Amount( currencySymbol = appCurrency.symbol, value = fiatValue, decimals = FIAT_DECIMALS, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt index 0a2a65c93e..c42e7e152d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt @@ -54,7 +54,7 @@ internal fun SendSpeedSelectorItem( onSelect = onSelect, modifier = modifier, preDot = getCryptoReference(amount, state.isFeeApproximate), - postDot = getFiatReference(amount, state.rate, state.appCurrency), + postDot = getFiatReference(amount?.value, state.rate, state.appCurrency), ellipsizeOffset = amount?.currencySymbol?.length, isSelected = content?.selectedFee == feeType, showDivider = showDivider, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt index 85ff055ed6..ab68759908 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt @@ -31,8 +31,8 @@ internal fun AmountBlock( val amount = amountState.amountTextField val cryptoAmount = BigDecimalFormatter.formatWithSymbol(amount.value, amount.cryptoAmount.currencySymbol) - val fiatAmount = BigDecimalFormatter.formatFiatEditableAmount( - fiatAmount = amount.fiatValue, + val fiatAmount = BigDecimalFormatter.formatFiatAmount( + fiatAmount = amount.fiatAmount.value, fiatCurrencySymbol = amount.fiatAmount.currencySymbol, fiatCurrencyCode = amountState.appCurrencyCode, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt index 11423e7c73..30814b802f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt @@ -60,7 +60,7 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick titleRes = title, iconRes = icon, preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate), - postDot = feeAmount?.let { getFiatReference(it, feeState.rate, feeState.appCurrency) }, + postDot = getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency), ellipsizeOffset = feeAmount?.currencySymbol?.length, isSelected = true, showDivider = false, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt index 022d0c5711..e21fe8c0c5 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt @@ -22,11 +22,11 @@ internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): Tex ) } -internal fun getFiatReference(amount: Amount?, rate: BigDecimal?, appCurrency: AppCurrency): TextReference? { - if (amount == null) return null +internal fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): TextReference? { + if (value == null || rate == null) return null return stringReference( BigDecimalFormatter.formatFiatAmount( - fiatAmount = rate?.let { amount.value?.multiply(it) }, + fiatAmount = value.multiply(rate), fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol, ), From 754a599f9fed732fb0f8057a913192f3e573fe4c Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 14:59:00 +0500 Subject: [PATCH 62/85] Updated on 2026-08-14 --- .../impl/presentation/viewmodel/SendViewModel.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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 3212417a3e..4ad08c9264 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 @@ -734,14 +734,12 @@ internal class SendViewModel @Inject constructor( val recipientState = uiState.getRecipientState(isFromConfirmation) ?: return null val amount = amountState.amountTextField.cryptoAmount.value ?: return null - return feeCryptoCurrencyStatus?.let { feeCurrencyStatus -> - getFeeUseCase.invoke( - amount = amount, - destination = recipientState.addressTextField.value, - userWallet = userWallet, - cryptoCurrency = feeCurrencyStatus.currency, - ) - } + return getFeeUseCase.invoke( + amount = amount, + destination = recipientState.addressTextField.value, + userWallet = userWallet, + cryptoCurrency = cryptoCurrencyStatus.currency, + ) } // endregion From 355e0bf0ef120e83958bed1a22f3b1b25d8530ad Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 17:57:29 +0500 Subject: [PATCH 63/85] Updated on 2026-08-14 --- .../ui/components/fields/SimpleTextField.kt | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index 577485da0d..ff2bf968ae 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -57,7 +57,7 @@ fun SimpleTextField( handleColor = TangemTheme.colors.text.accent, backgroundColor = TangemTheme.colors.text.accent.copy(alpha = 0.3f), ) - + val textFieldValue = textFieldValueState.copy(text = value) var lastTextValue by remember(proxyValue, isValuePasted) { textFieldValueState = textFieldValueState.copy( text = proxyValue, @@ -70,6 +70,13 @@ fun SimpleTextField( mutableStateOf(proxyValue) } + val isSelectionChanged by rememberSelectionChanged(textFieldValue, textFieldValueState) + LaunchedEffect(key1 = isSelectionChanged) { + if (isSelectionChanged) { + textFieldValueState = textFieldValue + } + } + // resets paste value cursor trigger LaunchedEffect(key1 = isValuePasted) { if (isValuePasted) { @@ -79,7 +86,7 @@ fun SimpleTextField( CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) { BasicTextField( - value = textFieldValueState, + value = textFieldValue, onValueChange = { newTextFieldValueState -> textFieldValueState = newTextFieldValueState @@ -131,4 +138,14 @@ private fun SimpleTextPlaceholder( } textValue() } +} + +@Composable +private fun rememberSelectionChanged(textFieldValue: TextFieldValue, textFieldValueState: TextFieldValue) = remember { + derivedStateOf { + val isSelectionChanged = textFieldValue.selection != textFieldValueState.selection || + textFieldValue.composition != textFieldValueState.composition + val isTextNotChanged = textFieldValue.text == textFieldValueState.text + isSelectionChanged && isTextNotChanged + } } \ No newline at end of file From 622c14e401eb244b8c5ed379b49093e1aa1e8dea Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 17:58:03 +0500 Subject: [PATCH 64/85] Updated on 2026-08-14 --- .../presentation/state/SendStateFactory.kt | 22 ++++++++++++++++++- .../impl/presentation/state/SendUiState.kt | 1 + .../presentation/ui/SendNavigationButtons.kt | 6 ++++- .../presentation/viewmodel/SendViewModel.kt | 5 ++++- 4 files changed, 31 insertions(+), 3 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 caab9c9dc0..631cbdf100 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 @@ -18,6 +18,7 @@ import com.tangem.features.send.impl.presentation.state.common.SendSyncEditConve import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter +import com.tangem.features.send.impl.presentation.state.fee.checkFeeCoverage import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientHistoryListConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter @@ -28,8 +29,9 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import timber.log.Timber +import java.math.BigDecimal -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") internal class SendStateFactory( private val clickIntents: SendClickIntents, private val stateRouterProvider: Provider, @@ -95,6 +97,7 @@ internal class SendStateFactory( isEditingDisabled = false, isBalanceHidden = false, cryptoCurrencyName = "", + isSubtracted = false, ) fun getReadyState(): SendUiState { @@ -280,6 +283,23 @@ internal class SendStateFactory( //endregion //region send + fun getIsAmountSubtractedState(isAmountSubtractAvailable: Boolean): SendUiState { + val state = currentStateProvider() + val balance = cryptoCurrencyStatusProvider().value.amount ?: return state + val amountState = state.getAmountState(stateRouterProvider().isEditState) ?: return state + val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return state + val amountValue = amountState.amountTextField.cryptoAmount.value ?: return state + val feeValue = feeState.fee?.amount?.value ?: BigDecimal.ZERO + return state.copy( + isSubtracted = checkFeeCoverage( + isSubtractAvailable = isAmountSubtractAvailable, + balance = balance, + amountValue = amountValue, + feeValue = feeValue, + ), + ) + } + fun getSendingStateUpdate(isSending: Boolean): SendUiState { val state = currentStateProvider() return state.copy( 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 23453c4b4d..3ae999f99b 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 @@ -32,6 +32,7 @@ internal data class SendUiState( val editRecipientState: SendStates.RecipientState? = null, val editFeeState: SendStates.FeeState? = null, val isBalanceHidden: Boolean, + val isSubtracted: Boolean, val event: StateEvent, ) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index ace8e220f8..b0b37b764d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -165,7 +165,11 @@ private fun SendingText( val fiatRate = feeState?.rate val fiatAmount = amountState?.amountTextField?.fiatAmount val feeFiat = fiatRate?.let { feeState.fee?.amount?.value?.multiply(it) } - val sendingFiat = feeFiat?.let { fiatAmount?.value?.plus(it) } + val sendingFiat = if (uiState.isSubtracted) { + fiatAmount?.value + } else { + feeFiat?.let { fiatAmount?.value?.plus(it) } + } if (feeFiat != null && sendingFiat != null) { val sendingValue = BigDecimalFormatter.formatFiatAmount( 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 4ad08c9264..50f368670f 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 @@ -465,7 +465,10 @@ internal class SendViewModel @Inject constructor( SendUiStateType.Fee, SendUiStateType.EditFee, -> loadFee() - SendUiStateType.Send -> sendIdleTimer = SystemClock.elapsedRealtime() + SendUiStateType.Send -> { + uiState = stateFactory.getIsAmountSubtractedState(isAmountSubtractAvailable) + sendIdleTimer = SystemClock.elapsedRealtime() + } else -> Unit } } From 8c3071ed1b08b05ec3c336faa7381d22651dc7ab Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 21:32:59 +0500 Subject: [PATCH 65/85] Updated on 2026-08-14 --- .../operations/CurrenciesStatusesOperations.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index fac9f7fc95..d12ca4c9ad 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -264,10 +264,18 @@ internal class CurrenciesStatusesOperations( var quotesRetrievingFailed = false val networksStatuses = maybeNetworkStatuses?.bind()?.toNonEmptySetOrNull() - val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) { - quotesRetrievingFailed = true - null - } + val quotes: Set? = maybeQuotes?.fold( + ifLeft = { + quotesRetrievingFailed = true + null + }, + ifRight = { + it.ifEmpty { + quotesRetrievingFailed = true + null + } + }, + ) currencies.map { currency -> val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId } From 390513dc53efc13b8936702347a4fe48b46dfd89 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 21:33:50 +0500 Subject: [PATCH 66/85] Updated on 2026-08-14 --- .../tangem/data/transaction/DefaultTransactionRepository.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index 76b16b1409..3fe078eaad 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -71,7 +71,10 @@ internal class DefaultTransactionRepository( } Blockchain.Binance -> BinanceTransactionExtras(memo) Blockchain.XRP -> memo.toLongOrNull()?.let { XrpTransactionBuilder.XrpTransactionExtras(it) } - Blockchain.Cosmos -> CosmosTransactionExtras(memo) + Blockchain.Cosmos, + Blockchain.TerraV1, + Blockchain.TerraV2, + -> CosmosTransactionExtras(memo) Blockchain.TON -> TonTransactionExtras(memo) Blockchain.Hedera -> HederaTransactionBuilder.HederaTransactionExtras(memo) Blockchain.Algorand -> AlgorandTransactionExtras(memo) From 54cc9e6245787948f1847e1ddd8405ee0c267536 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 21:34:59 +0500 Subject: [PATCH 67/85] Updated on 2026-08-14 --- .../presentation/state/confirm/SendNotificationFactory.kt | 5 +++-- .../send/impl/presentation/state/fee/FeeCalculation.kt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) 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 1de2ea58fb..74abacad98 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 @@ -71,7 +71,7 @@ internal class SendNotificationFactory( buildList { // errors addFeeUnreachableNotification(feeState.feeSelectorState) - addExceedBalanceNotification(feeValue, sendingAmount) + addExceedBalanceNotification(feeValue, sendingAmount, isFeeCoverage) addExceedsBalanceNotification(feeState.fee) addDustWarningNotification(feeValue, sendingAmount) addTransactionLimitErrorNotification(feeValue, sendingAmount) @@ -107,11 +107,12 @@ internal class SendNotificationFactory( private fun MutableList.addExceedBalanceNotification( feeAmount: BigDecimal, receivedAmount: BigDecimal, + isFeeCoverage: Boolean, ) { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - if (!isSubtractAvailableProvider()) return + if (isFeeCoverage) return val showNotification = receivedAmount + feeAmount > balance if (showNotification) { 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 5376e57084..c7f5a024df 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 @@ -45,7 +45,7 @@ internal fun checkFeeCoverage( feeValue: BigDecimal, ): Boolean { if (!isSubtractAvailable) return false - return balance < amountValue + feeValue && balance > feeValue + return balance < amountValue + feeValue && balance > feeValue && balance >= amountValue } /** From 31145bbbe77028041537d232324ad969d00a663b Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 21:35:25 +0500 Subject: [PATCH 68/85] Updated on 2026-08-14 --- .../tangem/features/send/impl/presentation/state/StateRouter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt index 5f2201052b..e09467fa9c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt @@ -34,7 +34,7 @@ internal class StateRouter( when { isSuccess -> popBackStack() isEditingDisabled -> when (type) { - SendUiStateType.Send -> showFee() + SendUiStateType.EditFee -> showSend() else -> popBackStack() } else -> when (type) { From ab246b2ec618c89af40e3904705a31fb5d861fac Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 21:36:47 +0500 Subject: [PATCH 69/85] Updated on 2026-08-14 --- .../features/send/impl/presentation/viewmodel/SendViewModel.kt | 1 + 1 file changed, 1 insertion(+) 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 50f368670f..f47a2a6fab 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 @@ -702,6 +702,7 @@ internal class SendViewModel @Inject constructor( val isShowStatus = uiState.feeState?.fee == null if (isShowStatus) { uiState = feeStateFactory.onFeeOnLoadingState() + updateNotifications() } val result = callFeeUseCase()?.fold( ifRight = { From 34cf43fc578f12893ed1a122db52bc993321c486 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 21:37:18 +0500 Subject: [PATCH 70/85] Updated on 2026-08-14 --- .../features/send/impl/presentation/viewmodel/SendViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f47a2a6fab..eca7088154 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 @@ -667,7 +667,7 @@ internal class SendViewModel @Inject constructor( private fun autoNextFromRecipient(type: EnterAddressSource?, isValidAddress: Boolean) { val isRecent = type == EnterAddressSource.RecentAddress - if (isRecent && isValidAddress) onNextClick() + if (isRecent && isValidAddress) onNextClick(stateRouter.isEditState) } // endregion From 6acd8d98d192b2b16a93c4c10c8936e3a409ef9b Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 May 2024 13:44:39 +0500 Subject: [PATCH 71/85] Updated on 2026-08-14 --- .../presentation/state/confirm/SendNotificationFactory.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 74abacad98..1de2ea58fb 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 @@ -71,7 +71,7 @@ internal class SendNotificationFactory( buildList { // errors addFeeUnreachableNotification(feeState.feeSelectorState) - addExceedBalanceNotification(feeValue, sendingAmount, isFeeCoverage) + addExceedBalanceNotification(feeValue, sendingAmount) addExceedsBalanceNotification(feeState.fee) addDustWarningNotification(feeValue, sendingAmount) addTransactionLimitErrorNotification(feeValue, sendingAmount) @@ -107,12 +107,11 @@ internal class SendNotificationFactory( private fun MutableList.addExceedBalanceNotification( feeAmount: BigDecimal, receivedAmount: BigDecimal, - isFeeCoverage: Boolean, ) { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - if (isFeeCoverage) return + if (!isSubtractAvailableProvider()) return val showNotification = receivedAmount + feeAmount > balance if (showNotification) { From 224f40c4884c3224c3dacb216371ea9883e892e4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 May 2024 13:46:07 +0500 Subject: [PATCH 72/85] Updated on 2026-08-14 --- .../impl/presentation/state/SendUiState.kt | 1 + .../state/amount/AmountStateFactory.kt | 10 +++ .../presentation/state/amount/AmountUtils.kt | 59 +++++++++++++++++ .../amount/SendAmountReducedConverter.kt | 62 ++++++++++++++++++ .../confirm/SendConfirmStateConverter.kt | 1 + .../state/confirm/SendNotificationFactory.kt | 4 +- .../presentation/state/fee/FeeCalculation.kt | 18 +++--- .../fields/SendAmountFieldChangeConverter.kt | 63 ++++--------------- .../state/previewdata/SendClickIntentsStub.kt | 2 +- .../viewmodel/SendClickIntents.kt | 2 +- .../presentation/viewmodel/SendViewModel.kt | 5 +- 11 files changed, 160 insertions(+), 67 deletions(-) create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountUtils.kt create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReducedConverter.kt 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 fd3364e729..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 @@ -141,6 +141,7 @@ internal sealed class SendStates { val transactionDate: Long, val txUrl: String, val ignoreAmountReduce: Boolean, + val reduceAmountBy: 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/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt index e91f30356f..6ff84cf35d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt @@ -6,6 +6,7 @@ import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldMaxAmountConverter import com.tangem.utils.Provider +import java.math.BigDecimal /** * Factory to produce amount state for [SendUiState] @@ -44,9 +45,18 @@ internal class AmountStateFactory( currentStateProvider = currentStateProvider, ) } + private val amountReducedConverter by lazy { + SendAmountReducedConverter( + stateRouterProvider = stateRouterProvider, + currentStateProvider = currentStateProvider, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value) + fun getOnAmountReducedState(reduceAmountBy: BigDecimal) = amountReducedConverter.convert(reduceAmountBy) + fun getOnMaxAmountClick(): SendUiState { return amountFieldMaxAmountConverter.convert(Unit) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountUtils.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountUtils.kt new file mode 100644 index 0000000000..3921429828 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountUtils.kt @@ -0,0 +1,59 @@ +package com.tangem.features.send.impl.presentation.state.amount + +import androidx.compose.ui.text.input.ImeAction +import com.tangem.common.extensions.isZero +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import java.math.BigDecimal +import java.math.RoundingMode + +internal fun String.getCryptoValue(fiatRate: BigDecimal?, isFiatValue: Boolean, decimals: Int): String { + return if (isFiatValue && fiatRate != null) { + parseToBigDecimal(decimals).divide(fiatRate, decimals, RoundingMode.DOWN) + .parseBigDecimal(decimals) + } else { + this + } +} + +internal fun String.getFiatValue( + fiatRate: BigDecimal?, + isFiatValue: Boolean, + decimals: Int, +): Pair { + return if (fiatRate != null) { + val fiatValue = if (!isFiatValue) { + parseToBigDecimal(decimals).multiply(fiatRate).parseBigDecimal(decimals) + } else { + this + } + val decimalFiatValue = fiatValue.parseToBigDecimal(decimals) + fiatValue to decimalFiatValue + } else { + "" to null + } +} + +internal fun String.checkExceedBalance( + cryptoCurrencyStatus: CryptoCurrencyStatus, + amountTextField: SendTextField.AmountField, +): Boolean { + val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO + val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals) + val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals) + return if (amountTextField.isFiatValue) { + fiatDecimal > currencyFiatAmount + } else { + cryptoDecimal > currencyCryptoAmount + } +} + +internal fun getKeyboardAction(isExceedBalance: Boolean, decimalCryptoValue: BigDecimal) = + if (!isExceedBalance && !decimalCryptoValue.isZero()) { + ImeAction.Done + } else { + ImeAction.None + } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReducedConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReducedConverter.kt new file mode 100644 index 0000000000..d624608b14 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReducedConverter.kt @@ -0,0 +1,62 @@ +package com.tangem.features.send.impl.presentation.state.amount + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.common.extensions.isZero +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.StateRouter +import com.tangem.utils.Provider +import com.tangem.utils.converter.Converter +import com.tangem.utils.isNullOrZero +import java.math.BigDecimal + +internal class SendAmountReducedConverter( + private val stateRouterProvider: Provider, + private val currentStateProvider: Provider, + private val cryptoCurrencyStatusProvider: Provider, +) : Converter { + override fun convert(value: BigDecimal): SendUiState { + val state = currentStateProvider() + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val isEditState = stateRouterProvider().isEditState + val amountState = state.getAmountState(isEditState) ?: return state + val amountTextField = amountState.amountTextField + val cryptoDecimals = amountTextField.cryptoAmount.decimals + val fiatDecimals = amountTextField.fiatAmount.decimals + val amountValue = amountState.amountTextField.cryptoAmount.value ?: return state + + val decimalCryptoValue = amountValue.minus(value) + val cryptoValue = decimalCryptoValue.parseBigDecimal(cryptoDecimals) + val (fiatValue, decimalFiatValue) = cryptoValue.getFiatValue( + fiatRate = cryptoCurrencyStatus.value.fiatRate, + isFiatValue = false, + decimals = fiatDecimals, + ) + + val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue + val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField) + val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else decimalCryptoValue.isZero() + return state.copyWrapped( + isEditState = isEditState, + sendState = state.sendState?.copy( + reduceAmountBy = value, + ), + amountState = amountState.copy( + isPrimaryButtonEnabled = !isExceedBalance && !isZero, + amountTextField = amountTextField.copy( + value = cryptoValue, + fiatValue = fiatValue, + isError = isExceedBalance, + cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), + fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), + keyboardOptions = KeyboardOptions( + imeAction = getKeyboardAction(isExceedBalance, decimalCryptoValue), + keyboardType = KeyboardType.Number, + ), + ), + ), + ) + } +} \ No newline at end of file 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 7935da04c8..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 @@ -16,6 +16,7 @@ internal class SendConfirmStateConverter( transactionDate = 0L, txUrl = "", ignoreAmountReduce = false, + reduceAmountBy = 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 1de2ea58fb..c98336636c 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 @@ -93,6 +93,7 @@ internal class SendNotificationFactory( return state.copy( sendState = sendState.copy( ignoreAmountReduce = isIgnored, + reduceAmountBy = if (isIgnored) null else sendState.reduceAmountBy, notifications = updatedNotifications.toImmutableList(), ), ) @@ -225,8 +226,7 @@ internal class SendNotificationFactory( SendNotification.Warning.HighFeeError( amount = threshold.toPlainString(), onConfirmClick = { - val reduceTo = sendAmount.minus(threshold) - clickIntents.onAmountReduceClick(reduceTo, SendNotification.Warning.HighFeeError::class.java) + clickIntents.onAmountReduceClick(threshold, SendNotification.Warning.HighFeeError::class.java) }, onCloseClick = { clickIntents.onNotificationCancel(SendNotification.Warning.HighFeeError::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 c7f5a024df..b06d42a23d 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 @@ -1,13 +1,10 @@ package com.tangem.features.send.impl.presentation.state.fee -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.extensions.isZero import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal -import com.tangem.domain.common.extensions.minimalAmount import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.lib.crypto.BlockchainUtils import java.math.BigDecimal import java.math.RoundingMode @@ -19,6 +16,7 @@ internal fun checkAndCalculateSubtractedAmount( cryptoCurrencyStatus: CryptoCurrencyStatus, amountValue: BigDecimal, feeValue: BigDecimal, + reduceAmountBy: BigDecimal?, ): BigDecimal { val balance = cryptoCurrencyStatus.value.amount ?: return amountValue val isFeeCoverage = checkFeeCoverage( @@ -27,12 +25,17 @@ internal fun checkAndCalculateSubtractedAmount( amountValue = amountValue, feeValue = feeValue, ) - return calculateSubtractedAmount( + val subtractedAmount = calculateSubtractedAmount( isFeeCoverage = isFeeCoverage, cryptoCurrencyStatus = cryptoCurrencyStatus, amountValue = amountValue, feeValue = feeValue, ) + return if (reduceAmountBy != null) { + subtractedAmount.minus(reduceAmountBy) + } else { + subtractedAmount + } } /** @@ -59,12 +62,7 @@ internal fun calculateSubtractedAmount( ): BigDecimal { val balance = cryptoCurrencyStatus.value.amount ?: return amountValue return if (isFeeCoverage) { - var subtractedValue = minOf(amountValue, balance.minus(feeValue)) - if (BlockchainUtils.isTezos(cryptoCurrencyStatus.currency.network.id.value)) { - val threshold = Blockchain.Tezos.minimalAmount() - subtractedValue = -threshold - } - subtractedValue + minOf(amountValue, balance.minus(feeValue)) } else { amountValue } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index 86d87bf3cc..3958e3b64a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -1,19 +1,20 @@ package com.tangem.features.send.impl.presentation.state.fields import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.common.extensions.isZero -import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.StateRouter +import com.tangem.features.send.impl.presentation.state.amount.checkExceedBalance +import com.tangem.features.send.impl.presentation.state.amount.getCryptoValue +import com.tangem.features.send.impl.presentation.state.amount.getFiatValue +import com.tangem.features.send.impl.presentation.state.amount.getKeyboardAction import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import com.tangem.utils.isNullOrZero import java.math.BigDecimal -import java.math.RoundingMode internal class SendAmountFieldChangeConverter( private val stateRouterProvider: Provider, @@ -22,6 +23,7 @@ internal class SendAmountFieldChangeConverter( ) : Converter { override fun convert(value: String): SendUiState { val state = currentStateProvider() + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val isEditState = stateRouterProvider().isEditState val amountState = state.getAmountState(isEditState) ?: return state val amountTextField = amountState.amountTextField @@ -31,15 +33,20 @@ internal class SendAmountFieldChangeConverter( val fiatDecimals = amountTextField.fiatAmount.decimals val trimmedValue = value.trim() - val cryptoValue = trimmedValue.getCryptoValue(amountTextField.isFiatValue, cryptoDecimals) + val cryptoValue = trimmedValue.getCryptoValue( + fiatRate = cryptoCurrencyStatus.value.fiatRate, + isFiatValue = amountTextField.isFiatValue, + decimals = cryptoDecimals, + ) val decimalCryptoValue = cryptoValue.parseToBigDecimal(cryptoDecimals) val (fiatValue, decimalFiatValue) = trimmedValue.getFiatValue( + fiatRate = cryptoCurrencyStatus.value.fiatRate, isFiatValue = amountTextField.isFiatValue, decimals = fiatDecimals, ) val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue - val isExceedBalance = checkValue.checkExceedBalance(amountTextField) + val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField) val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else decimalCryptoValue.isZero() return state.copyWrapped( isEditState = isEditState, @@ -60,32 +67,6 @@ internal class SendAmountFieldChangeConverter( ) } - private fun String.getCryptoValue(isFiatValue: Boolean, decimals: Int): String { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val fiatRate = cryptoCurrencyStatus.value.fiatRate - return if (isFiatValue && fiatRate != null) { - parseToBigDecimal(decimals).divide(fiatRate, decimals, RoundingMode.DOWN) - .parseBigDecimal(decimals) - } else { - this - } - } - - private fun String.getFiatValue(isFiatValue: Boolean, decimals: Int): Pair { - val fiatRate = cryptoCurrencyStatusProvider().value.fiatRate - return if (fiatRate != null) { - val fiatValue = if (!isFiatValue) { - parseToBigDecimal(decimals).multiply(fiatRate).parseBigDecimal(decimals) - } else { - this - } - val decimalFiatValue = fiatValue.parseToBigDecimal(decimals) - fiatValue to decimalFiatValue - } else { - "" to null - } - } - private fun SendUiState.emptyState(): SendUiState { val isEditState = stateRouterProvider().isEditState val amountState = getAmountState(isEditState) ?: return this @@ -104,24 +85,4 @@ internal class SendAmountFieldChangeConverter( ), ) } - - private fun String.checkExceedBalance(amountTextField: SendTextField.AmountField): Boolean { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO - val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals) - val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals) - return if (amountTextField.isFiatValue) { - fiatDecimal > currencyFiatAmount - } else { - cryptoDecimal > currencyCryptoAmount - } - } - - private fun getKeyboardAction(isExceedBalance: Boolean, decimalCryptoValue: BigDecimal) = - if (!isExceedBalance && !decimalCryptoValue.isZero()) { - ImeAction.Done - } else { - ImeAction.None - } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 84a2a7b90d..4f05dd6c30 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -60,7 +60,7 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onShareClick() {} - override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) {} + override fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class) {} override fun onNotificationCancel(clazz: Class) {} } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index 76a22a3b24..61c33cb023 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -67,7 +67,7 @@ internal interface SendClickIntents { fun onShareClick() - fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) + fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class) fun onNotificationCancel(clazz: Class) // endregion 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 eca7088154..0273e12490 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 @@ -797,8 +797,8 @@ internal class SendViewModel @Inject constructor( analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked) } - override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) { - uiState = amountStateFactory.getOnAmountValueChange(reducedAmount.parseBigDecimal(cryptoCurrency.decimals)) + override fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class) { + uiState = amountStateFactory.getOnAmountReducedState(reduceAmountBy) uiState = sendNotificationFactory.dismissNotificationState(clazz) feeReload() } @@ -820,6 +820,7 @@ internal class SendViewModel @Inject constructor( cryptoCurrencyStatus = cryptoCurrencyStatus, amountValue = amountValue, feeValue = feeValue, + reduceAmountBy = uiState.sendState?.reduceAmountBy, ) viewModelScope.launch(dispatchers.main) { From c881e8d6dbd6fafe569e1c4fe12a29cc8643206d Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 May 2024 17:36:26 +0500 Subject: [PATCH 73/85] Updated on 2026-08-14 --- .../ui/fee/SendSpeedSelectorItem.kt | 49 ++++++++++++++----- .../impl/presentation/ui/send/FeeBlock.kt | 2 +- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt index c42e7e152d..d9c609a5ca 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt @@ -4,15 +4,14 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.animation.* import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.res.TangemTheme @@ -59,25 +58,50 @@ internal fun SendSpeedSelectorItem( isSelected = content?.selectedFee == feeType, showDivider = showDivider, ) - SendSpeedSelectorItemError(isError = feeSelectorState is FeeSelectorState.Error) + FeeLoading(feeSelectorState) + FeeError(feeSelectorState) } } } @Composable -private fun SendSpeedSelectorItemError(isError: Boolean) { +private fun FeeLoading(feeSelectorState: FeeSelectorState) { Row { SpacerWMax() AnimatedVisibility( - visible = isError, - label = "Error state indication animation", - enter = fadeIn(), - exit = fadeOut(), + visible = feeSelectorState == FeeSelectorState.Loading, + label = "Fee Loading State Change", + modifier = Modifier.align(Alignment.CenterVertically), + ) { + RectangleShimmer( + radius = TangemTheme.dimens.radius3, + modifier = Modifier + .padding( + vertical = TangemTheme.dimens.spacing18, + horizontal = TangemTheme.dimens.spacing12, + ) + .size( + height = TangemTheme.dimens.size12, + width = TangemTheme.dimens.size90, + ), + ) + } + } +} + +@Composable +private fun FeeError(feeSelectorState: FeeSelectorState) { + Row { + SpacerWMax() + AnimatedVisibility( + visible = feeSelectorState == FeeSelectorState.Error, + label = "Fee Error State Change", + modifier = Modifier.align(Alignment.CenterVertically), ) { Text( text = BigDecimalFormatter.EMPTY_BALANCE_SIGN, - style = TangemTheme.typography.body2, color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.body2, modifier = Modifier .padding( vertical = TangemTheme.dimens.spacing14, @@ -101,10 +125,9 @@ private fun FeeSelectorState.Content.getAmount(feeType: FeeType): Amount? { private fun FeeSelectorState.Content?.getDividerAndVisibility(feeType: FeeType): Pair { val hasCustomValues = !this?.customValues.isNullOrEmpty() val isNotSingle = this?.fees !is TransactionFee.Single - val isLoaded = this?.fees != null return when (feeType) { FeeType.Slow -> true to isNotSingle - FeeType.Market -> isNotSingle to isLoaded + FeeType.Market -> isNotSingle to true FeeType.Fast -> hasCustomValues to isNotSingle FeeType.Custom -> false to hasCustomValues } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt index 30814b802f..54ef73296a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt @@ -32,7 +32,7 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick .fillMaxWidth() .clip(TangemTheme.shapes.roundedCornersXMedium) .background(TangemTheme.colors.background.action) - .clickable(enabled = !isSuccess && feeState.fee != null) { onClick() } + .clickable(enabled = !isSuccess, onClick = onClick) .padding(TangemTheme.dimens.spacing12), ) { Text( From 770b175ab6d9bc2fc7dbbaa6e38bbdbcd4b2001e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 May 2024 19:29:09 +0500 Subject: [PATCH 74/85] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 5bdbdc3921..75242285e6 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-612" +tangemBlockchainSdk = "release-app_5.9.1-615" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 182ea8e160fa8c8924337f6abfc75dabdc137cc5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 May 2024 19:29:09 +0500 Subject: [PATCH 75/85] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 5bdbdc3921..75242285e6 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-612" +tangemBlockchainSdk = "release-app_5.9.1-615" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 1415297f58667ebb2c492063f4e834f3ccb75fb6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 11:18:10 +0300 Subject: [PATCH 76/85] Updated on 2026-08-14 --- .../presentation/ui/SendNavigationButtons.kt | 22 +++++++++---------- .../send/impl/presentation/ui/SendScreen.kt | 1 + .../presentation/ui/amount/AmountField.kt | 7 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index b0b37b764d..e858d9b150 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -1,12 +1,10 @@ package com.tangem.features.send.impl.presentation.ui import androidx.compose.animation.* +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -21,18 +19,19 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.tangem.core.ui.R +import com.tangem.core.ui.components.Keyboard import com.tangem.core.ui.components.SecondaryButtonIconStart import com.tangem.core.ui.components.SpacerW12 import com.tangem.core.ui.components.buttons.common.TangemButton import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults +import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.extensions.shareText import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.SendUiStateType -import kotlinx.coroutines.delay @Composable internal fun SendNavigationButtons( @@ -144,11 +143,12 @@ private fun SendingText( modifier: Modifier = Modifier, ) { var isVisibleProxy by remember { mutableStateOf(isVisible) } + val keyboard by keyboardAsState() - // text appearance delay for smooth screen transitions - LaunchedEffect(key1 = isVisible) { - if (isVisible) { - delay(timeMillis = 400) + // the text should appear when the keyboard is closed + LaunchedEffect(isVisible, keyboard) { + if (isVisible && keyboard is Keyboard.Opened) { + return@LaunchedEffect } isVisibleProxy = isVisible } @@ -156,8 +156,8 @@ private fun SendingText( AnimatedVisibility( visible = isVisibleProxy, modifier = modifier, - enter = slideInVertically().plus(fadeIn()), - exit = slideOutVertically().plus(fadeOut()), + enter = slideInVertically() + fadeIn(), + exit = fadeOut(tween(durationMillis = 300)), label = "Animate show sending state text", ) { val amountState = uiState.getAmountState(isEditState) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index 5c7eba689d..d6529709b7 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -134,6 +134,7 @@ private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentS Box(modifier = modifier.fillMaxSize()) { AnimatedContent( targetState = currentStateProxy, + contentAlignment = Alignment.TopCenter, label = "Send Scree Navigation", transitionSpec = { val direction = if (initialState.type.ordinal < targetState.type.ordinal) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt index 640189da93..8f7a69c8c1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt @@ -22,7 +22,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.rememberDecimalFormat import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import kotlinx.coroutines.job +import kotlinx.coroutines.delay @Composable internal fun AmountField(sendField: SendTextField.AmountField, appCurrencyCode: String) { @@ -66,9 +66,8 @@ internal fun AmountField(sendField: SendTextField.AmountField, appCurrencyCode: ) LaunchedEffect(key1 = Unit) { - this.coroutineContext.job.invokeOnCompletion { - requester.requestFocus() - } + delay(timeMillis = 200) + requester.requestFocus() } AmountSecondary(sendField, appCurrencyCode) From 2377322a3424f87b0adce9c45a390a1d5f9a4b9e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 12:56:05 +0500 Subject: [PATCH 77/85] Updated on 2026-08-14 --- .../impl/presentation/state/SendUiState.kt | 1 + .../state/amount/AmountStateFactory.kt | 14 ++++- ...rter.kt => SendAmountReduceByConverter.kt} | 2 +- .../amount/SendAmountReduceToConverter.kt | 60 +++++++++++++++++++ .../confirm/SendConfirmStateConverter.kt | 1 + .../state/confirm/SendNotificationFactory.kt | 9 ++- .../state/previewdata/SendClickIntentsStub.kt | 6 +- .../viewmodel/SendClickIntents.kt | 6 +- .../presentation/viewmodel/SendViewModel.kt | 13 +++- 9 files changed, 101 insertions(+), 11 deletions(-) rename features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/{SendAmountReducedConverter.kt => SendAmountReduceByConverter.kt} (98%) create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt 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 c7aa7ddff0..84adf93793 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,6 +142,7 @@ 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/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt index 6ff84cf35d..715c696b71 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt @@ -45,8 +45,15 @@ internal class AmountStateFactory( currentStateProvider = currentStateProvider, ) } - private val amountReducedConverter by lazy { - SendAmountReducedConverter( + private val amountReduceByConverter by lazy { + SendAmountReduceByConverter( + stateRouterProvider = stateRouterProvider, + currentStateProvider = currentStateProvider, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } + private val amountReduceToConverter by lazy { + SendAmountReduceToConverter( stateRouterProvider = stateRouterProvider, currentStateProvider = currentStateProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, @@ -55,7 +62,8 @@ internal class AmountStateFactory( fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value) - fun getOnAmountReducedState(reduceAmountBy: BigDecimal) = amountReducedConverter.convert(reduceAmountBy) + fun getOnAmountReduceByState(reduceAmountBy: BigDecimal) = amountReduceByConverter.convert(reduceAmountBy) + fun getOnAmountReduceToState(reduceAmountTo: BigDecimal) = amountReduceToConverter.convert(reduceAmountTo) fun getOnMaxAmountClick(): SendUiState { return amountFieldMaxAmountConverter.convert(Unit) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReducedConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceByConverter.kt similarity index 98% rename from features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReducedConverter.kt rename to features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceByConverter.kt index d624608b14..59dadf2844 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReducedConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceByConverter.kt @@ -12,7 +12,7 @@ import com.tangem.utils.converter.Converter import com.tangem.utils.isNullOrZero import java.math.BigDecimal -internal class SendAmountReducedConverter( +internal class SendAmountReduceByConverter( private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, 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 new file mode 100644 index 0000000000..5b40022bbb --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt @@ -0,0 +1,60 @@ +package com.tangem.features.send.impl.presentation.state.amount + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.common.extensions.isZero +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.StateRouter +import com.tangem.utils.Provider +import com.tangem.utils.converter.Converter +import com.tangem.utils.isNullOrZero +import java.math.BigDecimal + +internal class SendAmountReduceToConverter( + private val stateRouterProvider: Provider, + private val currentStateProvider: Provider, + private val cryptoCurrencyStatusProvider: Provider, +) : Converter { + override fun convert(value: BigDecimal): SendUiState { + val state = currentStateProvider() + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val isEditState = stateRouterProvider().isEditState + val amountState = state.getAmountState(isEditState) ?: return state + val amountTextField = amountState.amountTextField + val cryptoDecimals = amountTextField.cryptoAmount.decimals + val fiatDecimals = amountTextField.fiatAmount.decimals + + val cryptoValue = value.parseBigDecimal(cryptoDecimals) + val (fiatValue, decimalFiatValue) = cryptoValue.getFiatValue( + fiatRate = cryptoCurrencyStatus.value.fiatRate, + isFiatValue = false, + decimals = fiatDecimals, + ) + + val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue + val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField) + 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( + value = cryptoValue, + fiatValue = fiatValue, + isError = isExceedBalance, + cryptoAmount = amountTextField.cryptoAmount.copy(value = value), + fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), + keyboardOptions = KeyboardOptions( + imeAction = getKeyboardAction(isExceedBalance, value), + keyboardType = KeyboardType.Number, + ), + ), + ), + ) + } +} \ No newline at end of file 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 254a979807..6e6e67132b 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,6 +17,7 @@ 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 c98336636c..4f4766450f 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 @@ -166,8 +166,8 @@ internal class SendNotificationFactory( ), onConfirmClick = { clickIntents.onAmountReduceClick( - utxoLimit.maxAmount, - SendNotification.Error.TransactionLimitError::class.java, + reduceAmountTo = utxoLimit.maxAmount, + clazz = SendNotification.Error.TransactionLimitError::class.java, ) }, ), @@ -226,7 +226,10 @@ internal class SendNotificationFactory( SendNotification.Warning.HighFeeError( amount = threshold.toPlainString(), onConfirmClick = { - clickIntents.onAmountReduceClick(threshold, SendNotification.Warning.HighFeeError::class.java) + clickIntents.onAmountReduceClick( + reduceAmountBy = threshold, + clazz = SendNotification.Warning.HighFeeError::class.java, + ) }, onCloseClick = { clickIntents.onNotificationCancel(SendNotification.Warning.HighFeeError::class.java) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 4f05dd6c30..0fedb031f2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -60,7 +60,11 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onShareClick() {} - override fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class) {} + override fun onAmountReduceClick( + reduceAmountBy: BigDecimal?, + reduceAmountTo: BigDecimal?, + clazz: Class, + ) {} override fun onNotificationCancel(clazz: Class) {} } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index 61c33cb023..32ba81ce73 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -67,7 +67,11 @@ internal interface SendClickIntents { fun onShareClick() - fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class) + fun onAmountReduceClick( + reduceAmountBy: BigDecimal? = null, + reduceAmountTo: BigDecimal? = null, + clazz: Class, + ) fun onNotificationCancel(clazz: Class) // endregion 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 0273e12490..5a6260d448 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 @@ -797,8 +797,17 @@ internal class SendViewModel @Inject constructor( analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked) } - override fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class) { - uiState = amountStateFactory.getOnAmountReducedState(reduceAmountBy) + override fun onAmountReduceClick( + reduceAmountBy: BigDecimal?, + reduceAmountTo: BigDecimal?, + clazz: Class, + ) { + uiState = when { + reduceAmountBy != null -> amountStateFactory.getOnAmountReduceByState(reduceAmountBy) + reduceAmountTo != null -> amountStateFactory.getOnAmountReduceToState(reduceAmountTo) + else -> return + } + uiState = sendNotificationFactory.dismissNotificationState(clazz) feeReload() } From eeca735cc0a4d5f8cbc47e94c5ef07507365932e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 13:07:32 +0100 Subject: [PATCH 78/85] Updated on 2026-08-14 --- .../com/tangem/core/analytics/Analytics.kt | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt b/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt index 3c12cf80a8..790de00ea3 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt @@ -4,6 +4,8 @@ import com.tangem.core.analytics.api.* import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import java.util.concurrent.Executors /** @@ -22,6 +24,7 @@ object Analytics : GlobalAnalyticsEventHandler { private val handlers = mutableMapOf() private val paramsInterceptors = mutableMapOf() private val analyticsFilters = mutableSetOf() + private val analyticsMutex = Mutex() private val analyticsHandlers: List get() = handlers.values.toList() @@ -55,23 +58,26 @@ object Analytics : GlobalAnalyticsEventHandler { event.params = applyParamsInterceptors(event) val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(event) } - when { - eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(event) } - eventFilter.canBeSent(event) -> { - analyticsHandlers - .filter { handler -> eventFilter.canBeConsumedByHandler(handler, event) } - .forEach { handler -> handler.send(event) } + analyticsMutex.withLock { + when { + eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(event) } + eventFilter.canBeSent(event) -> { + analyticsHandlers + .filter { handler -> eventFilter.canBeConsumedByHandler(handler, event) } + .forEach { handler -> handler.send(event) } + } } } } } - private fun applyParamsInterceptors(event: AnalyticsEvent): MutableMap { + private suspend fun applyParamsInterceptors(event: AnalyticsEvent): MutableMap { val interceptedParams = event.params.toMutableMap() - paramsInterceptors.values - .filter { it.canBeAppliedTo(event) } - .forEach { it.intercept(interceptedParams) } - + analyticsMutex.withLock { + paramsInterceptors.values + .filter { it.canBeAppliedTo(event) } + .forEach { it.intercept(interceptedParams) } + } return interceptedParams } From 5be919da589d6cba9e0f448adf62c28ca3453015 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:02:18 +0500 Subject: [PATCH 79/85] Updated on 2026-08-14 --- .../features/send/impl/presentation/ui/send/RecipientBlock.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt index 0801e63cd6..e3b92db56c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt @@ -35,6 +35,7 @@ internal fun RecipientBlock( Column( modifier = Modifier + .fillMaxWidth() .clip(TangemTheme.shapes.roundedCornersXMedium) .background(backgroundColor) .clickable(enabled = !isSuccess && !isEditingDisabled, onClick = onClick) From bca687b217149e8ad4c00619c19416016677d881 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:02:45 +0500 Subject: [PATCH 80/85] Updated on 2026-08-14 --- .../impl/presentation/utils/FormatterUtils.kt | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt index e21fe8c0c5..73ef044814 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt @@ -5,8 +5,13 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.combinedReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.utils.BigDecimalFormatter.EMPTY_BALANCE_SIGN import com.tangem.domain.appcurrency.model.AppCurrency import java.math.BigDecimal +import java.math.RoundingMode + +private const val FIAT_DECIMALS = 2 +private const val FEE_MINIMUM_VALUE = 0.01 internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): TextReference? { if (amount == null) return null @@ -24,11 +29,31 @@ internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): Tex internal fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): TextReference? { if (value == null || rate == null) return null - return stringReference( + val formattedFiat = getFiatString(value = value, rate = rate, appCurrency = appCurrency) + return stringReference(formattedFiat) +} + +internal fun getFiatString(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): String { + if (value == null || rate == null) return EMPTY_BALANCE_SIGN + val feeValue = value.multiply(rate) + val scaled = feeValue.setScale(FIAT_DECIMALS, RoundingMode.UP) ?: BigDecimal.ZERO + val formattedValue = if (scaled < BigDecimal(FEE_MINIMUM_VALUE)) { + buildString { + append(BigDecimalFormatter.CAN_BE_LOWER_SIGN) + append( + BigDecimalFormatter.formatFiatAmount( + fiatAmount = BigDecimal(FEE_MINIMUM_VALUE), + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ), + ) + } + } else { BigDecimalFormatter.formatFiatAmount( - fiatAmount = value.multiply(rate), + fiatAmount = feeValue, fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol, - ), - ) + ) + } + return formattedValue } \ No newline at end of file From 467d62aa4fc50e767a919af5c50be32c4c02d9f6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:06:38 +0500 Subject: [PATCH 81/85] Updated on 2026-08-14 --- .../impl/presentation/state/SendNotification.kt | 6 +++++- .../state/confirm/SendNotificationFactory.kt | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt index 3334f3faaa..2df3421a1a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt @@ -94,9 +94,13 @@ internal sealed class SendNotification(val config: NotificationConfig) { }, ) - data class ExistentialDeposit(val deposit: String) : Error( + data class ExistentialDeposit(val deposit: String, val onConfirmClick: () -> Unit) : Error( title = resourceReference(R.string.send_notification_existential_deposit_title), subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)), + buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig( + text = resourceReference(R.string.send_notification_existential_deposit_button, wrappedList(deposit)), + onClick = onConfirmClick, + ), ) } 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 4f4766450f..9ac1b7a5a7 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 @@ -7,6 +7,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.extensions.networkIconResId import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.minimalAmount import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase @@ -19,6 +20,8 @@ import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.state.fee.* +import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import com.tangem.features.send.impl.presentation.utils.getFiatString import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils.isTezos import com.tangem.utils.Provider @@ -30,7 +33,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import java.math.BigDecimal -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") internal class SendNotificationFactory( private val cryptoCurrencyStatusProvider: Provider, private val coinCryptoCurrencyStatusProvider: Provider, @@ -193,13 +196,19 @@ internal class SendNotificationFactory( cryptoCurrency.network, ) val diff = balance.minus(spendingAmount) - if (currencyDeposit != null && currencyDeposit > diff) { + if (currencyDeposit != null && diff >= BigDecimal.ZERO && currencyDeposit > diff) { add( SendNotification.Error.ExistentialDeposit( - BigDecimalFormatter.formatCryptoAmountUncapped( + deposit = BigDecimalFormatter.formatCryptoAmountUncapped( cryptoAmount = currencyDeposit, cryptoCurrency = cryptoCurrency, ), + onConfirmClick = { + clickIntents.onAmountReduceClick( + reduceAmountBy = currencyDeposit, + clazz = SendNotification.Error.ExistentialDeposit::class.java, + ) + }, ), ) } From a0c9cb20e5205a17c05a9d8707d6be9b0fa3a6f7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:07:07 +0500 Subject: [PATCH 82/85] Updated on 2026-08-14 --- .../presentation/state/SendNotification.kt | 7 ++-- .../state/confirm/SendNotificationFactory.kt | 35 ++++++++++++++++--- .../presentation/viewmodel/SendViewModel.kt | 1 + 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt index 2df3421a1a..a22b3ee620 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt @@ -153,9 +153,12 @@ internal sealed class SendNotification(val config: NotificationConfig) { ), ) - data object FeeCoverageNotification : Warning( + data class FeeCoverageNotification(val cryptoAmount: String, val fiatAmount: String) : Warning( title = resourceReference(R.string.send_network_fee_warning_title), - subtitle = resourceReference(R.string.swapping_network_fee_warning_content), + subtitle = resourceReference( + R.string.send_network_fee_warning_content, + wrappedList(cryptoAmount, fiatAmount), + ), ) } } \ No newline at end of file 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 9ac1b7a5a7..4b30a9466f 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 @@ -42,6 +42,7 @@ internal class SendNotificationFactory( private val currencyChecksRepository: CurrencyChecksRepository, private val stateRouterProvider: Provider, private val isSubtractAvailableProvider: Provider, + private val appCurrencyProvider: Provider, private val clickIntents: SendClickIntents, private val analyticsEventHandler: AnalyticsEventHandler, private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, @@ -80,7 +81,11 @@ internal class SendNotificationFactory( addTransactionLimitErrorNotification(feeValue, sendingAmount) // warnings addExistentialWarningNotification(feeValue, amountValue) - addFeeCoverageNotification(isFeeCoverage) + addFeeCoverageNotification( + isFeeCoverage = isFeeCoverage, + amountField = amountState.amountTextField, + sendingValue = sendingAmount, + ) addHighFeeWarningNotification(amountValue, sendState.ignoreAmountReduce) addTooHighNotification(feeState.feeSelectorState) addTooLowNotification(feeState) @@ -214,10 +219,32 @@ internal class SendNotificationFactory( } } - private fun MutableList.addFeeCoverageNotification(sendingAmount: Boolean) { - if (sendingAmount) { + private fun MutableList.addFeeCoverageNotification( + isFeeCoverage: Boolean, + amountField: SendTextField.AmountField, + sendingValue: BigDecimal, + ) { + if (isFeeCoverage) { analyticsEventHandler.send(SendAnalyticEvents.NoticeFeeCoverage) - add(SendNotification.Warning.FeeCoverageNotification) + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val cryptoCurrency = cryptoCurrencyStatus.currency + val fiatRate = cryptoCurrencyStatus.value.fiatRate + val amountValue = amountField.cryptoAmount.value ?: return + + val cryptoDiff = amountValue.minus(sendingValue) + add( + SendNotification.Warning.FeeCoverageNotification( + cryptoAmount = BigDecimalFormatter.formatCryptoAmountUncapped( + cryptoAmount = cryptoDiff, + cryptoCurrency = cryptoCurrency, + ), + fiatAmount = getFiatString( + value = cryptoDiff, + rate = fiatRate, + appCurrency = appCurrencyProvider(), + ), + ), + ) } } 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 5a6260d448..5410b54f17 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 @@ -168,6 +168,7 @@ internal class SendViewModel @Inject constructor( userWalletProvider = Provider { userWallet }, stateRouterProvider = Provider { stateRouter }, isSubtractAvailableProvider = Provider { isAmountSubtractAvailable }, + appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), currencyChecksRepository = currencyChecksRepository, clickIntents = this, analyticsEventHandler = analyticsEventHandler, From f7ed6f88df4a9bf043e0400493682e73a4a65efc Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 21:08:09 +0500 Subject: [PATCH 83/85] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 12 +++++++----- core/res/src/main/res/values/strings.xml | 13 +++++++------ gradle/dependencies.toml | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 298dffbcda..f12dc6ee9c 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -470,16 +470,17 @@ Комиссия не превысит Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение. Допустим ввод только цифр - Сумма отправки будет уменьшена на %1$s для покрытия выбранного уровня комиссии. Получателю будет отправлено %2$s. + Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии Покрытие сетевой комиссии Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса Недостаточно средств - Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, убедитесь, что остаток после отправки будет не менее %s. + Оставить %s + Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе. Экзистенциальный депозит Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. Установлена высокая комиссия - Комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %s. - Комиссия увеличилась + Ввиду особенности сети Tezos комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %s. + Комиссия повышена Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению Недопустимая сумма Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %2$s. @@ -500,7 +501,8 @@ Отправить Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. Мои кошельки - Это способ измерения комиссии за отправку биткоин-транзакции. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый байт данных в транзакции. Чем выше это число, тем быстрее будет обработана транзакция сетью. + Способ измерения комиссии за биткоин-транзакцию. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый виртуальный байт в транзакции. Чем выше число, тем быстрее будет обработана транзакция майнерами. + Сатоши / вбайт Отправка Нажмите на любое поле, чтобы изменить его Отправка %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 257e2e4adc..1b3c413136 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -467,16 +467,17 @@ Max fee The fee that will be charged for your transaction. You can set your own value. Numbers only for Destination Tag - Sending amount will be reduced by %1$s to cover the selected commission level. The recipient will get %2$s. + Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level Network fee coverage Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance Total exceeds balance - The account will be wiped from the blockchain if a balance goes below the existential deposit. Please ensure that the remaining balance after sending will not be less than %s. + Leave %s + The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance. Existential deposit The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. Custom fee is high - The fee for transferring the entire balance is higher. To reduce the commission, you can leave %s. - Fee is increased + Due to the peculiarities of the Tezos network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %s. + The fee is higher The included commission exceeds the transfer amount, leading to a negative value Invalid amount The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s. @@ -499,8 +500,8 @@ Send to A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds My wallets - The fee for a Bitcoin transaction is measured by the number of the smallest Bitcoin unit (Satoshi) per byte of data. The higher this number, the faster the transaction will be processed. - Satoshi per vbyte + A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners. + Satoshi / vByte Sending... Tap any field to change it Send %s diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 75242285e6..80bda90466 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9.1-615" +tangemBlockchainSdk = "release-app_5.10-618" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 27b71e7def9501eaec3b45eabb3018809f4a3c22 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 5 May 2024 21:35:53 +0100 Subject: [PATCH 84/85] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index fdf0660901..07cfce37ff 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit fdf0660901442ade296db42bb867fc5c16a14262 +Subproject commit 07cfce37ff84e70081aca82d9948acb13c5c07b0 From af862e7ef1bf2074ef2680095d77bef4c24663a1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 5 May 2024 22:00:47 +0100 Subject: [PATCH 85/85] Updated on 2026-08-14 --- .../impl/data/TangemApiTokensPagingSource.kt | 2 +- .../state/confirm/SendNotificationFactory.kt | 24 ------------------- .../presentation/state/fee/FeeCalculation.kt | 1 - .../presentation/ui/amount/AmountField.kt | 1 - .../presentation/viewmodel/SendViewModel.kt | 1 - 5 files changed, 1 insertion(+), 28 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt index f9390fc4a2..dcacf9fef2 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt @@ -3,10 +3,10 @@ package com.tangem.tap.features.tokens.impl.data import androidx.paging.PagingSource import androidx.paging.PagingState import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.isSupportedInApp import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.domain.common.extensions.isSupportedInApp import com.tangem.domain.common.extensions.supportedBlockchains import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase 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 0cdc5ba539..baa881f4d6 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 @@ -10,8 +10,6 @@ import com.tangem.core.ui.extensions.networkIconResId import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.domain.common.extensions.minimalAmount import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -250,13 +248,6 @@ internal class SendNotificationFactory( } } - private fun MutableList.addFeeCoverageNotification(sendingAmount: Boolean) { - if (sendingAmount) { - analyticsEventHandler.send(SendAnalyticEvents.NoticeFeeCoverage) - add(SendNotification.Warning.FeeCoverageNotification) - } - } - private fun MutableList.addHighFeeWarningNotification( sendAmount: BigDecimal, ignoreAmountReduce: Boolean, @@ -284,21 +275,6 @@ internal class SendNotificationFactory( } } - // todo remove in [REDACTED_TASK_KEY] - private fun MutableList.addMinimumAmountErrorNotification( - feeAmount: BigDecimal, - receivedAmount: BigDecimal, - ) { - val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider() - val minimum = BigDecimal(DOGECOIN_MINIMUM) - - val isDogecoin = isDogecoin(coinCryptoCurrencyStatus.currency.network.id.value) - val isExceedDustLimit = checkDustLimits(feeAmount, receivedAmount, minimum) - if (isDogecoin && isExceedDustLimit) { - add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM)) - } - } - private suspend fun MutableList.addDustWarningNotification( feeAmount: BigDecimal, receivedAmount: BigDecimal, 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 c0be8a0756..b06d42a23d 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 @@ -1,6 +1,5 @@ package com.tangem.features.send.impl.presentation.state.fee -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.extensions.isZero import com.tangem.core.ui.utils.parseBigDecimal diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt index c77f28309b..8f7a69c8c1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt @@ -23,7 +23,6 @@ import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.rememberDecimalFormat import com.tangem.features.send.impl.presentation.state.fields.SendTextField import kotlinx.coroutines.delay -import kotlinx.coroutines.job @Composable internal fun AmountField(sendField: SendTextField.AmountField, appCurrencyCode: String) { 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 72e1de02e6..5410b54f17 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 @@ -557,7 +557,6 @@ internal class SendViewModel @Inject constructor( ) return true } - val feeSelectorState = uiState.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false return checkIfFeeTooHigh( feeSelectorState = feeSelectorState, onShow = { diff ->