From f6a3716a86cc2cec3d8e8ed0dc0729e96c77910b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Apr 2024 20:47:44 +0500 Subject: [PATCH 01/18] 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 02/18] 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 03/18] 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 04/18] 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 05/18] 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 06/18] 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 07/18] 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 08/18] 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 09/18] 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 10/18] 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 11/18] 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 12/18] 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 13/18] 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 14/18] 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 15/18] 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 16/18] 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 17/18] 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 18/18] 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 ^