From cff0f51c84f8e6c0c1a6884fc9df9952d0e8343d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 6 Feb 2026 15:09:48 +0300 Subject: [PATCH 01/24] Updated on 2026-08-14 --- .../usecase/gasless/GetFeeForGaslessUseCase.kt | 18 +++++++++++++----- .../usecase/gasless/TokenFeeCalculator.kt | 13 ++++++++++++- .../v2/feeselector/model/FeeSelectorLogic.kt | 5 ++++- gradle/tangem_dependencies.toml | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/GetFeeForGaslessUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/GetFeeForGaslessUseCase.kt index 1bb6b4074c..8400491228 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/GetFeeForGaslessUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/GetFeeForGaslessUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.transaction.usecase.gasless import arrow.core.Either +import arrow.core.getOrElse import arrow.core.raise.Raise import arrow.core.raise.catch import arrow.core.raise.either @@ -128,25 +129,32 @@ class GetFeeForGaslessUseCase( } ?: raiseIllegalStateError("native currency not found for network ${network.id}") val nativeBalance = nativeCurrencyStatus.value.amount ?: BigDecimal.ZERO - return if (nativeBalance >= feeValue) { + val nativeCoinSelectedResult = TransactionFeeExtended(transactionFee = initialFee, feeTokenId = nativeCurrencyStatus.currency.id) + return if (nativeBalance >= feeValue) { + nativeCoinSelectedResult } else { findTokensToPayFee( walletManager = walletManager, initialTxFee = initialFee, nativeCurrencyStatus = nativeCurrencyStatus, networkCurrenciesStatuses = networkCurrenciesStatuses, - ) + ).getOrElse { error -> + when (error) { + GaslessError.NotEnoughFunds -> nativeCoinSelectedResult + else -> raise(error) + } + } } } @Suppress("NullableToStringCall") - private suspend fun Raise.findTokensToPayFee( + private suspend fun findTokensToPayFee( walletManager: EthereumWalletManager, initialTxFee: TransactionFee, nativeCurrencyStatus: CryptoCurrencyStatus, networkCurrenciesStatuses: List, - ): TransactionFeeExtended { + ): Either = either { val initialFee = initialTxFee.normal as? Fee.Ethereum ?: raiseIllegalStateError( error = "only Fee.Ethereum supported, but was ${initialTxFee.normal::class.qualifiedName}", @@ -178,6 +186,6 @@ class GetFeeForGaslessUseCase( tokenForPayFeeStatus = tokenForPayFeeStatus, nativeCurrencyStatus = nativeCurrencyStatus, initialFee = initialFee, - ).bind() + ) } } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/TokenFeeCalculator.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/TokenFeeCalculator.kt index cec62f3d5c..451be7a94b 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/TokenFeeCalculator.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/TokenFeeCalculator.kt @@ -6,6 +6,7 @@ import arrow.core.raise.either import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.blockchains.ethereum.tokenmethods.TransferERC20TokenCallData import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.Fee @@ -89,6 +90,7 @@ internal class TokenFeeCalculator( } } + @Suppress("LongMethod", "CyclomaticComplexMethod") suspend fun calculateTokenFee( walletManager: EthereumWalletManager, tokenForPayFeeStatus: CryptoCurrencyStatus, @@ -119,8 +121,17 @@ internal class TokenFeeCalculator( ) val feeTransferGasLimit = when (feeTransferGasLimitResult) { - is Result.Failure -> raise(GaslessError.DataError(feeTransferGasLimitResult.error)) is Result.Success -> feeTransferGasLimitResult.data + is Result.Failure -> { + // If there is a dust on the balance, the gas limit estimation will fail with code + if (feeTransferGasLimitResult.error is BlockchainSdkError.WrappedThrowable) { + val cause = feeTransferGasLimitResult.error.cause + if (cause is BlockchainSdkError.Ethereum.InsufficientFundsForOperation) { + raise(GaslessError.NotEnoughFunds) + } + } + raise(GaslessError.DataError(feeTransferGasLimitResult.error)) + } }.increaseByPercent(PERCENT_TO_INCREASE_TRANSFER_GASLIMIT) val baseGas = gaslessTransactionRepository.getBaseGasForTransaction() diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorLogic.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorLogic.kt index b39a4b4717..a3cbaac500 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorLogic.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorLogic.kt @@ -31,6 +31,8 @@ import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorRelo import com.tangem.features.send.v2.api.subcomponents.feeSelector.analytics.CommonSendFeeAnalyticEvents import com.tangem.features.send.v2.api.subcomponents.feeSelector.analytics.CommonSendFeeAnalyticEvents.GasPriceInserter import com.tangem.features.send.v2.feeselector.model.transformers.* +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.saveIn import com.tangem.utils.transformer.update import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -62,6 +64,7 @@ internal class FeeSelectorLogic @AssistedInject constructor( ) : FeeSelectorIntents { private var appCurrency: AppCurrency = AppCurrency.Default + private val loadFeeJobHolder = JobHolder() val uiState = MutableStateFlow(params.state) val isGaslessEnabled = sendFeatureToggles.isGaslessTransactionsEnabled && @@ -113,7 +116,7 @@ internal class FeeSelectorLogic @AssistedInject constructor( ) }, ) - } + }.saveIn(loadFeeJobHolder) } private fun isFeeApproximate(amountType: AmountType): Boolean { diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 2318c92aa6..0758ec2945 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.33-1413" +tangemBlockchainSdk = "releases-5.33-1421" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.33-576" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From b34ba3d88230d6dc51296f75fe46d0fd8e92c5c9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 6 Feb 2026 15:38:26 +0300 Subject: [PATCH 02/24] Updated on 2026-08-14 --- .../com/tangem/feature/swap/DefaultSwapComponent.kt | 13 ++++++++++--- .../java/com/tangem/feature/swap/model/SwapModel.kt | 5 +++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt index 1ab3e27465..6bb735f4f8 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt @@ -30,9 +30,11 @@ import com.tangem.features.feed.components.market.details.portfolio.add.AddToPor import com.tangem.features.send.v2.api.SendFeatureToggles import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents import com.tangem.features.swap.SwapComponent +import com.tangem.utils.extensions.isZero import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import java.math.BigDecimal @Suppress("UnusedPrivateMember") internal class DefaultSwapComponent @AssistedInject constructor( @@ -104,10 +106,15 @@ internal class DefaultSwapComponent @AssistedInject constructor( val dataState by model.dataStateStateFlow.collectAsStateWithLifecycle() val fromCryptoCurrency by remember { derivedStateOf { dataState.fromCryptoCurrency } } val feePaidCryptoCurrency by remember { derivedStateOf { dataState.feePaidCryptoCurrency } } - val amount by remember { derivedStateOf { dataState.amount } } + val shouldHideBlock by remember { + derivedStateOf { + dataState.amount.isNullOrBlank() || BigDecimal(dataState.amount).isZero() || + model.uiState.isInsufficientFunds + } + } - LaunchedEffect(fromCryptoCurrency, feePaidCryptoCurrency, amount.isNullOrBlank()) { - if (amount.isNullOrBlank()) { + LaunchedEffect(fromCryptoCurrency, feePaidCryptoCurrency, shouldHideBlock) { + if (shouldHideBlock) { slotNavigation.dismiss() return@LaunchedEffect } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 17d79935d2..ee96daa1cd 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -2200,6 +2200,11 @@ internal class SwapModel @Inject constructor( return } + if (newState is FeeSelectorUM.Error) { + state.value = newState.copy(isHidden = true) + return + } + state.value = newState // If fee currency is same as from currency, we need to reload quotes to update fee info From 8deed07408c8ed62112f164a704f3d108dbf93e8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 6 Feb 2026 18:26:50 +0500 Subject: [PATCH 03/24] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 5 ++ core/res/src/main/res/values-es/strings.xml | 2 + core/res/src/main/res/values-fr/strings.xml | 9 ++++ core/res/src/main/res/values-it/strings.xml | 2 + core/res/src/main/res/values-ja/strings.xml | 2 + core/res/src/main/res/values-ru/strings.xml | 13 +++++ .../src/main/res/values-uk-rUA/strings.xml | 2 + .../src/main/res/values-zh-rTW/strings.xml | 2 + core/res/src/main/res/values/strings.xml | 5 ++ .../TangemPayTxHistoryItemStatusConverter.kt | 1 + .../visa/model/TangemPayTxHistoryItem.kt | 1 + .../TangemPayTxHistoryDetailsConverter.kt | 50 ++++++++++++------- .../TangemPayTxHistoryItemsConverter.kt | 1 + 13 files changed, 77 insertions(+), 18 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index ab28c8f601..a3491e0401 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -468,9 +468,12 @@ %s Netzwerk Sende Geld nur mit Beste Gelegenheiten + Filter löschen Alle Netzwerke Alle Arten + Filtern nach Meist verwendet + Keine Ergebnisse Verdienen Hallo Support-Team, ich habe einen Fehler mit dem Code %s festgestellt. WalletConnect-Fehler @@ -1481,10 +1484,12 @@ Abgeschlossen Abgelehnt Ausstehend + Storniert Bedingungen, Gebühren & Limits Bedingungen und Einschränkungen Die Bank hat diese Transaktionsanfrage abgelehnt. Diese Gebühr dient zur Deckung der Kosten für die Abwicklung Deiner Überweisung.Questa commissione copre il costo della gestione del tuo trasferimento. + Die Transaktion wurde vom Händler teilweise oder vollständig storniert Nutze Dein Geld weiterhin. Du kannst es jederzeit einfrieren. Karte entsperren? Entsperren der Karte fehlgeschlagen. Versuchen Sie es später erneut. diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 4e0238e409..fac79c1d1f 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -1481,10 +1481,12 @@ Completado Rechazado Pendiente + Revertida Términos, tarifas y límites Términos y límites El banco rechazó esta solicitud de transacción. Esta tarifa cubre el costo de procesar tu transferencia. + La transacción fue revertida parcial o totalmente por el comerciante Sigue usando tu dinero. Puedes congelarlo en cualquier momento. ¿Descongelar tu tarjeta? No se pudo descongelar la tarjeta. Inténtalo de nuevo más tarde. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 7eda943c3f..a89fafa94f 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -468,9 +468,14 @@ %s réseau Envoyez des fonds en utilisant uniquement Meilleures opportunités + Effacer le filtre Tous les réseaux Tous les types + Filtrer par + Mes réseaux + Réseaux Principalement utilisé + Pas de résultat Gagner Bonjour équipe de support, j’ai rencontré une erreur avec le code : %s Erreur WalletConnect @@ -823,6 +828,7 @@ Volume Tirez vers le haut ou appuyez sur la barre de recherche pour ajouter des jetons directement depuis le marché Ajouter des jetons + Ajouter plus de tokens Optimisez vos actifs tout en leur fournissant un accès instantané. %s Activer le mode rendement Vous devez effectuer la mise à jour %1$s afin de créer un portefeuille mobile. @@ -1481,10 +1487,12 @@ Terminé Refusé En attente + Annulée Conditions, frais et limites Conditions et limites La banque a rejeté cette demande de transaction. Ces frais couvrent le coût du traitement de votre virement. + La transaction a été partiellement ou totalement annulée par le commerçant Continuez à utiliser votre argent. Vous pouvez le geler à tout moment. Dégeler votre carte ? Échec du dégel de la carte. Réessayez plus tard. @@ -1849,6 +1857,7 @@ Rafraîchir Lancer la migration Copier + Pour conserver l\'accès à vos fonds, lancez la migration conformément aux directives officielles de Clore. La signature des messages n\'est pas prise en charge pour ce réseau. Impossible de signer le message. Veuillez réessayer. Selon la documentation officielle de Clore, toutes les pièces reçues avant le 21 décembre seront migrées vers Clore (token ERC-20) ; les pièces reçues après cette date ne le seront pas. Une solution de transfert arrive — restez à l\'écoute. diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 0c8bcde7ac..50290eaea8 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -87,10 +87,12 @@ Completato Rifiutato In sospeso + Stornata Termini, commissioni e limiti Termini e limiti La banca ha rifiutato questa richiesta di transazione. Questa commissione copre il costo della gestione del tuo trasferimento. + La transazione è stata parzialmente o totalmente stornata dal commerciante Continua a usare i tuoi soldi. Puoi congelarli in qualsiasi momento. Sbloccare la tua carta? Impossibile sbloccare la carta. Riprova più tardi. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index c773a24141..d554a4bd2f 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -1462,10 +1462,12 @@ 完了 拒否 保留中 + 取消済み 利用規約・手数料・利用制限 利用規約と上限条件 銀行がこの取引リクエストを拒否しました。 この手数料は、送金処理にかかるコストをカバーするためのものです。 + この取引は加盟店により一部または全額取り消されました 資金は引き続き使用できます。いつでも一時停止できます。 カードの一時停止を解除しますか? カードの凍結解除に失敗しました。しばらくしてからもう一度お試しください。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index cc50a2188a..550f256f3f 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -310,6 +310,7 @@ Перейти в токен Понятно Скрыть + Удерживайте, чтобы %s час Импортировать В процессе @@ -387,6 +388,7 @@ Обменять Tangem Tangem Wallet + Нажмите и удерживайте условия участия Условиями использования На @@ -478,9 +480,15 @@ Отправка средств в другой сети может повлечь потерю средств. %s сеть Отправляйте средства, используя только + Лучшие возможности + Очистить фильтр Все сети Все типы + Фильтровать по + Мои сети + Сети Часто используемые + Нет результата Привет, команда поддержки, у меня возникла ошибка с кодом: %s Ошибка WalletConnect Вы использовали карту или кольцо от другого кошелька. Приложите карту или кольцо, связанную с этим кошельком. @@ -836,6 +844,7 @@ Объем Потяните вверх или коснитесь поисковой строки, чтобы добавить токены напрямую из рынка Добавить токены + Добавить токены Увеличивайте доход с активов, сохраняя мгновенный доступ к ним. %s Активировать режим доходности Обновитесь до версии %1$s, чтобы создать мобильный кошелёк @@ -1507,10 +1516,12 @@ Успешно завершено Отклонено В процессе + Возврат Тарифы и полные условия Тарифы и лимиты Банк отклонил транзакцию Эта комиссия покрывает стоимость обработки вашего перевода. + Транзакция частично или полностью возвращена продавцом Продолжайте пользоваться картой, заморозить всегда успеете Разморозить карту? Не удалось разморозить карту, попробуйте еще раз @@ -1810,6 +1821,7 @@ Обновить Начать миграцию Копировать + Чтобы сохранить доступ к своим средствам, начните миграцию в соответствии с официальными рекомендациями Clore. Подписание сообщений не поддерживается в этой сети Невозможно подписать сообщение. Пожалуйста, попробуй позже. Согласно официальной документации Clore, все монеты, полученные до 21 декабря, будут мигрированы в токен Clore (ERC-20); монеты, полученные после этой даты, — нет. Решение для перевода находится в разработке — следите за обновлениями. @@ -1920,6 +1932,7 @@ Мы обнаружили неизвестную ошибку Кошелек Tangem в настоящий момент не поддерживает %s Неподдерживаемый dApp + Код ошибки: 8 005.\nЕсли проблема сохраняется — пожалуйста, свяжитесь с нашей службой поддержки. Мы обнаружили неизвестную ошибку Эта сеть %s не поддерживается Tangem Wallet и не может быть подключена. Неподдерживаемая сеть diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 4bb916c300..cd70c14fcd 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -1480,10 +1480,12 @@ Завершено Відхилено В очікуванні + Скасовано Умови, комісії та ліміти Умови та обмеження Банк відхилив цей запит на транзакцію. Ця комісія покриває витрати на обробку вашого переказу. + Транзакцію було частково або повністю скасовано продавцем Продовжуйте користуватися карткою. Заморозити можна в будь-який момент. Розморозити картку? Не вдалося розморозити картку. Спробуйте пізніше. diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index ad66420637..f2a8057772 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -330,10 +330,12 @@ 已完成 已拒絕 處理中 + 已撤銷 條款、費用與限制 條款與限制 銀行拒絕了此交易請求。 此費用用於支付處理您轉帳的成本。 + 該交易已被商家部分或全額撤銷 繼續使用您的資金。您可以隨時凍結。 解凍您的卡片? 無法解凍卡片。請稍後再試。 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 8916a5a857..38fbaf5e44 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -469,9 +469,11 @@ Send funds using only Best opportunities Clear filter + The list is temporarily empty as it’s being refreshed. Check back in a moment. All networks All types Filter by + My networks Networks Mostly used No results @@ -828,6 +830,7 @@ Volume Pull this up or tap the search bar to add tokens directly from the market Add tokens + Add more tokens Power up your assets while supplying them with instant access. %s Activate Yield Mode You must update to %1$s before creating a mobile wallet @@ -1486,10 +1489,12 @@ Completed Declined Pending + Reversed Terms, Fees & Limits Terms and Limits The bank rejected this transaction request. This fee goes to cover the cost of handling your transfer. + The transaction was partially or fully reversed by the merchant Keep using your money. You can freeze anytime. Unfreeze your card? Failed to unfreeze the card. Try again later. diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/TangemPayTxHistoryItemStatusConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/TangemPayTxHistoryItemStatusConverter.kt index 775b3843a9..e1ac53a595 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/TangemPayTxHistoryItemStatusConverter.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/TangemPayTxHistoryItemStatusConverter.kt @@ -10,6 +10,7 @@ internal object TangemPayTxHistoryItemStatusConverter : Converter TangemPayTxHistoryItem.Status.RESERVED "COMPLETED" -> TangemPayTxHistoryItem.Status.COMPLETED "DECLINED" -> TangemPayTxHistoryItem.Status.DECLINED + "REVERSED" -> TangemPayTxHistoryItem.Status.REVERSED else -> TangemPayTxHistoryItem.Status.UNKNOWN } } diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/TangemPayTxHistoryItem.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/TangemPayTxHistoryItem.kt index 9741b0d593..33289fa8f7 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/TangemPayTxHistoryItem.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/TangemPayTxHistoryItem.kt @@ -72,6 +72,7 @@ sealed class TangemPayTxHistoryItem { RESERVED, COMPLETED, DECLINED, + REVERSED, UNKNOWN, } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryDetailsConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryDetailsConverter.kt index d5be0d999b..4a1de8ef38 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryDetailsConverter.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryDetailsConverter.kt @@ -110,6 +110,7 @@ internal object TangemPayTxHistoryDetailsConverter : is TangemPayTxHistoryItem.Spend -> { val amountPrefix = when { this.amount.isZero() -> "" + this.status == TangemPayTxHistoryItem.Status.REVERSED -> StringsSigns.MINUS this.status == TangemPayTxHistoryItem.Status.DECLINED || this.amount.isPositive() -> StringsSigns.MINUS else -> StringsSigns.PLUS @@ -205,6 +206,10 @@ internal object TangemPayTxHistoryDetailsConverter : text = resourceReference(R.string.tangem_pay_status_declined), style = LabelStyle.WARNING, ) + TangemPayTxHistoryItem.Status.REVERSED -> LabelUM( + text = resourceReference(R.string.tangem_pay_status_reversed), + style = LabelStyle.REGULAR, + ) TangemPayTxHistoryItem.Status.RESERVED, TangemPayTxHistoryItem.Status.UNKNOWN, -> null @@ -227,24 +232,33 @@ internal object TangemPayTxHistoryDetailsConverter : containerColor = null, ) is TangemPayTxHistoryItem.Spend -> when (this.status) { - TangemPayTxHistoryItem.Status.DECLINED -> - TangemPayTxHistoryDetailsUM.NotificationState( - config = NotificationConfig( - title = if (declinedReason.isNullOrEmpty()) { - resourceReference(R.string.tangem_pay_transaction_declined_notification_text) - } else { - resourceReference( - id = R.string.tangem_pay_history_item_spend_mc_declined_reason, - formatArgs = wrappedList(requireNotNull(declinedReason)), - ) - }, - subtitle = TextReference.EMPTY, - iconResId = R.drawable.ic_token_info_24, - ), - titleColor = themedColor { TangemTheme.colors.text.warning }, - iconTint = themedColor { TangemTheme.colors.icon.warning }, - containerColor = themedColor { TangemColorPalette.Amaranth.copy(alpha = 0.1F) }, - ) + TangemPayTxHistoryItem.Status.DECLINED -> TangemPayTxHistoryDetailsUM.NotificationState( + config = NotificationConfig( + title = if (declinedReason.isNullOrEmpty()) { + resourceReference(R.string.tangem_pay_transaction_declined_notification_text) + } else { + resourceReference( + id = R.string.tangem_pay_history_item_spend_mc_declined_reason, + formatArgs = wrappedList(requireNotNull(declinedReason)), + ) + }, + subtitle = TextReference.EMPTY, + iconResId = R.drawable.ic_token_info_24, + ), + titleColor = themedColor { TangemTheme.colors.text.warning }, + iconTint = themedColor { TangemTheme.colors.icon.warning }, + containerColor = themedColor { TangemColorPalette.Amaranth.copy(alpha = 0.1F) }, + ) + TangemPayTxHistoryItem.Status.REVERSED -> TangemPayTxHistoryDetailsUM.NotificationState( + config = NotificationConfig( + title = resourceReference(R.string.tangem_pay_transaction_reversed_notification_text), + subtitle = TextReference.EMPTY, + iconResId = R.drawable.ic_token_info_24, + ), + titleColor = themedColor { TangemTheme.colors.text.tertiary }, + iconTint = themedColor { TangemTheme.colors.icon.secondary }, + containerColor = null, + ) TangemPayTxHistoryItem.Status.PENDING, TangemPayTxHistoryItem.Status.COMPLETED, TangemPayTxHistoryItem.Status.RESERVED, diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryItemsConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryItemsConverter.kt index bf0f79c163..e868de30a5 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryItemsConverter.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryItemsConverter.kt @@ -37,6 +37,7 @@ internal class TangemPayTxHistoryItemsConverter( val localDate = spend.date.withZone(DateTimeZone.getDefault()) val amountPrefix = when { spend.amount.isZero() -> "" + spend.status == TangemPayTxHistoryItem.Status.REVERSED -> StringsSigns.MINUS spend.status == TangemPayTxHistoryItem.Status.DECLINED || spend.amount.isPositive() -> StringsSigns.MINUS else -> StringsSigns.PLUS } From 54d476caddddf4e032db50b99b47a1cac879a2b3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 6 Feb 2026 18:30:13 +0300 Subject: [PATCH 04/24] Updated on 2026-08-14 --- ...efaultHoldToConfirmButtonFeatureToggles.kt | 13 +++++++++++++ .../tap/di/core/ui/CoreUiBindsModule.kt | 7 +++++++ .../configs/feature_toggles_config.json | 4 ++++ .../ui/HoldToConfirmButtonFeatureToggles.kt | 5 +++++ .../v2/send/confirm/model/SendConfirmModel.kt | 5 ++++- .../confirm/model/SendWithSwapConfirmModel.kt | 5 ++++- .../tangem/feature/swap/model/SwapModel.kt | 3 +++ .../tangem/feature/swap/ui/StateBuilder.kt | 19 ++++++++++++------- .../converter/WcSendTransactionUMConverter.kt | 5 ++++- .../converter/WcSignTransactionUMConverter.kt | 5 ++++- .../converter/WcSignTypedDataUMConverter.kt | 5 ++++- .../approve/model/YieldSupplyApproveModel.kt | 5 ++++- .../model/YieldSupplyStartEarningModel.kt | 7 ++++++- .../model/YieldSupplyStopEarningModel.kt | 5 ++++- 14 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/core/ui/DefaultHoldToConfirmButtonFeatureToggles.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/HoldToConfirmButtonFeatureToggles.kt diff --git a/app/src/main/java/com/tangem/tap/core/ui/DefaultHoldToConfirmButtonFeatureToggles.kt b/app/src/main/java/com/tangem/tap/core/ui/DefaultHoldToConfirmButtonFeatureToggles.kt new file mode 100644 index 0000000000..e488d368a2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/core/ui/DefaultHoldToConfirmButtonFeatureToggles.kt @@ -0,0 +1,13 @@ +package com.tangem.tap.core.ui + +import com.tangem.core.configtoggle.feature.FeatureTogglesManager +import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles +import javax.inject.Inject + +class DefaultHoldToConfirmButtonFeatureToggles @Inject constructor( + featureTogglesManager: FeatureTogglesManager, +) : HoldToConfirmButtonFeatureToggles { + override val isHoldToConfirmEnabled: Boolean = featureTogglesManager.isFeatureEnabled( + "HOLD_TO_CONFIRM_BUTTON_ENABLED", + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/core/ui/CoreUiBindsModule.kt b/app/src/main/java/com/tangem/tap/di/core/ui/CoreUiBindsModule.kt index b02028c5bf..47e1215497 100644 --- a/app/src/main/java/com/tangem/tap/di/core/ui/CoreUiBindsModule.kt +++ b/app/src/main/java/com/tangem/tap/di/core/ui/CoreUiBindsModule.kt @@ -1,7 +1,9 @@ package com.tangem.tap.di.core.ui import com.tangem.core.ui.DesignFeatureToggles +import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles import com.tangem.tap.core.ui.DefaultDesignFeatureToggles +import com.tangem.tap.core.ui.DefaultHoldToConfirmButtonFeatureToggles import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -13,4 +15,9 @@ interface CoreUiBindsModule { @Binds fun bindDesignFeatureToggles(impl: DefaultDesignFeatureToggles): DesignFeatureToggles + + @Binds + fun bindHoldToConfirmButtonFeatureToggles( + impl: DefaultHoldToConfirmButtonFeatureToggles, + ): HoldToConfirmButtonFeatureToggles } \ No newline at end of file diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 299c0abe09..208a5cf457 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -82,5 +82,9 @@ { "name": "EARN_BLOCK_ENABLED", "version": "undefined" + }, + { + "name": "HOLD_TO_CONFIRM_BUTTON_ENABLED", + "version": "undefined" } ] diff --git a/core/ui/src/main/java/com/tangem/core/ui/HoldToConfirmButtonFeatureToggles.kt b/core/ui/src/main/java/com/tangem/core/ui/HoldToConfirmButtonFeatureToggles.kt new file mode 100644 index 0000000000..35efae6bc9 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/HoldToConfirmButtonFeatureToggles.kt @@ -0,0 +1,5 @@ +package com.tangem.core.ui + +interface HoldToConfirmButtonFeatureToggles { + val isHoldToConfirmEnabled: Boolean +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt index 8f3da0e8ed..a18cc27acf 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt @@ -19,6 +19,7 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference @@ -118,6 +119,7 @@ internal class SendConfirmModel @Inject constructor( private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase, private val currenciesRepository: CurrenciesRepository, private val createAndSendGaslessTransactionUseCase: CreateAndSendGaslessTransactionUseCase, + private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles, sendBalanceUpdaterFactory: SendBalanceUpdater.Factory, ) : Model(), SendConfirmClickIntents, FeeSelectorModelCallback, SendNotificationsComponent.ModelCallback { @@ -597,7 +599,8 @@ internal class SendConfirmModel @Inject constructor( val confirmUM = uiState.value.confirmUM val isContent = confirmUM is ConfirmUM.Content val isReadyToSend = isContent && !confirmUM.isSending - val isHoldToConfirm = userWallet.isHotWallet && isContent + val isHoldToConfirm = holdToConfirmButtonFeatureToggles.isHoldToConfirmEnabled && + userWallet.isHotWallet && isContent return NavigationButton( textReference = getPrimaryButtonText(confirmUM, isHoldToConfirm), iconRes = walletInterationIcon(userWallet), diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt index 90526afb56..50666b4e1b 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt @@ -18,6 +18,7 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router +import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList @@ -96,6 +97,7 @@ internal class SendWithSwapConfirmModel @Inject constructor( private val swapAlertFactory: SwapAlertFactory, private val appRouter: AppRouter, private val analyticsEventHandler: AnalyticsEventHandler, + private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles, swapTransactionSenderFactory: SwapTransactionSender.Factory, paramsContainer: ParamsContainer, ) : Model(), FeeSelectorModelCallback, SendNotificationsComponent.ModelCallback { @@ -502,7 +504,8 @@ internal class SendWithSwapConfirmModel @Inject constructor( val confirmUM = state.confirmUM val isContent = confirmUM is ConfirmUM.Content val isReadyToSend = isContent && !confirmUM.isTransactionInProcess - val isHoldToConfirm = params.userWallet.isHotWallet && isContent + val isHoldToConfirm = holdToConfirmButtonFeatureToggles.isHoldToConfirmEnabled && + params.userWallet.isHotWallet && isContent params.callback.onResult( route = SendWithSwapRoute.Confirm, sendWithSwapUM = state.copy( diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index ee96daa1cd..1448ec5de8 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -23,6 +23,7 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.utils.InputNumberFormatter @@ -160,6 +161,7 @@ internal class SwapModel @Inject constructor( private val getUserWalletsUseCase: GetWalletsUseCase, private val getTangemPayCustomerIdUseCase: GetTangemPayCustomerIdUseCase, private val appsFlyerStore: AppsFlyerStore, + private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles, ) : Model() { private val params = paramsContainer.require() @@ -192,6 +194,7 @@ internal class SwapModel @Inject constructor( appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), isAccountsModeProvider = Provider { isAccountsMode }, iGaslessFeeSupportedForNetwork = iGaslessFeeSupportedForNetwork, + holdToConfirmButtonFeatureToggles = holdToConfirmButtonFeatureToggles, ) private val inputNumberFormatter = diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index f43c1f4caa..c3a85fe555 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -24,6 +24,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.account.Account import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.isHotWallet import com.tangem.domain.promo.models.StoryContent @@ -60,7 +61,7 @@ import kotlin.math.min /** * State builder creates a specific states for SwapScreen */ -@Suppress("LargeClass", "TooManyFunctions") +@Suppress("LargeClass", "TooManyFunctions", "LongParameterList") internal class StateBuilder( private val userWalletProvider: Provider, private val actions: UiActions, @@ -68,8 +69,12 @@ internal class StateBuilder( private val appCurrencyProvider: Provider, private val isAccountsModeProvider: Provider, private val iGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork, + private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles, ) { + private val isHoldToConfirmEnabled: Boolean = + holdToConfirmButtonFeatureToggles.isHoldToConfirmEnabled && userWalletProvider().isHotWallet + private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) private val tokensDataConverter = TokensDataConverter( @@ -127,7 +132,7 @@ internal class StateBuilder( swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(userWalletProvider()), isEnabled = false, - isHoldToConfirm = userWalletProvider().isHotWallet, + isHoldToConfirm = isHoldToConfirmEnabled, onClick = {}, ), onRefresh = {}, @@ -187,7 +192,7 @@ internal class StateBuilder( swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(userWalletProvider()), isEnabled = false, - isHoldToConfirm = userWalletProvider().isHotWallet, + isHoldToConfirm = isHoldToConfirmEnabled, onClick = { }, ), changeCardsButtonState = ChangeCardsButtonState.DISABLED, @@ -253,7 +258,7 @@ internal class StateBuilder( swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(userWalletProvider()), isEnabled = false, - isHoldToConfirm = userWalletProvider().isHotWallet, + isHoldToConfirm = isHoldToConfirmEnabled, onClick = {}, ), providerState = ProviderState.Loading(), @@ -376,7 +381,7 @@ internal class StateBuilder( swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(userWalletProvider()), isEnabled = getSwapButtonEnabled(notifications), - isHoldToConfirm = userWalletProvider().isHotWallet, + isHoldToConfirm = isHoldToConfirmEnabled, onClick = actions.onSwapClick, ), changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible), @@ -505,7 +510,7 @@ internal class StateBuilder( swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(userWalletProvider()), isEnabled = false, - isHoldToConfirm = userWalletProvider().isHotWallet, + isHoldToConfirm = isHoldToConfirmEnabled, onClick = actions.onSwapClick, ), changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible), @@ -603,7 +608,7 @@ internal class StateBuilder( swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(userWalletProvider()), isEnabled = false, - isHoldToConfirm = userWalletProvider().isHotWallet, + isHoldToConfirm = isHoldToConfirmEnabled, onClick = { }, ), changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible), diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt index e4969a134c..6976bac3a3 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt @@ -16,6 +16,7 @@ import com.tangem.features.walletconnect.transaction.entity.common.WcTransaction import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionItemUM import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionUM import com.tangem.features.walletconnect.utils.WcNotificationsFactory +import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles import com.tangem.domain.models.wallet.isHotWallet import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList @@ -26,6 +27,7 @@ internal class WcSendTransactionUMConverter @Inject constructor( private val networkInfoUMConverter: WcNetworkInfoUMConverter, private val requestBlockUMConverter: WcTransactionRequestBlockUMConverter, private val notificationsFactory: WcNotificationsFactory, + private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles, ) : Converter { override fun convert(value: Input): WcSendTransactionUM? { @@ -63,7 +65,8 @@ internal class WcSendTransactionUMConverter @Inject constructor( } }, feeErrorNotification = feeErrorNotification, - isHoldToConfirmEnabled = value.context.session.wallet.isHotWallet, + isHoldToConfirmEnabled = holdToConfirmButtonFeatureToggles.isHoldToConfirmEnabled && + value.context.session.wallet.isHotWallet, ), feeSelectorUM = when (value.feeState) { WcTransactionFeeState.None -> FeeSelectorUM.Loading diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt index 4fd75570a0..fcccd0f7f8 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt @@ -10,6 +10,7 @@ import com.tangem.features.walletconnect.transaction.entity.common.WcTransaction import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoUM import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionItemUM import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionUM +import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles import com.tangem.domain.models.wallet.isHotWallet import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList @@ -19,6 +20,7 @@ internal class WcSignTransactionUMConverter @Inject constructor( private val appInfoContentUMConverter: WcTransactionAppInfoContentUMConverter, private val networkInfoUMConverter: WcNetworkInfoUMConverter, private val requestBlockUMConverter: WcTransactionRequestBlockUMConverter, + private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles, ) : Converter { override fun convert(value: Input) = WcSignTransactionUM( @@ -36,7 +38,8 @@ internal class WcSignTransactionUMConverter @Inject constructor( isLoading = value.signState.domainStep == WcSignStep.Signing, address = WcAddressConverter.convert(value.context.derivationState), walletInteractionIcon = walletInterationIcon(value.context.session.wallet), - isHoldToConfirmEnabled = value.context.session.wallet.isHotWallet, + isHoldToConfirmEnabled = holdToConfirmButtonFeatureToggles.isHoldToConfirmEnabled && + value.context.session.wallet.isHotWallet, ), transactionRequestInfo = WcTransactionRequestInfoUM( requestBlockUMConverter.convert( diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt index 6f185ecfd9..5a8f8d3fc3 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt @@ -10,6 +10,7 @@ import com.tangem.features.walletconnect.transaction.entity.common.WcTransaction import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoUM import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionItemUM import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionUM +import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles import com.tangem.domain.models.wallet.isHotWallet import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList @@ -19,6 +20,7 @@ internal class WcSignTypedDataUMConverter @Inject constructor( private val appInfoContentUMConverter: WcTransactionAppInfoContentUMConverter, private val networkInfoUMConverter: WcNetworkInfoUMConverter, private val requestBlockUMConverter: WcTransactionRequestBlockUMConverter, + private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles, ) : Converter { override fun convert(value: Input): WcSignTransactionUM = WcSignTransactionUM( @@ -36,7 +38,8 @@ internal class WcSignTypedDataUMConverter @Inject constructor( address = WcAddressConverter.convert(value.context.derivationState), isLoading = value.signState.domainStep == WcSignStep.Signing, walletInteractionIcon = walletInterationIcon(value.context.session.wallet), - isHoldToConfirmEnabled = value.context.session.wallet.isHotWallet, + isHoldToConfirmEnabled = holdToConfirmButtonFeatureToggles.isHoldToConfirmEnabled && + value.context.session.wallet.isHotWallet, ), transactionRequestInfo = WcTransactionRequestInfoUM( blocks = buildList { diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt index 4ac6a9f1ce..f817548872 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt @@ -10,6 +10,7 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.extensions.* import com.tangem.core.ui.format.bigdecimal.fiat @@ -62,6 +63,7 @@ internal class YieldSupplyApproveModel @Inject constructor( private val yieldSupplyGetContractAddressUseCase: YieldSupplyGetContractAddressUseCase, private val yieldSupplyPendingTracker: YieldSupplyPendingTracker, private val yieldSupplyAlertFactory: YieldSupplyAlertFactory, + private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles, ) : Model(), YieldSupplyNotificationsComponent.ModelCallback { private val params: YieldSupplyApproveComponent.Params = paramsContainer.require() @@ -98,7 +100,8 @@ internal class YieldSupplyApproveModel @Inject constructor( yieldSupplyFeeUM = YieldSupplyFeeUM.Loading, isPrimaryButtonEnabled = false, isTransactionSending = false, - isHoldToConfirmEnabled = params.userWallet.isHotWallet, + isHoldToConfirmEnabled = holdToConfirmButtonFeatureToggles.isHoldToConfirmEnabled && + params.userWallet.isHotWallet, ), ) diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt index 1f70eff651..9c8635a26b 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt @@ -8,6 +8,7 @@ import com.tangem.core.analytics.models.Basic import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList @@ -69,6 +70,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor( private val yieldSupplyRepository: YieldSupplyRepository, private val yieldSupplyPendingTracker: YieldSupplyPendingTracker, private val appsFlyerStore: AppsFlyerStore, + private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles, ) : Model(), YieldSupplyNotificationsComponent.ModelCallback { private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require() @@ -313,7 +315,10 @@ internal class YieldSupplyStartEarningModel @Inject constructor( ifRight = { wallet -> userWallet = wallet uiState.update { - it.copy(isHoldToConfirmEnabled = wallet.isHotWallet) + it.copy( + isHoldToConfirmEnabled = holdToConfirmButtonFeatureToggles.isHoldToConfirmEnabled && + wallet.isHotWallet, + ) } getCurrenciesStatusUpdates() }, diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt index 97020e9990..dd0b697ccd 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt @@ -8,6 +8,7 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList @@ -66,6 +67,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor( private val yieldSupplyRepository: YieldSupplyRepository, private val yieldSupplyPendingTracker: YieldSupplyPendingTracker, private val appsFlyerStore: AppsFlyerStore, + private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles, ) : Model(), YieldSupplyNotificationsComponent.ModelCallback { private val params: YieldSupplyStopEarningComponent.Params = paramsContainer.require() @@ -102,7 +104,8 @@ internal class YieldSupplyStopEarningModel @Inject constructor( yieldSupplyFeeUM = YieldSupplyFeeUM.Loading, isPrimaryButtonEnabled = false, isTransactionSending = false, - isHoldToConfirmEnabled = params.userWallet.isHotWallet, + isHoldToConfirmEnabled = holdToConfirmButtonFeatureToggles.isHoldToConfirmEnabled && + params.userWallet.isHotWallet, ), ) From d258cb2f5cfc89d9b537a4995f0f0323901f5113 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 6 Feb 2026 18:34:53 +0300 Subject: [PATCH 05/24] Updated on 2026-08-14 --- .../java/com/tangem/feature/swap/DefaultSwapComponent.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt index 6bb735f4f8..6a88017cb4 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt @@ -107,10 +107,7 @@ internal class DefaultSwapComponent @AssistedInject constructor( val fromCryptoCurrency by remember { derivedStateOf { dataState.fromCryptoCurrency } } val feePaidCryptoCurrency by remember { derivedStateOf { dataState.feePaidCryptoCurrency } } val shouldHideBlock by remember { - derivedStateOf { - dataState.amount.isNullOrBlank() || BigDecimal(dataState.amount).isZero() || - model.uiState.isInsufficientFunds - } + derivedStateOf { toBigDecimalOrZero(dataState.amount).isZero() || model.uiState.isInsufficientFunds } } LaunchedEffect(fromCryptoCurrency, feePaidCryptoCurrency, shouldHideBlock) { @@ -208,6 +205,10 @@ internal class DefaultSwapComponent @AssistedInject constructor( ) } + private fun toBigDecimalOrZero(bigDecimalString: String?): BigDecimal { + return bigDecimalString?.replace(",", ".")?.toBigDecimalOrNull() ?: BigDecimal.ZERO + } + @AssistedFactory interface Factory : SwapComponent.Factory { override fun create(context: AppComponentContext, params: SwapComponent.Params): DefaultSwapComponent From a94e23c90c929582419dc008bba3f0b1086b3ab4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 6 Feb 2026 18:36:14 +0300 Subject: [PATCH 06/24] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index a3491e0401..5ee7d6ff61 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1488,7 +1488,7 @@ Bedingungen, Gebühren & Limits Bedingungen und Einschränkungen Die Bank hat diese Transaktionsanfrage abgelehnt. - Diese Gebühr dient zur Deckung der Kosten für die Abwicklung Deiner Überweisung.Questa commissione copre il costo della gestione del tuo trasferimento. + Diese Gebühr dient zur Deckung der Kosten für die Abwicklung Deiner Überweisung. Die Transaktion wurde vom Händler teilweise oder vollständig storniert Nutze Dein Geld weiterhin. Du kannst es jederzeit einfrieren. Karte entsperren? From 277cfd8a06d1e1eecbfaa7931aea3dbec56e1ddf Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 6 Feb 2026 17:00:32 +0100 Subject: [PATCH 07/24] Updated on 2026-08-14 --- .../ui/market/detailed/components/TokenMarketDetailsBody.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/TokenMarketDetailsBody.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/TokenMarketDetailsBody.kt index 434ba73d53..ff3047cac8 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/TokenMarketDetailsBody.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/TokenMarketDetailsBody.kt @@ -254,7 +254,7 @@ private fun LazyListScope.relatedNews(relatedNews: RelatedNews) { articleConfigUM = article, onArticleClick = { relatedNews.onArticledClicked(article.id) }, modifier = articleModifier - .height(164.dp) + .heightIn(min = 164.dp) .width(216.dp), colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action), ) From 5d9be26571dad3763d03a970d5ef2008a6ad5d42 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 6 Feb 2026 19:31:27 +0300 Subject: [PATCH 08/24] Updated on 2026-08-14 --- .../com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index 20046a427d..8639981110 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -438,6 +438,7 @@ internal class DefaultTangemSdkManager( ), ) showAlert() + break } else { delay(timeMillis = 400) } From 1f7e38eea154f7f9a3f5357089656d5ce1587f74 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Feb 2026 13:38:09 +0400 Subject: [PATCH 09/24] Updated on 2026-08-14 --- .../config/environment/converter/BlockchainSDKConfigConverter.kt | 1 + .../local/config/environment/models/EnvironmentConfigModel.kt | 1 + .../com/tangem/blockchainsdk/providers/ProviderTypeIdMapping.kt | 1 + 3 files changed, 3 insertions(+) diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/BlockchainSDKConfigConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/BlockchainSDKConfigConverter.kt index ade515b41c..bf9d444051 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/BlockchainSDKConfigConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/BlockchainSDKConfigConverter.kt @@ -45,6 +45,7 @@ internal object BlockchainSDKConfigConverter : Converter Date: Mon, 9 Feb 2026 13:12:45 +0300 Subject: [PATCH 10/24] Updated on 2026-08-14 --- .../market/details/portfolio/add/impl/model/AddTokenModel.kt | 4 +++- .../markets/portfolio/add/impl/model/AddTokenModel.kt | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddTokenModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddTokenModel.kt index 1d6e6d57be..d616bf53be 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddTokenModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddTokenModel.kt @@ -120,7 +120,9 @@ internal class AddTokenModel @Inject constructor( selectedPortfolio: SelectedPortfolio, ): Boolean = coldWalletAndHasMissedDerivationsUseCase.invoke( userWalletId = selectedPortfolio.userWallet.walletId, - networksWithDerivationPath = mapOf(selectedNetwork.selectedNetwork.networkId to null), + networksWithDerivationPath = mapOf( + selectedNetwork.selectedNetwork.networkId to selectedNetwork.cryptoCurrency.network.derivationPath.value, + ), ) private fun processError(error: Throwable?) { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt index 416154863e..7f8214d8fa 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt @@ -120,7 +120,9 @@ internal class AddTokenModel @Inject constructor( selectedPortfolio: SelectedPortfolio, ): Boolean = coldWalletAndHasMissedDerivationsUseCase.invoke( userWalletId = selectedPortfolio.userWallet.walletId, - networksWithDerivationPath = mapOf(selectedNetwork.selectedNetwork.networkId to null), + networksWithDerivationPath = mapOf( + selectedNetwork.selectedNetwork.networkId to selectedNetwork.cryptoCurrency.network.derivationPath.value, + ), ) private fun processError(error: Throwable?) { From 617ad2af9e0dd13c1e225566183b1b1a3b85797a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Feb 2026 12:18:09 +0200 Subject: [PATCH 11/24] Updated on 2026-08-14 --- .../kotlin/com/tangem/plugin/configuration/model/BuildType.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt index a0814f9003..0ab5e8f747 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt @@ -74,7 +74,7 @@ internal enum class BuildType( BuildConfigField.LogEnabled(isEnabled = true), BuildConfigField.TesterMenuAvailability(isEnabled = true), BuildConfigField.MockDataSource(isEnabled = false), - BuildConfigField.ABTestsEnabled(isEnabled = true), + BuildConfigField.ABTestsEnabled(isEnabled = false), ), ), @@ -114,7 +114,7 @@ internal enum class BuildType( BuildConfigField.LogEnabled(isEnabled = false), BuildConfigField.TesterMenuAvailability(isEnabled = false), BuildConfigField.MockDataSource(isEnabled = false), - BuildConfigField.ABTestsEnabled(isEnabled = true), + BuildConfigField.ABTestsEnabled(isEnabled = false), ), ), } \ No newline at end of file From eaf0507b89e25fff1e5827a7c762969172338040 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Feb 2026 13:30:16 +0300 Subject: [PATCH 12/24] Updated on 2026-08-14 --- .../com/tangem/domain/walletconnect/WcAnalyticEvents.kt | 3 ++- .../managetokens/analytics/CustomTokenAnalyticsEvent.kt | 5 ++--- .../component/impl/DefaultAddCustomTokenComponent.kt | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt index d9f540cefb..800271cacd 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt @@ -53,12 +53,13 @@ sealed class WcAnalyticEvents( network: Set, domainVerification: CheckDAppResult, ) : WcAnalyticEvents( - event = "dApp Connection Requested", + event = "DApp Connection Requested", params = mapOf( AnalyticsParam.DAPP_NAME to dAppName, AnalyticsParam.DAPP_URL to dAppUrl, NETWORKS to network.joinToString(",") { it.name }, DOMAIN_VERIFICATION to domainVerification.toAnalyticVerificationStatus(), + AnalyticsParam.ACCOUNT_DERIVATION to "0", ), ) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/analytics/CustomTokenAnalyticsEvent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/analytics/CustomTokenAnalyticsEvent.kt index 03cab5fb6d..14aa8c4e17 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/analytics/CustomTokenAnalyticsEvent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/analytics/CustomTokenAnalyticsEvent.kt @@ -34,13 +34,12 @@ internal sealed class CustomTokenAnalyticsEvent( class AddTokenToAnotherAccount( currencySymbol: String, derivationPath: String, - source: ManageTokensSource, - ) : CustomTokenAnalyticsEvent( + ) : AnalyticsEvent( + category = "Settings / Account", event = "Button - Add Token To Another Account", params = mapOf( AnalyticsParam.Key.TOKEN_PARAM to currencySymbol, AnalyticsParam.Key.DERIVATION to derivationPath, - AnalyticsParam.Key.SOURCE to source.analyticsName, ), ) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt index b49478560f..54e95f4260 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt @@ -239,7 +239,6 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor( val event = CustomTokenAnalyticsEvent.AddTokenToAnotherAccount( currencySymbol = currency.symbol, derivationPath = currency.network.derivationPath.value.orEmpty(), - source = params.source, ) analyticsEventHandler.send(event) } From c5edd9f13d5c38824f529a3b7fe9410ff7f3b2e1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Feb 2026 13:33:03 +0300 Subject: [PATCH 13/24] Updated on 2026-08-14 --- .../tap/features/root/RootDetectedWarningComponent.kt | 6 +++++- .../routing/component/impl/DefaultRoutingComponent.kt | 10 ++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/root/RootDetectedWarningComponent.kt b/app/src/main/java/com/tangem/tap/features/root/RootDetectedWarningComponent.kt index 16077bce72..8a28992da8 100644 --- a/app/src/main/java/com/tangem/tap/features/root/RootDetectedWarningComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/root/RootDetectedWarningComponent.kt @@ -30,10 +30,14 @@ class RootDetectedWarningComponent @AssistedInject constructor( private val isShown = instanceKeeper.getOrCreateSimple { MutableStateFlow(false) } + suspend fun shouldShowWarning(): Boolean { + return settingsRepository.isRootDetectedWarningShown().not() && securityInfoProvider.isSecurityExposed() + } + suspend fun tryToShowWarningAndWaitContinuation() { if (isShown.value) return - if (settingsRepository.isRootDetectedWarningShown().not() && securityInfoProvider.isSecurityExposed()) { + if (shouldShowWarning()) { isShown.value = true } diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt index 23bc630cec..3000c53538 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt @@ -141,9 +141,15 @@ internal class DefaultRoutingComponent @AssistedInject constructor( private fun initializeInitialNavigation() { if (initialStack.isNullOrEmpty()) { componentScope.launch { - rootDetectedWarningComponent.tryToShowWarningAndWaitContinuation() val initialRoute = resolveInitialRoute() - router.replaceAll(initialRoute) + if (rootDetectedWarningComponent.shouldShowWarning()) { + launch(dispatchers.main) { + rootDetectedWarningComponent.tryToShowWarningAndWaitContinuation() + router.replaceAll(initialRoute) + } + } else { + router.replaceAll(initialRoute) + } } } } From efe2e3063bfa93ae653cb2b889800c7dd799ecc4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Feb 2026 11:40:29 +0100 Subject: [PATCH 14/24] Updated on 2026-08-14 --- .../main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt index 50bee9b150..95ffc567c2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt @@ -103,7 +103,7 @@ object DateTimeFormatters { */ val localFullDate: DateTimeFormatter by lazy { val locale = Locale.getDefault() - val datePattern = DateFormat.getBestDateTimePattern(locale, "dd MMMM") + val datePattern = DateFormat.getBestDateTimePattern(locale, "d MMMM") val timeSkeleton = if (is12HourFormat) "h:mm a" else "HH:mm" val timePattern = DateFormat.getBestDateTimePattern(locale, timeSkeleton) val fullPattern = "$datePattern, $timePattern" From 7ec835751e92ae8beb8abed91e92748f52485eb4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Feb 2026 14:56:39 +0300 Subject: [PATCH 15/24] Updated on 2026-08-14 --- .../network/ethereum/WcEthSendTransactionUseCase.kt | 7 ++++--- .../network/ethereum/WcEthSignTransactionUseCase.kt | 7 ++++--- gradle/tangem_dependencies.toml | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt index 4b599ee4d7..0bee94b52c 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt @@ -107,15 +107,16 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor( val newState = when (action) { is WcEthTxAction.UpdateApprovalAmount -> { val extras = uncompiled.extras as EthereumTransactionExtras - val callData = ApprovalERC20TokenCallData( - spenderAddress = uncompiled.sourceAddress, + val compiledData = extras.callData?.data ?: return + val approvalCallData = ApprovalERC20TokenCallData(compiledData) ?: return + val newApprovalCallData = approvalCallData.copy( amount = action.amount?.amount?.let { BlockchainAmount(currencySymbol = it.currencySymbol, decimals = it.decimals, value = it.value) }, ) approvalAmount = action.amount isIgnoreDAppFee = true - uncompiled.copy(extras = extras.copy(callData = callData)) + uncompiled.copy(extras = extras.copy(callData = newApprovalCallData)) } is WcEthTxAction.UpdateFee -> uncompiled.copy(fee = action.fee) } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt index e71f37533d..f471d59ba8 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt @@ -92,15 +92,16 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor( val newState = when (action) { is WcEthTxAction.UpdateApprovalAmount -> { val extras = uncompiled.extras as EthereumTransactionExtras - val callData = ApprovalERC20TokenCallData( - spenderAddress = uncompiled.sourceAddress, + val compiledData = extras.callData?.data ?: return + val approvalCallData = ApprovalERC20TokenCallData(compiledData) ?: return + val newApprovalCallData = approvalCallData.copy( amount = action.amount?.amount?.let { BlockchainAmount(currencySymbol = it.currencySymbol, decimals = it.decimals, value = it.value) }, ) approvalAmount = action.amount isIgnoreDAppFee = true - uncompiled.copy(extras = extras.copy(callData = callData)) + uncompiled.copy(extras = extras.copy(callData = newApprovalCallData)) } is WcEthTxAction.UpdateFee -> uncompiled.copy(fee = action.fee) } diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 0758ec2945..d015ed0aba 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.33-1421" +tangemBlockchainSdk = "releases-5.33-1423" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.33-576" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 91149d48a2b47633c5a274afa975db8250956eb8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Feb 2026 17:13:58 +0300 Subject: [PATCH 16/24] Updated on 2026-08-14 --- .../ui/components/multicurrency/MultiCurrencyNFTCollections.kt | 2 +- .../ui/components/multicurrency/MultiCurrencyOrganizeButton.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyNFTCollections.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyNFTCollections.kt index e6257855e9..ba720b16af 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyNFTCollections.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyNFTCollections.kt @@ -10,7 +10,7 @@ private const val NFT_COLLECTIONS_CONTENT_TYPE = "NFTCollections" internal fun LazyListScope.nftCollections(state: WalletNFTItemUM, modifier: Modifier = Modifier) { item(key = NFT_COLLECTIONS_CONTENT_TYPE, contentType = NFT_COLLECTIONS_CONTENT_TYPE) { WalletNFTItem( - modifier = modifier.animateItem(), + modifier = modifier, state = state, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt index da334b825e..94592fc246 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt @@ -26,7 +26,7 @@ internal fun LazyListScope.organizeTokensButton( ) { item(key = ORGANIZE_BUTTON_CONTENT_TYPE, contentType = ORGANIZE_BUTTON_CONTENT_TYPE) { RoundedActionButton( - modifier = modifier.animateItem().testTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON), + modifier = modifier.testTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON), config = ActionButtonConfig( text = resourceReference(id = R.string.organize_tokens_title), iconResId = R.drawable.ic_filter_24, From d64383fc7d44d74db4045f40156776463b2e3d60 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Feb 2026 18:33:38 +0300 Subject: [PATCH 17/24] Updated on 2026-08-14 --- .../ui/components/tokenlist/TokenListItem.kt | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt index 4b016c5544..214c54f967 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerW4 +import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.account.AccountCharIcon import com.tangem.core.ui.components.account.AccountIconSize import com.tangem.core.ui.components.account.AccountResIcon @@ -257,22 +258,26 @@ fun ExpandedPortfolioHeader( } } - val text = when (val fiatAmountState = state.fiatAmountState) { - is TokenItemState.FiatAmountState.Content -> fiatAmountState.text - is TokenItemState.FiatAmountState.TextContent -> fiatAmountState.text - else -> null + when (val fiatAmountState = state.fiatAmountState) { + is TokenItemState.FiatAmountState.Content -> FiatAmount( + text = fiatAmountState.text, + isBalanceHidden = isBalanceHidden, + modifier = balanceTextModifier, + ) + is TokenItemState.FiatAmountState.TextContent -> FiatAmount( + text = fiatAmountState.text, + isBalanceHidden = isBalanceHidden, + modifier = balanceTextModifier, + ) + is TokenItemState.FiatAmountState.Loading -> TextShimmer( + style = TangemTheme.typography.caption1, + textSizeHeight = true, + modifier = balanceTextModifier + .padding(horizontal = 4.dp) + .fillMaxWidth(fraction = 0.2f), + ) + else -> Unit } - - Text( - text = text.orEmpty().orMaskWithStars(isBalanceHidden), - modifier = balanceTextModifier - .alignByBaseline() - .padding(horizontal = 4.dp), - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.caption1, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) } if (isCollapsable) { @@ -284,4 +289,18 @@ fun ExpandedPortfolioHeader( ) } } +} + +@Composable +private fun RowScope.FiatAmount(text: String, isBalanceHidden: Boolean, modifier: Modifier = Modifier) { + Text( + text = text.orMaskWithStars(isBalanceHidden), + modifier = modifier + .alignByBaseline() + .padding(horizontal = 4.dp), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption1, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) } \ No newline at end of file From 65a2f369c8539701b740986ac24266e1cc44f1f1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Feb 2026 20:36:41 +0500 Subject: [PATCH 18/24] Updated on 2026-08-14 --- .../AppsFlyerReferralParamsHandler.kt | 2 +- .../di/UserWalletsListManagerModule.kt | 3 +++ .../DefaultUserWalletsListRepository.kt | 21 +++++++++++++++++++ .../AppsFlyerReferralParamsHandlerTest.kt | 2 +- .../DefaultMobileWalletPromoRepository.kt | 4 ++-- .../domain/MobileWalletPromoRepository.kt | 2 +- .../SetShouldShowMobileWalletPromoUseCase.kt | 4 ++-- 7 files changed, 31 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandler.kt index 68a63f0c00..cd9e78634b 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandler.kt @@ -71,7 +71,7 @@ class AppsFlyerReferralParamsHandler @Inject constructor( private fun storeConversionData(refcode: String, campaign: String?) { coroutineScope.launch { mutex.withLock { - setShouldShowMobileWalletPromoUseCase() + setShouldShowMobileWalletPromoUseCase(true) .onLeft { Timber.e(it) } appsFlyerStore.storeIfAbsent( value = AppsFlyerConversionData(refcode = refcode, campaign = campaign), diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt index 1c3cfc1d98..92f1c4702e 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt @@ -17,6 +17,7 @@ import com.tangem.domain.visa.model.VisaCardActivationStatus import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository import com.tangem.domain.wallets.hot.HotWalletPasswordRequester import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.feature.referral.domain.MobileWalletPromoRepository import com.tangem.hot.sdk.TangemHotSdk import com.tangem.sdk.storage.AndroidSecureStorage import com.tangem.sdk.storage.AndroidSecureStorageV2 @@ -130,6 +131,7 @@ internal object UserWalletsListManagerModule { trackingContextProxy: TrackingContextProxy, analyticsEventHandler: AnalyticsEventHandler, hotWalletRepository: HotWalletRepository, + mobileWalletPromoRepository: MobileWalletPromoRepository, ): UserWalletsListRepository { val moshi = buildMoshi() val secureStorage = buildSecureStorage(applicationContext = applicationContext) @@ -180,6 +182,7 @@ internal object UserWalletsListManagerModule { trackingContextProxy = trackingContextProxy, analyticsEventHandler = analyticsEventHandler, hotWalletRepository = hotWalletRepository, + mobileWalletPromoRepository = mobileWalletPromoRepository, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt index 414ea8a0b3..8471ade6de 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt @@ -25,6 +25,7 @@ import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository import com.tangem.domain.wallets.hot.HotWalletPasswordRequester +import com.tangem.feature.referral.domain.MobileWalletPromoRepository import com.tangem.hot.sdk.TangemHotSdk import com.tangem.hot.sdk.model.HotWalletId import com.tangem.sdk.api.TangemSdkManager @@ -58,6 +59,7 @@ internal class DefaultUserWalletsListRepository( private val trackingContextProxy: TrackingContextProxy, private val analyticsEventHandler: AnalyticsEventHandler, private val hotWalletRepository: HotWalletRepository, + private val mobileWalletPromoRepository: MobileWalletPromoRepository, ) : UserWalletsListRepository { override val userWallets = MutableStateFlow?>(null) @@ -126,6 +128,8 @@ internal class DefaultUserWalletsListRepository( raise(SaveWalletError.WalletAlreadySaved(messageId = R.string.user_wallet_list_error_wallet_already_saved)) } + val isFirstWallet = userWallets.value?.isEmpty() == true + if (savePersistentInformation()) { publicInformationRepository.save(userWallet, canOverride) if (userWallet.isLocked.not()) { @@ -148,6 +152,10 @@ internal class DefaultUserWalletsListRepository( wallets.addOrReplace(userWallet) { it.walletId == userWallet.walletId } } + if (isFirstWallet) { + onFirstWalletCreated() + } + // update the selectedUserWallet state if it is the only wallet if (userWallets.value?.size == 1) { selectedUserWalletRepository.set(userWallet.walletId) @@ -221,6 +229,9 @@ internal class DefaultUserWalletsListRepository( val newSelected = updatedWallets?.findAvailableUserWallet( currentWallets.indexOfFirstOrNull { it.walletId == currentSelected.walletId } ?: 0, ) + if (newSelected == null) { + onAllWalletsDeleted() + } selectedUserWalletRepository.set(newSelected?.walletId) newSelected } @@ -542,4 +553,14 @@ internal class DefaultUserWalletsListRepository( private fun trackWalletUpgradeEvent() { analyticsEventHandler.send(event = WalletSettingsAnalyticEvents.WalletUpgraded()) } + + private suspend fun onFirstWalletCreated() { + // reset flag (that is set from AF deeplink) after creating a new wallet + mobileWalletPromoRepository.setShouldShowMobileWalletPromo(false) + } + + private suspend fun onAllWalletsDeleted() { + // reset flag (that is set from AF deeplink) after removing the last wallet + mobileWalletPromoRepository.setShouldShowMobileWalletPromo(false) + } } \ No newline at end of file diff --git a/app/src/test/kotlin/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandlerTest.kt b/app/src/test/kotlin/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandlerTest.kt index c804791992..e83e93807b 100644 --- a/app/src/test/kotlin/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandlerTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/common/analytics/appsflyer/AppsFlyerReferralParamsHandlerTest.kt @@ -26,7 +26,7 @@ class AppsFlyerReferralParamsHandlerTest { private val appsFlyerStore: AppsFlyerStore = mockk(relaxUnitFun = true) private val setShouldShowMobileWalletPromoUseCase: SetShouldShowMobileWalletPromoUseCase = mockk { - coEvery { this@mockk.invoke() } returns Unit.right() + coEvery { this@mockk.invoke(true) } returns Unit.right() } private val handler = AppsFlyerReferralParamsHandler( appsFlyerStore = appsFlyerStore, diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/data/DefaultMobileWalletPromoRepository.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/data/DefaultMobileWalletPromoRepository.kt index 56a482e96d..1fd76e4838 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/data/DefaultMobileWalletPromoRepository.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/data/DefaultMobileWalletPromoRepository.kt @@ -14,9 +14,9 @@ internal class DefaultMobileWalletPromoRepository @Inject constructor( return appPreferencesStore.getSyncOrDefault(key = SHOULD_SHOW_MOBILE_WALLET_PROMO_KEY, default = false) } - override suspend fun setShouldShowMobileWalletPromo(value: Boolean) { + override suspend fun setShouldShowMobileWalletPromo(shouldShowPromo: Boolean) { appPreferencesStore.editData { preferences -> - preferences[SHOULD_SHOW_MOBILE_WALLET_PROMO_KEY] = value + preferences[SHOULD_SHOW_MOBILE_WALLET_PROMO_KEY] = shouldShowPromo } } diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/MobileWalletPromoRepository.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/MobileWalletPromoRepository.kt index 642ab878c7..95cba17c9d 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/MobileWalletPromoRepository.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/MobileWalletPromoRepository.kt @@ -4,5 +4,5 @@ interface MobileWalletPromoRepository { suspend fun shouldShowMobileWalletPromo(): Boolean - suspend fun setShouldShowMobileWalletPromo(value: Boolean) + suspend fun setShouldShowMobileWalletPromo(shouldShowPromo: Boolean) } \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/SetShouldShowMobileWalletPromoUseCase.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/SetShouldShowMobileWalletPromoUseCase.kt index 9049cc72bb..335669eabf 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/SetShouldShowMobileWalletPromoUseCase.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/SetShouldShowMobileWalletPromoUseCase.kt @@ -9,10 +9,10 @@ class SetShouldShowMobileWalletPromoUseCase @Inject constructor( private val userWalletsListRepository: UserWalletsListRepository, ) { - suspend operator fun invoke(): Either = Either.catch { + suspend operator fun invoke(shouldShowPromo: Boolean): Either = Either.catch { val wallets = userWalletsListRepository.userWallets.value if (wallets.isNullOrEmpty()) { - mobileWalletPromoRepository.setShouldShowMobileWalletPromo(true) + mobileWalletPromoRepository.setShouldShowMobileWalletPromo(shouldShowPromo) } } } \ No newline at end of file From 481eb81f7a0d9982fe95e958221a9adc44affbb3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Feb 2026 18:55:13 +0300 Subject: [PATCH 19/24] Updated on 2026-08-14 --- .../hotwallet/accesscode/AccessCodeModel.kt | 51 ++++++++++--------- gradle/tangem_dependencies.toml | 2 +- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/AccessCodeModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/AccessCodeModel.kt index 3ae84d85b1..70ac01eac8 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/AccessCodeModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/AccessCodeModel.kt @@ -31,14 +31,10 @@ import com.tangem.hot.sdk.TangemHotSdk import com.tangem.hot.sdk.model.HotAuth import com.tangem.hot.sdk.model.HotWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay +import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine import javax.inject.Inject import kotlin.coroutines.resume @@ -220,19 +216,11 @@ internal class AccessCodeModel @Inject constructor( } } + /** + * Set access code for hot wallet + * !!! Be aware that order of operations is important here !!! + */ private suspend fun setCodeOperation(userWallet: UserWallet.Hot, accessCode: String) { - userWalletsListRepository.setLock( - userWalletId = userWallet.walletId, - lockMethod = UserWalletsListRepository.LockMethod.AccessCode(accessCode.toCharArray()), - ) - - if (walletsRepository.useBiometricAuthentication()) { - userWalletsListRepository.setLock( - userWalletId = userWallet.walletId, - lockMethod = UserWalletsListRepository.LockMethod.Biometric, - ) - } - val unlockHotWallet = getHotWalletContextualUnlockUseCase(userWallet.hotWalletId) .getOrNull() ?: run { @@ -243,22 +231,39 @@ internal class AccessCodeModel @Inject constructor( hotWalletAccessor.unlockContextual(userWallet.hotWalletId) } - var updatedHotWalletId = tangemHotSdk.changeAuth( + val newHotWalletIdWithPass = tangemHotSdk.changeAuth( unlockHotWallet = unlockHotWallet, auth = HotAuth.Password(accessCode.toCharArray()), ) + userWalletsListRepository.saveWithoutLock( + userWallet.copy(hotWalletId = newHotWalletIdWithPass), + canOverride = true, + ) + + userWalletsListRepository.setLock( + userWalletId = userWallet.walletId, + lockMethod = UserWalletsListRepository.LockMethod.AccessCode(accessCode.toCharArray()), + ) + if (walletsRepository.requireAccessCode().not() && canUseBiometryUseCase()) { - updatedHotWalletId = tangemHotSdk.changeAuth( + val newHotWalletIdWithBiometry = tangemHotSdk.changeAuth( unlockHotWallet = unlockHotWallet, auth = HotAuth.Biometry, ) + + userWalletsListRepository.saveWithoutLock( + userWallet.copy(hotWalletId = newHotWalletIdWithBiometry), + canOverride = true, + ) } - userWalletsListRepository.saveWithoutLock( - userWallet.copy(hotWalletId = updatedHotWalletId), - canOverride = true, - ) + if (walletsRepository.useBiometricAuthentication()) { + userWalletsListRepository.setLock( + userWalletId = userWallet.walletId, + lockMethod = UserWalletsListRepository.LockMethod.Biometric, + ) + } clearHotWalletContextualUnlockUseCase.invoke(params.userWalletId) } diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index d015ed0aba..4372af7f4b 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -11,7 +11,7 @@ tangemCardSdk = "releases-5.33-576" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ -tangemHotSdk = "develop-541" +tangemHotSdk = "develop-545" #tangemHotSdk = "0.0.1" # Keep it! - used for local builds ^ From 20e2ffa64c393d1f2d5acd3037cf150db0a68626 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Feb 2026 19:38:23 +0300 Subject: [PATCH 20/24] Updated on 2026-08-14 --- .../hotwallet/forgetwallet/ui/ForgetWalletContent.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/forgetwallet/ui/ForgetWalletContent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/forgetwallet/ui/ForgetWalletContent.kt index 5dfc22b13c..ae82837b2f 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/forgetwallet/ui/ForgetWalletContent.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/forgetwallet/ui/ForgetWalletContent.kt @@ -3,10 +3,13 @@ package com.tangem.features.hotwallet.forgetwallet.ui import android.content.res.Configuration import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -89,7 +92,13 @@ internal fun ForgetWalletContent(state: ForgetWalletUM, modifier: Modifier = Mod @Composable private fun CheckboxItem(checked: Boolean, onCheckedChange: () -> Unit, text: String, modifier: Modifier = Modifier) { Row( - modifier = modifier.fillMaxWidth(), + modifier = modifier + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() }, + onClick = { onCheckedChange() }, + ) + .fillMaxWidth(), horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.Top, ) { From 47a7c02d295c0d4175f0ecff12a29af2e737488b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Feb 2026 20:34:51 +0300 Subject: [PATCH 21/24] Updated on 2026-08-14 --- .../com/tangem/utils/coroutines/JobHolder.kt | 4 ++ features/onramp/impl/build.gradle.kts | 5 ++ .../onramp/hottokens/model/HotCryptoModel.kt | 66 ++++++++++++++++++- .../portfolio/model/OnrampAddTokenModel.kt | 27 +++++--- 4 files changed, 92 insertions(+), 10 deletions(-) diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/JobHolder.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/JobHolder.kt index fa2f9a10b6..d6ac93583e 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/JobHolder.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/JobHolder.kt @@ -30,6 +30,10 @@ class JobHolder { job = null } + suspend fun join() { + job?.join() + } + fun isEmpty() = job == null } diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 290dd27ce5..1e3239f200 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -50,6 +50,7 @@ dependencies { implementation(projects.domain.account.status) implementation(projects.domain.appTheme) implementation(projects.domain.appTheme.models) + implementation(projects.data.common) /** DI */ implementation(deps.hilt.android) @@ -68,6 +69,10 @@ dependencies { implementation(deps.compose.shimmer) implementation(deps.compose.coil) + /** Tangem libraries */ + implementation(tangemDeps.blockchain) + implementation(projects.libs.blockchainSdk) + /** Other */ implementation(deps.decompose.ext.compose) implementation(deps.kotlin.immutable.collections) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt index 75812c5cef..40ac56570b 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt @@ -8,18 +8,28 @@ import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.popToFirst import com.arkivanov.decompose.router.stack.pushNew import com.arkivanov.decompose.router.stack.replaceAll +import com.tangem.blockchainsdk.utils.toBlockchain +import com.tangem.blockchainsdk.utils.toCoinId import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.extensions.resourceReference +import com.tangem.data.common.currency.getCoinId +import com.tangem.data.common.currency.getTokenId +import com.tangem.data.common.currency.isCustomCoin +import com.tangem.data.common.currency.isCustomToken +import com.tangem.data.common.network.NetworkFactory import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.account.derivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.onramp.GetHotCryptoUseCase import com.tangem.domain.onramp.model.HotCryptoCurrency import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase @@ -70,6 +80,7 @@ internal class HotCryptoModel @Inject constructor( private val accountsFeatureToggles: AccountsFeatureToggles, private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, val portfolioSelectorController: PortfolioSelectorController, + private val networkFactory: NetworkFactory, private val portfolioFetcherFactory: PortfolioFetcher.Factory, ) : Model(), OnrampAddTokenComponent.Callbacks by callbackDelegate { @@ -172,10 +183,15 @@ internal class HotCryptoModel @Inject constructor( if (isSingleAccount) { val account = hotCryptoPortfolioData.wallet.accounts.first().account - val tokenToAdd = AddHotCryptoData( + val cryptoCurrency = updateCryptoCurrency( cryptoCurrency = currency.cryptoCurrency, userWallet = userWallet, account = account, + ) + val tokenToAdd = AddHotCryptoData( + cryptoCurrency = requireNotNull(cryptoCurrency), + userWallet = userWallet, + account = account, isMorePortfolioAvailable = false, ) hotCryptoToAddDataFlow.emit(tokenToAdd) @@ -188,10 +204,15 @@ internal class HotCryptoModel @Inject constructor( .selectedAccountWithData(requireNotNull(portfolioFetcher)) .filterNotNull() .map { (_, selectedAccount) -> - AddHotCryptoData( + val cryptoCurrency = updateCryptoCurrency( cryptoCurrency = currency.cryptoCurrency, userWallet = userWallet, account = selectedAccount, + ) + AddHotCryptoData( + cryptoCurrency = requireNotNull(cryptoCurrency), + userWallet = userWallet, + account = selectedAccount, isMorePortfolioAvailable = true, ) } @@ -251,6 +272,47 @@ internal class HotCryptoModel @Inject constructor( return@isEnabled isNotAddedHotCrypto } } + + // todo account move to common module + private fun updateCryptoCurrency( + cryptoCurrency: CryptoCurrency, + userWallet: UserWallet, + account: AccountStatus, + ): CryptoCurrency? { + val derivationIndex = account.account.derivationIndex ?: return null + val blockchain = cryptoCurrency.network.toBlockchain() + + val network = networkFactory.create( + blockchain = blockchain, + extraDerivationPath = null, + accountIndex = derivationIndex, + userWallet = userWallet, + ) ?: return null + + return when (cryptoCurrency) { + is CryptoCurrency.Coin -> { + val id = getCoinId(network, network.toBlockchain().toCoinId()) + cryptoCurrency.copy( + id = id, + network = network, + isCustom = isCustomCoin(network), + ) + } + + is CryptoCurrency.Token -> { + val id = getTokenId( + network = network, + rawTokenId = cryptoCurrency.id.rawCurrencyId, + contractAddress = cryptoCurrency.contractAddress, + ) + cryptoCurrency.copy( + id = id, + network = network, + isCustom = isCustomToken(id, network), + ) + } + } + } } @ModelScoped diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddTokenModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddTokenModel.kt index d786999cad..0432278b80 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddTokenModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddTokenModel.kt @@ -10,6 +10,8 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.message.ToastMessage import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.usecase.BackendId import com.tangem.domain.wallets.usecase.ColdWalletAndHasMissedDerivationsUseCase @@ -47,10 +49,14 @@ internal class OnrampAddTokenModel @Inject constructor( params.tokenToAdd .distinctUntilChanged() .mapLatest { tokenToAdd: AddHotCryptoData -> - addTokenJob.cancel() + addTokenJob.join() val backendId = tokenToAdd.cryptoCurrency.network.backendId val userWalletId = tokenToAdd.account.accountId.userWalletId - val isTangemIconVisible = needColdWalletInteraction(userWalletId, backendId) + val isTangemIconVisible = needColdWalletInteraction( + walletId = userWalletId, + backendId = backendId, + cryptoCurrency = tokenToAdd.cryptoCurrency, + ) uiBuilder.updateContent( tokenToAdd = tokenToAdd, isTangemIconVisible = isTangemIconVisible, @@ -79,7 +85,9 @@ internal class OnrampAddTokenModel @Inject constructor( userWalletId = accountId.userWalletId, currencyId = cryptoCurrency.id, network = cryptoCurrency.network, - ).firstOrNull() + ) + .filter { (_, status) -> status.value !is CryptoCurrencyStatus.Loading } + .firstOrNull() if (status == null) { processError(error = null) } else { @@ -88,11 +96,14 @@ internal class OnrampAddTokenModel @Inject constructor( uiState.value = um.toggleProgress(false) } - private suspend fun needColdWalletInteraction(walletId: UserWalletId, backendId: BackendId): Boolean = - coldWalletAndHasMissedDerivationsUseCase.invoke( - userWalletId = walletId, - networksWithDerivationPath = mapOf(backendId to null), - ) + private suspend fun needColdWalletInteraction( + walletId: UserWalletId, + backendId: BackendId, + cryptoCurrency: CryptoCurrency, + ): Boolean = coldWalletAndHasMissedDerivationsUseCase.invoke( + userWalletId = walletId, + networksWithDerivationPath = mapOf(backendId to cryptoCurrency.network.derivationPath.value), + ) private fun processError(error: Throwable?) { val message = error?.message?.let { stringReference(it) } From f89ab30e5a1749bdf662e1365770ee3b4b8c904f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Feb 2026 04:44:36 +0300 Subject: [PATCH 22/24] Updated on 2026-08-14 --- .../DefaultDeviceSecurityInfoProvider.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/core/security/DefaultDeviceSecurityInfoProvider.kt b/app/src/main/java/com/tangem/tap/core/security/DefaultDeviceSecurityInfoProvider.kt index 7463e33236..10a4807522 100644 --- a/app/src/main/java/com/tangem/tap/core/security/DefaultDeviceSecurityInfoProvider.kt +++ b/app/src/main/java/com/tangem/tap/core/security/DefaultDeviceSecurityInfoProvider.kt @@ -2,12 +2,22 @@ package com.tangem.tap.core.security import com.dexprotector.rtc.RtcStatus import com.tangem.security.DeviceSecurityInfoProvider +import timber.log.Timber internal class DefaultDeviceSecurityInfoProvider : DeviceSecurityInfoProvider { override val isRooted: Boolean - get() = RtcStatus.getRtcStatus().root + get() = getRtcStatusSafely()?.root == true override val isBootloaderUnlocked: Boolean - get() = RtcStatus.getRtcStatus().unlockedBootloader + get() = getRtcStatusSafely()?.unlockedBootloader == true override val isXposed: Boolean - get() = RtcStatus.getRtcStatus().xposed + get() = getRtcStatusSafely()?.xposed == true + + private fun getRtcStatusSafely(): RtcStatus? { + return try { + RtcStatus.getRtcStatus() + } catch (e: Throwable) { + Timber.e(e) + null + } + } } \ No newline at end of file From 64fda96648152d2312ff1421c45eeed3888af2bb Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Feb 2026 04:50:44 +0300 Subject: [PATCH 23/24] Updated on 2026-08-14 --- .../tap/di/domain/SettingsDomainModule.kt | 13 +++++--- .../DefaultLegacySettingsRepository.kt | 4 +++ data/wallets/build.gradle.kts | 1 + .../wallets/hot/DefaultHotWalletAccessor.kt | 20 +++++++++--- .../domain/settings/CanUseBiometryUseCase.kt | 3 ++ .../repositories/LegacySettingsRepository.kt | 2 ++ .../hotwallet/accesscode/AccessCodeModel.kt | 32 +++++++++++-------- .../HotAccessCodeRequestModel.kt | 2 +- .../welcome/impl/model/WelcomeModel.kt | 2 +- 9 files changed, 56 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index 2cc898457b..ce2088f11c 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -7,6 +7,7 @@ import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import com.tangem.domain.settings.* import com.tangem.domain.settings.repositories.AppRatingRepository +import com.tangem.domain.settings.repositories.LegacySettingsRepository import com.tangem.domain.settings.repositories.PermissionRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase @@ -68,10 +69,14 @@ internal object SettingsDomainModule { @Provides @Singleton - fun providesCanUseBiometryUseCase(tangemSdkManager: TangemSdkManager): CanUseBiometryUseCase { - return CanUseBiometryUseCase( - legacySettingsRepository = DefaultLegacySettingsRepository(tangemSdkManager = tangemSdkManager), - ) + fun provideLegacySettingsRepository(tangemSdkManager: TangemSdkManager): LegacySettingsRepository { + return DefaultLegacySettingsRepository(tangemSdkManager = tangemSdkManager) + } + + @Provides + @Singleton + fun providesCanUseBiometryUseCase(legacySettingsRepository: LegacySettingsRepository): CanUseBiometryUseCase { + return CanUseBiometryUseCase(legacySettingsRepository = legacySettingsRepository) } @Provides diff --git a/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt b/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt index e074ddc8f8..b075775429 100644 --- a/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt @@ -8,4 +8,8 @@ internal class DefaultLegacySettingsRepository( ) : LegacySettingsRepository { override suspend fun canUseBiometry(): Boolean = tangemSdkManager.checkCanUseBiometry() + + override suspend fun canUseBiometryStrict(): Boolean { + return tangemSdkManager.checkCanUseBiometry() && tangemSdkManager.checkNeedEnrollBiometrics().not() + } } \ No newline at end of file diff --git a/data/wallets/build.gradle.kts b/data/wallets/build.gradle.kts index 5949dd0174..aafa1080db 100644 --- a/data/wallets/build.gradle.kts +++ b/data/wallets/build.gradle.kts @@ -31,6 +31,7 @@ dependencies { implementation(projects.domain.tokens.models) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) + implementation(projects.domain.settings) /** DI */ implementation(deps.hilt.android) diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotWalletAccessor.kt b/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotWalletAccessor.kt index f829c76c6f..62668755ef 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotWalletAccessor.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotWalletAccessor.kt @@ -4,6 +4,7 @@ import com.tangem.common.core.TangemSdkError import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.settings.repositories.LegacySettingsRepository import com.tangem.domain.wallets.hot.HotWalletAccessor import com.tangem.domain.wallets.hot.HotWalletPasswordRequester import com.tangem.domain.wallets.repository.WalletsRepository @@ -17,18 +18,21 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject +import javax.inject.Singleton +@Singleton class DefaultHotWalletAccessor @Inject constructor( private val tangemHotSdk: TangemHotSdk, private val userWalletsListRepository: UserWalletsListRepository, private val hotWalletPasswordRequester: HotWalletPasswordRequester, private val walletsRepository: WalletsRepository, + private val legacySettingsRepository: LegacySettingsRepository, dispatchers: CoroutineDispatcherProvider, ) : HotWalletAccessor { private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io) - private var contextualUnlockHotWallet: ConcurrentHashMap = ConcurrentHashMap() + private val contextualUnlockHotWallet: ConcurrentHashMap = ConcurrentHashMap() override suspend fun signHashes(hotWalletId: HotWalletId, dataToSign: List): List = hotSdkRequest(hotWalletId) { unlock -> @@ -82,7 +86,7 @@ class DefaultHotWalletAccessor @Inject constructor( } private suspend fun hotSdkRequest(hotWalletId: HotWalletId, block: suspend (unlock: UnlockHotWallet) -> T): T { - val isAccessCodeRequired = walletsRepository.requireAccessCode() + val isAccessCodeRequired = isAccessCodeRequired() val auth = when (hotWalletId.authType) { HotWalletId.AuthType.NoPassword -> HotAuth.NoAuth @@ -134,7 +138,7 @@ class DefaultHotWalletAccessor @Inject constructor( } private suspend fun updateBiometryAuthIfNeeded(hotWalletId: HotWalletId, originalAuth: HotAuth) { - val isAccessCodeRequired = walletsRepository.requireAccessCode() + val isAccessCodeRequired = isAccessCodeRequired() val isUseBiometricAuthenticationEnabled = walletsRepository.useBiometricAuthentication() if (originalAuth is HotAuth.Password && isUseBiometricAuthenticationEnabled && isAccessCodeRequired.not()) { @@ -167,7 +171,7 @@ class DefaultHotWalletAccessor @Inject constructor( ): T = runSuspendCatching { block(auth) }.getOrElse { exception -> - if (auth is HotAuth.Biometry && exception.isBiometryError()) { + if (auth is HotAuth.Biometry && (exception.isBiometryError() || exception.isBiometryReset())) { val shouldRetryBiometry = exception is TangemSdkError.AuthenticationCanceled // fallback to password if biometry fails @@ -215,6 +219,14 @@ class DefaultHotWalletAccessor @Inject constructor( ?: throw TangemSdkError.UserCancelled() } + private suspend fun isAccessCodeRequired(): Boolean { + return walletsRepository.requireAccessCode() || legacySettingsRepository.canUseBiometryStrict().not() + } + + private fun Throwable.isBiometryReset(): Boolean { + return this is IllegalStateException + } + private fun Throwable.isBiometryError(): Boolean { return this is TangemSdkError.AuthenticationFailed || this is TangemSdkError.AuthenticationCanceled || diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt index 880f941db8..c84d1f6361 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt @@ -4,5 +4,8 @@ import com.tangem.domain.settings.repositories.LegacySettingsRepository class CanUseBiometryUseCase(private val legacySettingsRepository: LegacySettingsRepository) { + @Deprecated("You probably want to use strict() instead. Check implementation", ReplaceWith("strict()")) suspend operator fun invoke(): Boolean = legacySettingsRepository.canUseBiometry() + + suspend fun strict(): Boolean = legacySettingsRepository.canUseBiometryStrict() } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt index e1f1b8ebb7..f37a051b33 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt @@ -3,4 +3,6 @@ package com.tangem.domain.settings.repositories interface LegacySettingsRepository { suspend fun canUseBiometry(): Boolean + + suspend fun canUseBiometryStrict(): Boolean } \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/AccessCodeModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/AccessCodeModel.kt index 70ac01eac8..9b046ff611 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/AccessCodeModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/AccessCodeModel.kt @@ -31,6 +31,8 @@ import com.tangem.hot.sdk.TangemHotSdk import com.tangem.hot.sdk.model.HotAuth import com.tangem.hot.sdk.model.HotWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.saveIn import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -63,6 +65,8 @@ internal class AccessCodeModel @Inject constructor( private val params = paramsContainer.require() + private val settingCodeJobHolder = JobHolder() + internal val uiState: StateFlow field = MutableStateFlow(getInitialState()) @@ -197,23 +201,25 @@ internal class AccessCodeModel @Inject constructor( } } - private fun setCode(userWalletId: UserWalletId, accessCode: String) { + private suspend fun setCode(userWalletId: UserWalletId, accessCode: String) = coroutineScope { + if (settingCodeJobHolder.isActive) { + return@coroutineScope + } + params.callbacks.onAccessCodeUpdateStarted(params.userWalletId) - modelScope.launch { - val userWallet = getUserWalletUseCase(userWalletId) - .getOrElse { error("User wallet with id $userWalletId not found") } - .requireHotWallet() + val userWallet = getUserWalletUseCase(userWalletId) + .getOrElse { error("User wallet with id $userWalletId not found") } + .requireHotWallet() - tryToAskForBiometry() + tryToAskForBiometry() - val settingCodeJob = launch(dispatchers.main) { - setCodeOperation(userWallet, accessCode) - params.callbacks.onAccessCodeUpdated(params.userWalletId) - } + val settingCodeJob = launch(dispatchers.main) { + setCodeOperation(userWallet, accessCode) + params.callbacks.onAccessCodeUpdated(params.userWalletId) + }.saveIn(settingCodeJobHolder) - setLoadingIfLongJob(settingCodeJob) - } + setLoadingIfLongJob(settingCodeJob) } /** @@ -246,7 +252,7 @@ internal class AccessCodeModel @Inject constructor( lockMethod = UserWalletsListRepository.LockMethod.AccessCode(accessCode.toCharArray()), ) - if (walletsRepository.requireAccessCode().not() && canUseBiometryUseCase()) { + if (walletsRepository.requireAccessCode().not() && canUseBiometryUseCase.strict()) { val newHotWalletIdWithBiometry = tangemHotSdk.changeAuth( unlockHotWallet = unlockHotWallet, auth = HotAuth.Biometry, diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscoderequest/HotAccessCodeRequestModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscoderequest/HotAccessCodeRequestModel.kt index ba2bbf512a..1a2cb122b6 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscoderequest/HotAccessCodeRequestModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscoderequest/HotAccessCodeRequestModel.kt @@ -219,7 +219,7 @@ internal class HotAccessCodeRequestModel @Inject constructor( } private suspend fun HotWalletPasswordRequester.AttemptRequest.isBiometryButtonVisible(): Boolean = - hasBiometry && canUseBiometryUseCase() + hasBiometry && canUseBiometryUseCase.strict() private fun dismissState() { uiState.update { diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt index 83ab025490..bc5b6dc548 100644 --- a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt @@ -254,7 +254,7 @@ internal class WelcomeModel @Inject constructor( } private suspend fun canUnlockWithBiometrics(): Boolean { - return canUseBiometryUseCase() && walletsRepository.useBiometricAuthentication() + return canUseBiometryUseCase.strict() && walletsRepository.useBiometricAuthentication() } suspend fun nonBiometricUnlockWallet(userWalletId: UserWalletId) { From d8e742975f43690c4f3b9bfb68b30395a3023b46 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Feb 2026 01:51:03 +0000 Subject: [PATCH 24/24] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 4372af7f4b..1cc9fdda26 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,13 +5,13 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.33-1423" +tangemBlockchainSdk = "develop-1419" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.33-576" +tangemCardSdk = "develop-577" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ -tangemHotSdk = "develop-545" +tangemHotSdk = "develop-539" #tangemHotSdk = "0.0.1" # Keep it! - used for local builds ^