From c0471dbf9fc04833a0b45a6cdafebb7c0b370fd5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Jul 2024 14:51:41 +0100 Subject: [PATCH 01/53] Updated on 2026-08-14 --- .../tap/proxy/TransactionManagerImpl.kt | 39 +-- .../models/response/ExchangeDataResponse.kt | 9 +- core/res/src/main/res/values-ru/strings.xml | 2 +- .../src/main/res/values-zh-rTW/strings.xml | 2 +- core/res/src/main/res/values/strings.xml | 2 +- .../tokens/utils/CryptoCurrencyFactory.kt | 2 +- .../DefaultTransactionRepository.kt | 29 +-- .../transaction/TransactionRepository.kt | 1 - .../usecase/CreateTransactionUseCase.kt | 2 - .../swap/converters/ExpressDataConverter.kt | 12 + .../models/domain/ExpressTransactionModel.kt | 10 + .../swap/domain/models/ui/SwapState.kt | 9 + .../feature/swap/domain/SwapInteractorImpl.kt | 227 ++++++++++++++---- .../swap/models/SwapPermissionStateHolder.kt | 1 + .../feature/swap/models/SwapStateHolder.kt | 12 +- .../tangem/feature/swap/ui/StateBuilder.kt | 65 +++-- .../swap/ui/SwapPermissionBottomSheet.kt | 2 + .../feature/swap/ui/SwapScreenContent.kt | 2 +- .../tangem/feature/swap/ui/TransactionCard.kt | 13 +- .../tangem/lib/crypto/TransactionManager.kt | 7 +- 20 files changed, 315 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index a18e07daef..8177d10df8 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -161,7 +161,7 @@ class TransactionManagerImpl( @Throws(IllegalStateException::class) override suspend fun getFee( networkId: String, - amountToSend: BigDecimal, + amountToSend: Amount, currencyToSend: Currency, destinationAddress: String, increaseBy: Int?, @@ -174,7 +174,7 @@ class TransactionManagerImpl( if (walletManager is EthereumOptimisticRollupWalletManager) { return getFeeForOptimismBlockchain( walletManager = walletManager, - amount = createAmount(amountToSend, currencyToSend, blockchain), + amount = amountToSend, destinationAddress = destinationAddress, data = data, ) @@ -183,7 +183,6 @@ class TransactionManagerImpl( walletManager = walletManager, blockchain = blockchain, amountToSend = amountToSend, - currency = currencyToSend, destinationAddress = destinationAddress, data = data, increaseBy = increaseBy, @@ -192,8 +191,6 @@ class TransactionManagerImpl( return getFeeForBlockchain( walletManager = walletManager, amountToSend = amountToSend, - currency = currencyToSend, - blockchain = blockchain, destinationAddress = destinationAddress, ) } @@ -209,13 +206,11 @@ class TransactionManagerImpl( private suspend fun getFeeForBlockchain( walletManager: WalletManager, - amountToSend: BigDecimal, - currency: Currency, - blockchain: Blockchain, + amountToSend: Amount, destinationAddress: String, ): ProxyFees { val fee = (walletManager as? TransactionSender)?.getFee( - amount = createAmount(amountToSend, currency, blockchain), + amount = amountToSend, destination = destinationAddress, ) ?: error("Cannot cast to TransactionSender") return when (fee) { @@ -268,17 +263,14 @@ class TransactionManagerImpl( private suspend fun getFeeForEthereumBlockchain( walletManager: EthereumWalletManager, blockchain: Blockchain, - amountToSend: BigDecimal, - currency: Currency, + amountToSend: Amount, destinationAddress: String, data: String?, increaseBy: Int?, ): ProxyFees { val gasLimit = getGasLimit( evmWalletManager = walletManager, - blockchain = blockchain, amount = amountToSend, - currency = currency, destinationAddress = destinationAddress, data = data, ).increaseBigIntegerByPercents(increaseBy) @@ -332,23 +324,20 @@ class TransactionManagerImpl( } } - @Suppress("LongParameterList") private suspend fun getGasLimit( evmWalletManager: EthereumWalletManager, - blockchain: Blockchain, - amount: BigDecimal, - currency: Currency, + amount: Amount, destinationAddress: String, data: String?, ): BigInteger { val result = if (data.isNullOrEmpty()) { evmWalletManager.getGasLimit( - amount = createAmount(amount, currency, blockchain), + amount = amount, destination = destinationAddress, ) } else { evmWalletManager.getGasLimit( - amount = createAmount(amount, currency, blockchain), + amount = amount, destination = destinationAddress, data = data, ) @@ -363,6 +352,18 @@ class TransactionManagerImpl( } } + override suspend fun getFeeForGas(networkId: String, gas: BigInteger, derivationPath: String?): ProxyFees { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val walletManager = getActualWalletManager(blockchain, derivationPath) + val gasPriceResult = (walletManager as? EthereumWalletManager)?.getGasPrice() + ?: error("not supported for $blockchain") + val gasPrice = when (gasPriceResult) { + is Result.Failure -> error("fail to receive gasPrice") + is Result.Success -> gasPriceResult.data + } + return createMultipleProxyFees(gasPrice, gas, blockchain) + } + private fun handleSendResult(result: Result): SendTxResult { when (result) { is Result.Success -> { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeDataResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeDataResponse.kt index 7496a0a8e2..5e1eb6d5d3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeDataResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeDataResponse.kt @@ -1,7 +1,6 @@ package com.tangem.datasource.api.express.models.response import com.squareup.moshi.Json -import java.math.BigDecimal data class ExchangeDataResponseWithTxDetails( val dataResponse: ExchangeDataResponse, @@ -50,7 +49,10 @@ data class TxDetails( val txData: String?, // transaction data if DEX, null if CEX @Json(name = "txValue") - val txValue: BigDecimal, // amount (same as fromAmount) + val txValue: String, // amount (same as fromAmount for Coin, but for bridge equal to otherNativeFee) + + @Json(name = "otherNativeFee") + val otherNativeFee: String?, @Json(name = "externalTxId") val externalTxId: String?, // null if DEX, provider transaction id if CEX @@ -63,6 +65,9 @@ data class TxDetails( @Json(name = "txExtraId") val txExtraId: String?, + + @Json(name = "gas") + val gas: String?, ) enum class TxType { diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index e70467d63f..3ee8a28d64 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -568,7 +568,7 @@ Дать разрешение Укажите лимит доступа к выбранному токену Количество %s - Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s + Чтобы продолжить, вам нужно разрешить смарт-контракту %1$s использовать ваш %2$s Безлимитно В процессе Обменять 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 e0b8c69381..2a44adee3c 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -283,7 +283,7 @@ 允許 賦予權限 數量 %s - 要繼續,您需要允許 1inch 智能合約使用您的 %s + 要繼續,您需要允許 %1$s 智能合約使用您的 %2$s 進行中 交易 選擇代幣 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 40b0b45e01..ffdb3de5f8 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -561,7 +561,7 @@ Give Permission Specify the approve limit for the selected token Amount %s - To continue, grant 1inch smart contracts permission to use your %s + To continue, grant %1$s smart contracts permission to use your %2$s Unlimited In progress Swap diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CryptoCurrencyFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CryptoCurrencyFactory.kt index 55b2b0b161..b1e215ed19 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CryptoCurrencyFactory.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CryptoCurrencyFactory.kt @@ -99,7 +99,7 @@ class CryptoCurrencyFactory { decimals = cryptoCurrency.decimals, id = cryptoCurrency.id.rawCurrencyId, ) - val blockchain = Blockchain.fromNetworkId(cryptoCurrency.network.id.value) ?: Blockchain.Unknown + val blockchain = Blockchain.fromNetworkId(cryptoCurrency.network.backendId) ?: Blockchain.Unknown val id = getTokenId(network, sdkToken) return CryptoCurrency.Token( id = id, diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index d36cb4f74b..3f3d8aaa68 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -19,7 +19,6 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import timber.log.Timber -import java.math.BigDecimal internal class DefaultTransactionRepository( private val walletManagersFacade: WalletManagersFacade, @@ -34,7 +33,6 @@ internal class DefaultTransactionRepository( destination: String, userWalletId: UserWalletId, network: Network, - isSwap: Boolean, txExtras: TransactionExtras?, hash: String?, ): TransactionData? = withContext(coroutineDispatcherProvider.io) { @@ -51,7 +49,6 @@ internal class DefaultTransactionRepository( memo = memo, destination = destination, network = network, - isSwap = isSwap, txExtras = txExtras, hash = hash, ) @@ -84,7 +81,6 @@ internal class DefaultTransactionRepository( memo = memo, destination = destination, network = network, - isSwap = isSwap, txExtras = txExtras, hash = hash, ) @@ -118,23 +114,15 @@ internal class DefaultTransactionRepository( memo: String?, destination: String, network: Network, - isSwap: Boolean, txExtras: TransactionExtras?, hash: String?, ): TransactionData { - // TODO: refactor workaround to use general mechanism in bsdk for build tx for DEX - val txAmount = if (isSwap) { - createAmountForSwap(amount) - } else { - amount - } - if (txExtras != null && memo != null) { // throw error for now to avoid programmers errors when use extras error("Both txExtras and memo provided, use only one of them") } val extras = txExtras ?: getMemoExtras(network.id.value, memo) - return createTransaction(txAmount, fee, destination).copy( + return createTransaction(amount, fee, destination).copy( hash = hash, extras = extras, ) @@ -163,19 +151,4 @@ internal class DefaultTransactionRepository( else -> null } } - - private fun createAmountForSwap(amount: Amount): Amount { - return when (amount.type) { - is AmountType.Coin -> amount - else -> { - // 1. when creates swap amount for NonNativeToken, amount should be ZERO - // 2. Amount has .Coin type, as workaround to use destinationAddress in bsdk, not contractAddress - Amount( - currencySymbol = amount.currencySymbol, - value = BigDecimal.ZERO, - decimals = amount.decimals, - ) - } - } - } } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt index 52b707e8d1..181ce66767 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt @@ -16,7 +16,6 @@ interface TransactionRepository { destination: String, userWalletId: UserWalletId, network: Network, - isSwap: Boolean, txExtras: TransactionExtras?, hash: String?, ): TransactionData? diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt index 056dfc0f0c..ab5172eaf8 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt @@ -24,7 +24,6 @@ class CreateTransactionUseCase( userWalletId: UserWalletId, network: Network, txExtras: TransactionExtras? = null, - isSwap: Boolean = false, hash: String? = null, ) = Either.catch { requireNotNull( @@ -35,7 +34,6 @@ class CreateTransactionUseCase( destination = destination, userWalletId = userWalletId, network = network, - isSwap = isSwap, txExtras = txExtras, hash = hash, ), diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExpressDataConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExpressDataConverter.kt index 1fb2d9a8d2..34c663840b 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExpressDataConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExpressDataConverter.kt @@ -8,6 +8,7 @@ import com.tangem.feature.swap.domain.models.createFromAmountWithOffset import com.tangem.feature.swap.domain.models.domain.ExpressTransactionModel import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.utils.converter.Converter +import java.math.BigDecimal internal class ExpressDataConverter : Converter { @@ -24,19 +25,30 @@ internal class ExpressDataConverter : Converter = emptyList(), + val swapProvider: SwapProvider, ) : SwapState data class EmptyAmountState(val zeroAmountEquivalent: String) : SwapState @@ -102,6 +108,9 @@ data class TxFee( val gasLimit: Int, val feeFiatFormatted: String, val feeCryptoFormatted: String, + val feeIncludeOtherNativeFee: BigDecimal, + val feeFiatFormattedWithNative: String, + val feeCryptoFormattedWithNative: String, val decimals: Int, val cryptoSymbol: String, val feeType: FeeType, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index df53e5e0e7..e5111d0545 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -4,8 +4,11 @@ import arrow.core.Either import arrow.core.getOrElse import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.minimalAmount import com.tangem.common.extensions.hexToBytes import com.tangem.core.ui.utils.BigDecimalFormatter @@ -15,10 +18,8 @@ import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.model.FeePaidCurrency -import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.QuotesRepository @@ -342,7 +343,7 @@ internal class SwapInteractorImpl @Inject constructor( ) } else { provider to getQuotesState( - exchangeProviderType = provider.type, + provider = provider, quoteDataModel = quotes, amount = amount, fromToken = fromToken, @@ -366,7 +367,6 @@ internal class SwapInteractorImpl @Inject constructor( isBalanceWithoutFeeEnough: Boolean, ): Pair { return provider to loadCexQuoteData( - exchangeProviderType = ExchangeProviderType.CEX, networkId = networkId, amount = amount, fromTokenStatus = fromToken, @@ -595,8 +595,8 @@ internal class SwapInteractorImpl @Inject constructor( swapData = requireNotNull(swapData), currencyToSendStatus = currencyToSend, currencyToGetStatus = currencyToGet, - amountToSwap = amountToSwap, fee = fee, + amountToSwap = amountToSwap, userWalletId = requireNotNull(getSelectedWallet()).walletId, ) } @@ -622,8 +622,8 @@ internal class SwapInteractorImpl @Inject constructor( ) val fee = when (val txFee = state.txFee) { TxFeeState.Empty -> BigDecimal.ZERO - is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue - is TxFeeState.SingleFeeState -> txFee.fee.feeValue + is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeIncludeOtherNativeFee + is TxFeeState.SingleFeeState -> txFee.fee.feeIncludeOtherNativeFee } val feeState = getFeeState( fee = fee, @@ -656,8 +656,9 @@ internal class SwapInteractorImpl @Inject constructor( val derivationPath = currencyToSendStatus.currency.network.derivationPath.value val dexTransaction = swapData.transaction as ExpressTransactionModel.DEX val dataToSign = dexTransaction.txData + val amountToSend = createNativeAmountForDex(swapData.transaction.txValue, currencyToSendStatus.currency.network) val txData = createTransactionUseCase( - amount = amount.value.convertToAmount(currencyToSendStatus.currency), + amount = amountToSend, fee = getFeeForTransaction( fee = fee, blockchain = Blockchain.fromId(currencyToSendStatus.currency.network.id.value), @@ -668,7 +669,6 @@ internal class SwapInteractorImpl @Inject constructor( network = currencyToSendStatus.currency.network, txExtras = createDexTxExtras(fee.gasLimit, dataToSign), hash = dataToSign, - isSwap = true, ).getOrElse { Timber.e(it) return SwapTransactionState.UnknownError @@ -984,7 +984,6 @@ internal class SwapInteractorImpl @Inject constructor( */ @Suppress("LongParameterList") private suspend fun loadCexQuoteData( - exchangeProviderType: ExchangeProviderType, networkId: String, amount: SwapAmount, fromTokenStatus: CryptoCurrencyStatus, @@ -1035,7 +1034,7 @@ internal class SwapInteractorImpl @Inject constructor( ) getQuotesState( - exchangeProviderType = exchangeProviderType, + provider = provider, quoteDataModel = quotes, amount = amount, fromToken = fromTokenStatus, @@ -1052,7 +1051,7 @@ internal class SwapInteractorImpl @Inject constructor( @Suppress("LongMethod") private suspend fun getQuotesState( - exchangeProviderType: ExchangeProviderType, + provider: SwapProvider, quoteDataModel: Either, amount: SwapAmount, fromToken: CryptoCurrencyStatus, @@ -1074,6 +1073,7 @@ internal class SwapInteractorImpl @Inject constructor( toTokenAmount = quoteModel.toTokenAmount, swapData = null, txFeeState = txFee, + provider = provider, ).copy( warnings = manageWarnings( fromTokenStatus = fromToken, @@ -1083,7 +1083,7 @@ internal class SwapInteractorImpl @Inject constructor( ), ) - when (exchangeProviderType) { + when (provider.type) { ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> { val state = updatePermissionState( networkId = networkId, @@ -1147,8 +1147,8 @@ internal class SwapInteractorImpl @Inject constructor( ): IncludeFeeInAmount { val feeValue = when (txFee) { TxFeeState.Empty -> BigDecimal.ZERO - is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue - is TxFeeState.SingleFeeState -> txFee.fee.feeValue + is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeIncludeOtherNativeFee + is TxFeeState.SingleFeeState -> txFee.fee.feeIncludeOtherNativeFee } val feePaidCurrency = getFeePaidCurrency( userWalletId = requireNotNull(getSelectedWallet()).walletId, @@ -1254,29 +1254,35 @@ internal class SwapInteractorImpl @Inject constructor( providerId = provider.providerId, rateType = RateType.FLOAT, toAddress = toToken.value.networkAddress?.defaultAddress?.value.orEmpty(), + refundAddress = fromToken.value.networkAddress?.defaultAddress?.value, ).fold( ifRight = { swapData -> - val feeData = transactionManager.getFee( - networkId = networkId, - amountToSend = amount.value, - currencyToSend = swapCurrencyConverter.convert(fromToken.currency), - destinationAddress = swapData.transaction.txTo, - increaseBy = INCREASE_GAS_LIMIT_BY, - data = (swapData.transaction as ExpressTransactionModel.DEX).txData, - derivationPath = fromToken.currency.network.derivationPath.value, - ) - val txFeeState = when (feeData) { - is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken.currency) - is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken.currency) + val transaction = swapData.transaction as ExpressTransactionModel.DEX + val nativeCoinDecimals = Blockchain.fromNetworkId(networkId)?.decimals() + ?: error("Blockchain not found") + val otherNativeFee = transaction.otherNativeFeeWei + ?.movePointLeft(nativeCoinDecimals) + ?: BigDecimal.ZERO + val txFeeState = when (val feeData = getFeeDataForDexSwap(networkId, transaction, fromToken.currency)) { + is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken.currency, otherNativeFee) + is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken.currency, otherNativeFee) } val feeByPriority = selectFeeByType(feeType = selectedFee, txFeeState = txFeeState) - val isBalanceIncludeFeeEnough = isBalanceEnough(fromToken, amount, feeByPriority) + val feeToCheckFunds = feeByPriority + (otherNativeFee ?: BigDecimal.ZERO) + val isBalanceIncludeFeeEnough = isBalanceEnough(fromToken, amount, feeToCheckFunds) val feeState = getFeeState( fee = feeByPriority, spendAmount = amount, networkId = networkId, fromTokenStatus = fromToken, ) + val preparedSwapConfigState = PreparedSwapConfigState( + isAllowedToSpend = true, + isBalanceEnough = isBalanceIncludeFeeEnough, + feeState = feeState, + hasOutgoingTransaction = hasOutgoingTransaction(fromToken), + includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex + ) val swapState = updateBalances( networkId = networkId, fromTokenStatus = fromToken, @@ -1285,6 +1291,7 @@ internal class SwapInteractorImpl @Inject constructor( toTokenAmount = swapData.toTokenAmount, swapData = swapData, txFeeState = txFeeState, + provider = provider, ) swapState.copy( permissionState = PermissionDataState.Empty, @@ -1292,17 +1299,9 @@ internal class SwapInteractorImpl @Inject constructor( fromTokenStatus = fromToken, amount = amount, feeState = txFeeState, - minAdaValue = (feeData as? ProxyFees.SingleFee)?.let { - (it.singleFee as? ProxyFee.CardanoToken)?.minAdaValue - }, - ), - preparedSwapConfigState = PreparedSwapConfigState( - isAllowedToSpend = true, - isBalanceEnough = isBalanceIncludeFeeEnough, - feeState = feeState, - hasOutgoingTransaction = hasOutgoingTransaction(fromToken), - includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex + minAdaValue = null, // no ADA in DEX ), + preparedSwapConfigState = preparedSwapConfigState, ) }, ifLeft = { error -> @@ -1322,8 +1321,42 @@ internal class SwapInteractorImpl @Inject constructor( ) } + private suspend fun getFeeDataForDexSwap( + networkId: String, + transaction: ExpressTransactionModel.DEX, + fromToken: CryptoCurrency, + ): ProxyFees { + return try { + val nativeBalance = userWalletManager.getNativeTokenBalance( + networkId = networkId, + derivationPath = fromToken.network.derivationPath.value, + ) ?: ProxyAmount.empty() + val amountToSend = createNativeAmountForDex(transaction.txValue, fromToken.network) + // transaction.txValue is always native coin + if (nativeBalance.value < amountToSend.value) { + error("It's impossible to calculate fee for nativeBalance.value < amountToSend.value") + } + transactionManager.getFee( + networkId = networkId, + amountToSend = amountToSend, + currencyToSend = swapCurrencyConverter.convert(fromToken), + destinationAddress = transaction.txTo, + increaseBy = INCREASE_GAS_LIMIT_BY, + data = transaction.txData, + derivationPath = fromToken.network.derivationPath.value, + ) + } catch (e: IllegalStateException) { + transactionManager.getFeeForGas( + networkId = networkId, + gas = transaction.gas, + derivationPath = fromToken.network.derivationPath.value, + ) + } + } + @Suppress("LongParameterList") private suspend fun updateBalances( + provider: SwapProvider, networkId: String, fromTokenStatus: CryptoCurrencyStatus, toTokenStatus: CryptoCurrencyStatus, @@ -1335,7 +1368,6 @@ internal class SwapInteractorImpl @Inject constructor( val fromToken = fromTokenStatus.currency val toToken = toTokenStatus.currency val nativeToken = repository.getNativeTokenForNetwork(networkId) - val rates = getQuotes(fromToken.id, toToken.id, nativeToken.id) return SwapState.QuotesLoadedState( fromTokenInfo = TokenSwapInfo( @@ -1358,6 +1390,7 @@ internal class SwapInteractorImpl @Inject constructor( ), swapDataModel = swapData, txFee = txFeeState, + swapProvider = provider, ) } @@ -1367,7 +1400,7 @@ internal class SwapInteractorImpl @Inject constructor( ): TxFeeState { return txFeeResult?.fold( ifLeft = { TxFeeState.Empty }, - ifRight = { txFee -> txFee.toTxFeeState(fromToken.currency) }, + ifRight = { txFee -> txFee.toTxFeeState(fromToken.currency, null) }, ) ?: TxFeeState.Empty } @@ -1427,7 +1460,7 @@ internal class SwapInteractorImpl @Inject constructor( try { transactionManager.getFee( networkId = networkId, - amountToSend = BigDecimal.ZERO, + amountToSend = createNativeAmountForDex("0", fromToken.network), currencyToSend = swapCurrencyConverter.convert(repository.getNativeTokenForNetwork(networkId)), destinationAddress = fromToken.getContractAddress(), increaseBy = INCREASE_GAS_LIMIT_BY, @@ -1475,11 +1508,16 @@ internal class SwapInteractorImpl @Inject constructor( ) } - private suspend fun ProxyFees.MultipleFees.proxyFeesToFeeState(fromToken: CryptoCurrency): TxFeeState { + private suspend fun ProxyFees.MultipleFees.proxyFeesToFeeState( + fromToken: CryptoCurrency, + otherNativeFee: BigDecimal? = null, + ): TxFeeState { + val otherNativeFeeValue = otherNativeFee ?: BigDecimal.ZERO val normalFeeValue = this.minFee.fee.value // in swap for normal use min fee val normalFeeGas = this.minFee.gasLimit.toInt() val priorityFeeValue = this.normalFee.fee.value // in swap for priority use normal fee val priorityFeeGas = this.normalFee.gasLimit.toInt() + // region fees to use val feesFiat = getFormattedFiatFees(fromToken, normalFeeValue, priorityFeeValue) val normalFiatFee = requireNotNull(feesFiat.getOrNull(0)) { "feesFiat item 0 couldn't be null" } val priorityFiatFee = requireNotNull(feesFiat.getOrNull(1)) { "feesFiat item 1 couldn't be null" } @@ -1491,12 +1529,35 @@ internal class SwapInteractorImpl @Inject constructor( amount = priorityFeeValue, decimals = normalFee.fee.decimals, ) + // + // region fees include otherNativeFee + val feesFiatWithNative = getFormattedFiatFees( + fromToken = fromToken, + normalFeeValue + otherNativeFeeValue, + priorityFeeValue + otherNativeFeeValue, + ) + val normalFiatFeeWithNative = + requireNotNull(feesFiatWithNative.getOrNull(0)) { "feesFiat item 0 couldn't be null" } + val priorityFiatFeeWithNative = + requireNotNull(feesFiatWithNative.getOrNull(1)) { "feesFiat item 1 couldn't be null" } + val normalCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI( + amount = normalFeeValue + otherNativeFeeValue, + decimals = minFee.fee.decimals, + ) + val priorityCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI( + amount = priorityFeeValue + otherNativeFeeValue, + decimals = normalFee.fee.decimals, + ) + // return TxFeeState.MultipleFeeState( normalFee = TxFee( feeValue = normalFeeValue, gasLimit = normalFeeGas, feeFiatFormatted = normalFiatFee, feeCryptoFormatted = normalCryptoFee, + feeIncludeOtherNativeFee = normalFeeValue + otherNativeFeeValue, + feeFiatFormattedWithNative = normalFiatFeeWithNative, + feeCryptoFormattedWithNative = normalCryptoFeeWithNative, decimals = minFee.fee.decimals, cryptoSymbol = minFee.fee.currencySymbol, feeType = FeeType.NORMAL, @@ -1506,6 +1567,9 @@ internal class SwapInteractorImpl @Inject constructor( gasLimit = priorityFeeGas, feeFiatFormatted = priorityFiatFee, feeCryptoFormatted = priorityCryptoFee, + feeIncludeOtherNativeFee = priorityFeeValue + otherNativeFeeValue, + feeFiatFormattedWithNative = priorityFiatFeeWithNative, + feeCryptoFormattedWithNative = priorityCryptoFeeWithNative, decimals = normalFee.fee.decimals, cryptoSymbol = normalFee.fee.currencySymbol, feeType = FeeType.PRIORITY, @@ -1513,7 +1577,11 @@ internal class SwapInteractorImpl @Inject constructor( ) } - private suspend fun ProxyFees.SingleFee.proxyFeesToFeeState(fromToken: CryptoCurrency): TxFeeState { + private suspend fun ProxyFees.SingleFee.proxyFeesToFeeState( + fromToken: CryptoCurrency, + otherNativeFee: BigDecimal? = null, + ): TxFeeState { + val otherNativeFeeValue = otherNativeFee ?: BigDecimal.ZERO val normalFeeValue = this.singleFee.fee.value val normalFeeGas = this.singleFee.gasLimit.toInt() val feesFiat = getFormattedFiatFees(fromToken, normalFeeValue) @@ -1522,12 +1590,28 @@ internal class SwapInteractorImpl @Inject constructor( amount = normalFeeValue, decimals = singleFee.fee.decimals, ) + // region fees include otherNativeFee + val feesFiatWithNative = getFormattedFiatFees( + fromToken = fromToken, + normalFeeValue + otherNativeFeeValue, + normalFeeValue + otherNativeFeeValue, + ) + val normalFiatFeeWithNative = + requireNotNull(feesFiatWithNative.getOrNull(0)) { "feesFiat item 0 couldn't be null" } + val normalCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI( + amount = normalFeeValue + otherNativeFeeValue, + decimals = singleFee.fee.decimals, + ) + // return TxFeeState.SingleFeeState( fee = TxFee( feeValue = normalFeeValue, gasLimit = normalFeeGas, feeFiatFormatted = normalFiatFee, feeCryptoFormatted = normalCryptoFee, + feeIncludeOtherNativeFee = normalFeeValue + otherNativeFeeValue, + feeFiatFormattedWithNative = normalFiatFeeWithNative, + feeCryptoFormattedWithNative = normalCryptoFeeWithNative, decimals = singleFee.fee.decimals, cryptoSymbol = singleFee.fee.currencySymbol, feeType = FeeType.NORMAL, @@ -1535,7 +1619,12 @@ internal class SwapInteractorImpl @Inject constructor( ) } - private suspend fun TransactionFee.toTxFeeState(fromToken: CryptoCurrency): TxFeeState { + @Suppress("LongMethod") + private suspend fun TransactionFee.toTxFeeState( + fromToken: CryptoCurrency, + otherNativeFee: BigDecimal?, + ): TxFeeState { + val otherNativeFeeValue = otherNativeFee ?: BigDecimal.ZERO return when (this) { is TransactionFee.Choosable -> { val normalFee = this.normal.increaseGasLimitBy(INCREASE_GAS_LIMIT_FOR_SEND) @@ -1553,12 +1642,31 @@ internal class SwapInteractorImpl @Inject constructor( amount = feePriority, decimals = priorityFee.amount.decimals, ) + + // region otherNativeFee + val normalFeeWithOtherNative = feeNormal + otherNativeFeeValue + val priorityFeeWithOtherNative = feeNormal + otherNativeFeeValue + val normalFiatValueWithNative = getFormattedFiatFees(fromToken, normalFeeWithOtherNative)[0] + val priorityFiatValueWithNative = getFormattedFiatFees(fromToken, priorityFeeWithOtherNative)[0] + + val normalCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI( + amount = normalFeeWithOtherNative, + decimals = normalFee.amount.decimals, + ) + val priorityCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI( + amount = priorityFeeWithOtherNative, + decimals = priorityFee.amount.decimals, + ) + // TxFeeState.MultipleFeeState( normalFee = TxFee( feeValue = feeNormal, gasLimit = normalFee.getGasLimit(), feeFiatFormatted = normalFiatValue, feeCryptoFormatted = normalCryptoFee, + feeIncludeOtherNativeFee = normalFeeWithOtherNative, + feeFiatFormattedWithNative = normalFiatValueWithNative, + feeCryptoFormattedWithNative = normalCryptoFeeWithNative, decimals = normalFee.amount.decimals, cryptoSymbol = normalFee.amount.currencySymbol, feeType = FeeType.NORMAL, @@ -1568,6 +1676,9 @@ internal class SwapInteractorImpl @Inject constructor( gasLimit = priorityFee.getGasLimit(), feeFiatFormatted = priorityFiatValue, feeCryptoFormatted = priorityCryptoFee, + feeIncludeOtherNativeFee = priorityFeeWithOtherNative, + feeFiatFormattedWithNative = priorityFiatValueWithNative, + feeCryptoFormattedWithNative = priorityCryptoFeeWithNative, decimals = priorityFee.amount.decimals, cryptoSymbol = priorityFee.amount.currencySymbol, feeType = FeeType.PRIORITY, @@ -1581,12 +1692,24 @@ internal class SwapInteractorImpl @Inject constructor( amount = feeNormal, decimals = this.normal.amount.decimals, ) + // region otherNativeFee + val normalFeeWithOtherNative = feeNormal + otherNativeFeeValue + val normalFiatValueWithNative = getFormattedFiatFees(fromToken, normalFeeWithOtherNative)[0] + + val normalCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI( + amount = normalFeeWithOtherNative, + decimals = this.normal.amount.decimals, + ) + // TxFeeState.SingleFeeState( fee = TxFee( feeValue = this.normal.amount.value ?: BigDecimal.ZERO, gasLimit = this.normal.getGasLimit(), feeFiatFormatted = normalFiatValue, feeCryptoFormatted = normalCryptoFee, + feeIncludeOtherNativeFee = normalFeeWithOtherNative, + feeFiatFormattedWithNative = normalFiatValueWithNative, + feeCryptoFormattedWithNative = normalCryptoFeeWithNative, decimals = normal.amount.decimals, cryptoSymbol = normal.amount.currencySymbol, feeType = FeeType.NORMAL, @@ -1596,6 +1719,18 @@ internal class SwapInteractorImpl @Inject constructor( } } + private fun createNativeAmountForDex(txValueAmount: String, network: Network): Amount { + val nativeDecimals = Blockchain.fromNetworkId(network.backendId)?.decimals() + ?: error("Blockchain not found") + val decimalValue = txValueAmount.toBigDecimalOrNull()?.movePointLeft(nativeDecimals) + ?: error("txValue parse error") + return Amount( + currencySymbol = network.currencySymbol, + value = decimalValue, + decimals = nativeDecimals, + ) + } + /** * Workaround to increase gas limit cause we calculate fee for random address */ @@ -1656,7 +1791,7 @@ internal class SwapInteractorImpl @Inject constructor( if (fromToken.currency is CryptoCurrency.Token) { tokenBalance >= amount.value } else { - tokenBalance > amount.value.plus(fee ?: BigDecimal.ZERO) + tokenBalance >= amount.value.plus(fee ?: BigDecimal.ZERO) } } } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt index 4192106336..7e31eb9e60 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt @@ -12,6 +12,7 @@ sealed class SwapPermissionState { object Empty : SwapPermissionState() data class ReadyForRequest( + val providerName: String, val currency: String, val amount: String, val walletAddress: String, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index cd22de2a3b..6e429c9c13 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -1,7 +1,6 @@ package com.tangem.feature.swap.models import androidx.annotation.DrawableRes -import androidx.annotation.StringRes import androidx.compose.ui.text.input.TextFieldValue import com.tangem.core.ui.R import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig @@ -74,18 +73,21 @@ data class SwapButton( sealed interface TransactionCardType { - val headerResId: Int + val header: TextReference + val isError: Boolean data class Inputtable( val onAmountChanged: ((String) -> Unit), val onFocusChanged: ((Boolean) -> Unit), - @StringRes override val headerResId: Int = R.string.swapping_from_title, + override val isError: Boolean, + override val header: TextReference = TextReference.Res(R.string.swapping_from_title), ) : TransactionCardType data class ReadOnly( val showWarning: Boolean = false, val onWarningClick: (() -> Unit)? = null, - @StringRes override val headerResId: Int = R.string.swapping_to_title, + override val isError: Boolean = false, + override val header: TextReference = TextReference.Res(R.string.swapping_to_title), ) : TransactionCardType } @@ -102,7 +104,7 @@ data class LegalState( sealed interface SwapWarning { data class PermissionNeeded(val notificationConfig: NotificationConfig) : SwapWarning - object InsufficientFunds : SwapWarning + data object InsufficientFunds : SwapWarning data class NoAvailableTokensToSwap(val notificationConfig: NotificationConfig) : SwapWarning data class GenericWarning( val title: TextReference? = null, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 1925e60ee7..05a1fa9451 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -51,7 +51,11 @@ internal class StateBuilder( return SwapStateHolder( blockchainId = networkInfo.blockchainId, sendCardData = SwapCardState.SwapCardData( - type = TransactionCardType.Inputtable(actions.onAmountChanged, actions.onAmountSelected), + type = TransactionCardType.Inputtable( + onAmountChanged = actions.onAmountChanged, + onFocusChanged = actions.onAmountSelected, + isError = false, + ), amountEquivalent = null, amountTextFieldValue = null, token = null, @@ -152,9 +156,13 @@ internal class StateBuilder( val canSelectReceiveToken = mainTokenId != toToken.id.value if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder + val sendInput = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable).copy( + isError = false, + header = TextReference.Res(R.string.swapping_from_title), + ) return uiStateHolder.copy( sendCardData = SwapCardState.SwapCardData( - type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable), + type = sendInput, amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue, amountEquivalent = null, token = uiStateHolder.sendCardData.token, @@ -221,9 +229,19 @@ internal class StateBuilder( val feeState = createFeeState(quoteModel.txFee, selectedFeeType) val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus val toCurrencyStatus = quoteModel.toTokenInfo.cryptoCurrencyStatus + val isInsufficientFunds = isInsufficientFundsCondition(quoteModel) + val insufficientFundsHeader = if (isInsufficientFunds) { + TextReference.Res(R.string.swapping_insufficient_funds) + } else { + TextReference.Res(R.string.swapping_from_title) + } + val sendInput = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable).copy( + isError = isInsufficientFunds, + header = insufficientFundsHeader, + ) return uiStateHolder.copy( sendCardData = SwapCardState.SwapCardData( - type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable), + type = sendInput, amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue, amountEquivalent = getFormattedFiatAmount(quoteModel.fromTokenInfo.amountFiat), token = fromCurrencyStatus, @@ -257,6 +275,7 @@ internal class StateBuilder( permissionState = convertPermissionState( lastPermissionState = uiStateHolder.permissionState, permissionDataState = quoteModel.permissionState, + providerName = quoteModel.swapProvider.name, onGivePermissionClick = actions.onGivePermissionClick, onChangeApproveType = actions.onChangeApproveType, ), @@ -320,7 +339,7 @@ internal class StateBuilder( val warnings = mutableListOf() maybeAddDomainWarnings(quoteModel, warnings) maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings) - maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken) + maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken, quoteModel.swapProvider.name) maybeAddNetworkFeeCoverageWarning(quoteModel, warnings, selectedFeeType) maybeAddUnableCoverFeeWarning(quoteModel, fromToken, warnings) maybeAddInsufficientFundsWarning(quoteModel, warnings) @@ -468,6 +487,7 @@ internal class StateBuilder( quoteModel: SwapState.QuotesLoadedState, warnings: MutableList, fromToken: CryptoCurrency, + providerName: String, ) { if (!quoteModel.preparedSwapConfigState.isAllowedToSpend && quoteModel.preparedSwapConfigState.feeState is SwapFeeState.Enough && @@ -475,7 +495,7 @@ internal class StateBuilder( ) { warnings.add( SwapWarning.PermissionNeeded( - createPermissionNotificationConfig(fromToken.symbol), + createPermissionNotificationConfig(fromToken.symbol, providerName), ), ) } @@ -493,8 +513,8 @@ internal class StateBuilder( warnings.add( SwapWarning.GeneralWarning( createNetworkFeeCoverageNotificationConfig( - fee.feeCryptoFormatted, - fee.feeFiatFormatted, + fee.feeCryptoFormattedWithNative, + fee.feeFiatFormattedWithNative, ), ), ) @@ -547,13 +567,16 @@ internal class StateBuilder( warnings: MutableList, ) { // check isBalanceEnough, but for dex includeFeeInAmount always Excluded - if (!quoteModel.preparedSwapConfigState.isBalanceEnough && - quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included - ) { + if (isInsufficientFundsCondition(quoteModel)) { warnings.add(SwapWarning.InsufficientFunds) } } + private fun isInsufficientFundsCondition(quoteModel: SwapState.QuotesLoadedState): Boolean { + return !quoteModel.preparedSwapConfigState.isBalanceEnough && + quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included + } + private fun getSwapButtonEnabled(quoteModel: SwapState.QuotesLoadedState): Boolean { val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value if (status is CryptoCurrencyStatus.NoAccount) { @@ -964,9 +987,9 @@ internal class StateBuilder( return FeeItemState.Content( feeType = feeType, title = resourceReference(R.string.common_network_fee_title), - amountCrypto = fee.feeCryptoFormatted, + amountCrypto = fee.feeCryptoFormattedWithNative, // display fee with native as workaround for okx symbolCrypto = fee.cryptoSymbol, - amountFiatFormatted = fee.feeFiatFormatted, + amountFiatFormatted = fee.feeFiatFormattedWithNative, // display fee with native as workaround for okx isClickable = isClickable, onClick = actions.onClickFee, ) @@ -1017,7 +1040,7 @@ internal class StateBuilder( showStatusButton = shouldShowStatus, providerIcon = providerState.iconUrl, rate = providerState.subtitle, - fee = stringReference("${fee.feeCryptoFormatted} (${fee.feeFiatFormatted})"), + fee = stringReference("${fee.feeCryptoFormattedWithNative} (${fee.feeFiatFormattedWithNative})"), fromTokenAmount = stringReference(swapTransactionState.fromAmount.orEmpty()), toTokenAmount = stringReference(swapTransactionState.toAmount.orEmpty()), fromTokenFiatAmount = stringReference(fromFiatAmount), @@ -1146,6 +1169,7 @@ internal class StateBuilder( private fun convertPermissionState( lastPermissionState: SwapPermissionState, permissionDataState: PermissionDataState, + providerName: String, onGivePermissionClick: () -> Unit, onChangeApproveType: (ApproveType) -> Unit, ): SwapPermissionState { @@ -1165,6 +1189,7 @@ internal class StateBuilder( is TxFeeState.SingleFeeState -> fee.fee } SwapPermissionState.ReadyForRequest( + providerName = providerName, currency = permissionDataState.currency, amount = permissionDataState.amount, approveType = approveType, @@ -1358,18 +1383,18 @@ internal class StateBuilder( FeeItemState.Content( feeType = this.normalFee.feeType, title = resourceReference(R.string.common_network_fee_title), - amountCrypto = this.normalFee.feeCryptoFormatted, + amountCrypto = this.normalFee.feeCryptoFormattedWithNative, symbolCrypto = this.normalFee.cryptoSymbol, - amountFiatFormatted = this.normalFee.feeFiatFormatted, + amountFiatFormatted = this.normalFee.feeFiatFormattedWithNative, isClickable = true, onClick = {}, ), FeeItemState.Content( feeType = this.priorityFee.feeType, title = resourceReference(R.string.common_network_fee_title), - amountCrypto = this.priorityFee.feeCryptoFormatted, + amountCrypto = this.priorityFee.feeCryptoFormattedWithNative, symbolCrypto = this.priorityFee.cryptoSymbol, - amountFiatFormatted = this.priorityFee.feeFiatFormatted, + amountFiatFormatted = this.priorityFee.feeFiatFormattedWithNative, isClickable = true, onClick = {}, ), @@ -1402,12 +1427,12 @@ internal class StateBuilder( } // region warnings - private fun createPermissionNotificationConfig(fromTokenSymbol: String): NotificationConfig { + private fun createPermissionNotificationConfig(fromTokenSymbol: String, providerName: String): NotificationConfig { return NotificationConfig( title = resourceReference(R.string.express_provider_permission_needed), subtitle = resourceReference( id = R.string.swapping_permission_subheader, - formatArgs = wrappedList(fromTokenSymbol), + formatArgs = wrappedList(providerName, fromTokenSymbol), ), iconResId = R.drawable.ic_locked_24, ) @@ -1604,7 +1629,7 @@ internal class StateBuilder( id = this.providerId, name = this.name, iconUrl = this.imageLarge, - type = this.type.toString(), + type = this.type.providerName, selectionType = selectionType, alertText = alertText, onProviderClick = onProviderClick, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt index 8ac175588a..1915863d98 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt @@ -68,6 +68,7 @@ private fun SwapPermissionBottomSheetContent(content: GivePermissionBottomSheetC Text( text = stringResource( id = R.string.swapping_permission_subheader, + data.providerName, data.currency, ), color = TangemTheme.colors.text.secondary, @@ -303,6 +304,7 @@ private fun Preview_AgreementBottomSheet() { private val previewData = GivePermissionBottomSheetConfig( data = SwapPermissionState.ReadyForRequest( + providerName = "1icnh", currency = "DAI", amount = "∞", walletAddress = "", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 7be20ace87..fc0b98feb3 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -434,7 +434,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U // region preview private val sendCard = SwapCardState.SwapCardData( - type = TransactionCardType.Inputtable({}, {}), + type = TransactionCardType.Inputtable({}, {}, false), amountTextFieldValue = TextFieldValue(), amountEquivalent = "1 000 000", tokenIconUrl = "", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index 7ae3edc6a2..9baf09e90a 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -37,6 +37,7 @@ import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest import com.tangem.core.ui.R import com.tangem.core.ui.components.* +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.ImageBackgroundContrastChecker @@ -185,10 +186,14 @@ private fun Header(type: TransactionCardType, balance: String, modifier: Modifie horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - val title = type.headerResId + val titleColor = if (type.isError) { + TangemTheme.colors.text.warning + } else { + TangemTheme.colors.text.tertiary + } Text( - text = stringResource(id = title), - color = TangemTheme.colors.text.tertiary, + text = type.header.resolveReference(), + color = titleColor, maxLines = 1, style = MaterialTheme.typography.subtitle2, modifier = Modifier @@ -546,7 +551,7 @@ private fun Preview_TransactionCardWithoutPriceImpact_InDarkTheme() { @Composable private fun TransactionCardPreview() { TransactionCard( - type = TransactionCardType.Inputtable({}, {}), + type = TransactionCardType.Inputtable({}, {}, false), amountEquivalent = "1 000 000", tokenIconUrl = "", tokenCurrency = "DAI", diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt index 6b69d96fb8..a3a9846b43 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt @@ -1,9 +1,11 @@ package com.tangem.lib.crypto +import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.TransactionExtras import com.tangem.lib.crypto.models.* import com.tangem.lib.crypto.models.transactions.SendTxResult import java.math.BigDecimal +import java.math.BigInteger interface TransactionManager { @@ -46,7 +48,7 @@ interface TransactionManager { @Throws(IllegalStateException::class) suspend fun getFee( networkId: String, - amountToSend: BigDecimal, + amountToSend: Amount, currencyToSend: Currency, destinationAddress: String, increaseBy: Int?, @@ -54,6 +56,9 @@ interface TransactionManager { derivationPath: String?, ): ProxyFees + @Throws(IllegalStateException::class) + suspend fun getFeeForGas(networkId: String, gas: BigInteger, derivationPath: String?): ProxyFees + @Throws(IllegalStateException::class) suspend fun updateWalletManager(networkId: String, derivationPath: String?) From 4e21f84b901d902b234a60159d9146fb3a333794 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 9 Jul 2024 12:57:28 +0100 Subject: [PATCH 02/53] Updated on 2026-08-14 --- .../java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt | 4 ++-- .../src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index e5111d0545..25d18c1f45 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -1348,7 +1348,7 @@ internal class SwapInteractorImpl @Inject constructor( } catch (e: IllegalStateException) { transactionManager.getFeeForGas( networkId = networkId, - gas = transaction.gas, + gas = transaction.gas.multiply(INCREASE_GAS_LIMIT_BY.toBigInteger()).divide(100.toBigInteger()), derivationPath = fromToken.network.derivationPath.value, ) } @@ -1971,7 +1971,7 @@ internal class SwapInteractorImpl @Inject constructor( gasLimit = 1.toBigInteger(), fee = demoFee.copy(value = normalDemoFee), - ), + ), priorityFee = ProxyFee.Common( gasLimit = 1.toBigInteger(), fee = demoFee.copy(value = priorityDemoFee), diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 05a1fa9451..e7d4edd818 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -1029,8 +1029,7 @@ internal class StateBuilder( val fromFiatAmount = getFormattedFiatAmount(fromCryptoCurrency.value.fiatRate?.multiply(fromAmount)) val toFiatAmount = getFormattedFiatAmount(toCryptoCurrency.value.fiatRate?.multiply(toAmount)) - val shouldShowStatus = providerState.type == ExchangeProviderType.CEX.providerName || - providerState.type == ExchangeProviderType.DEX_BRIDGE.providerName + val shouldShowStatus = providerState.type == ExchangeProviderType.CEX.providerName return uiState.copy( successState = SwapSuccessStateHolder( timestamp = swapTransactionState.timestamp, From b168f321b8f35f12f719d169d0d4b082a3a97806 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 9 Jul 2024 14:39:17 +0100 Subject: [PATCH 03/53] Updated on 2026-08-14 --- .../domain/walletconnect/WalletConnectSdkHelper.kt | 4 ++-- .../express/models/response/ExchangeStatusResponse.kt | 8 ++++---- .../local/swaptx/SwapTransactionStatusStore.kt | 3 +++ .../swap/domain/models/domain/ExchangeStatus.kt | 1 + .../tangem/feature/swap/domain/SwapInteractorImpl.kt | 9 ++++----- .../tokendetails/viewmodels/ExchangeStatusFactory.kt | 11 ++++++++--- .../kotlin/com/tangem/lib/visa/utils/BigIntegerExt.kt | 2 +- 7 files changed, 23 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index abb7d6ce7e..8fae8e9abb 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -177,9 +177,9 @@ class WalletConnectSdkHelper { is Result.Success -> gasLimitResult.data.toBigDecimal().multiply(BigDecimal("1.2")) is Result.Failure -> { (gasLimitResult.error as? Throwable)?.let { Timber.e(it, "getGasLimit failed") } - BigDecimal(DEFAULT_MAX_GASLIMIT) // Set high gasLimit if not provided + DEFAULT_MAX_GASLIMIT.toBigDecimal() // Set high gasLimit if not provided } - else -> BigDecimal(DEFAULT_MAX_GASLIMIT) // Set high gasLimit if not provided + else -> DEFAULT_MAX_GASLIMIT.toBigDecimal() // Set high gasLimit if not provided } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt index dc786b02de..5b43c9cea5 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt @@ -7,14 +7,14 @@ data class ExchangeStatusResponse( @Json(name = "providerId") val providerId: String, - @Json(name = "externalTxId") - val externalTxId: String, - @Json(name = "status") val status: ExchangeStatus, + @Json(name = "externalTxId") + val externalTxId: String?, + @Json(name = "externalTxUrl") - val externalTxUrl: String, + val externalTxUrl: String?, @Json(name = "error") val error: ExchangeStatusError?, diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/swaptx/SwapTransactionStatusStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/swaptx/SwapTransactionStatusStore.kt index 2bcb9a3dbb..cf99b7c4ac 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/swaptx/SwapTransactionStatusStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/swaptx/SwapTransactionStatusStore.kt @@ -10,9 +10,12 @@ interface SwapTransactionStatusStore { } enum class ExchangeAnalyticsStatus(val value: String) { + WaitingTxHash("Waiting tx hash"), InProgress("In Progress"), Done("Done"), Fail("Fail"), + FailTx("Fail tx"), + Unknown("Unknown"), KYC("KYC"), Refunded("Refunded"), Cancelled("Canceled"), diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt index c52f37ce6c..672d676fbb 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt @@ -20,5 +20,6 @@ enum class ExchangeStatus { Finished, Refunded, Cancelled, + TxFailed, Unknown, } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 25d18c1f45..c4cb77423f 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -317,8 +317,8 @@ internal class SwapInteractorImpl @Inject constructor( val fromTokenAddress = getTokenAddress(fromToken.currency) val isAllowedToSpend = quotes.fold( - ifRight = { - it.allowanceContract?.let { + ifRight = { quotes -> + quotes.allowanceContract?.let { isAllowedToSpend(networkId, fromToken.currency, amount, it) } ?: true }, @@ -870,7 +870,7 @@ internal class SwapInteractorImpl @Inject constructor( ) blockchain == Blockchain.Aptos -> { val gasUnitPrice = fee.feeValue.divide( - BigDecimal(fee.gasLimit), + fee.gasLimit.toBigDecimal(), Blockchain.Aptos.decimals(), RoundingMode.HALF_UP, ) @@ -1970,8 +1970,7 @@ internal class SwapInteractorImpl @Inject constructor( normalFee = ProxyFee.Common( gasLimit = 1.toBigInteger(), fee = demoFee.copy(value = normalDemoFee), - - ), + ), priorityFee = ProxyFee.Common( gasLimit = 1.toBigInteger(), fee = demoFee.copy(value = priorityDemoFee), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt index e77952c869..54356f4665 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt @@ -156,23 +156,28 @@ internal class ExchangeStatusFactory( ) } - private fun ExchangeStatus?.isTerminal() = - this == ExchangeStatus.Refunded || this == ExchangeStatus.Finished || this == ExchangeStatus.Cancelled + private fun ExchangeStatus?.isTerminal() = this == ExchangeStatus.Refunded || + this == ExchangeStatus.Finished || + this == ExchangeStatus.Cancelled || + this == ExchangeStatus.TxFailed || + this == ExchangeStatus.Unknown private fun toAnalyticStatus(status: ExchangeStatus?): ExchangeAnalyticsStatus? { return when (status) { ExchangeStatus.New, ExchangeStatus.Waiting, - ExchangeStatus.WaitingTxHash, ExchangeStatus.Sending, ExchangeStatus.Confirming, ExchangeStatus.Exchanging, -> ExchangeAnalyticsStatus.InProgress + ExchangeStatus.WaitingTxHash -> ExchangeAnalyticsStatus.WaitingTxHash ExchangeStatus.Verifying -> ExchangeAnalyticsStatus.KYC ExchangeStatus.Failed -> ExchangeAnalyticsStatus.Fail + ExchangeStatus.TxFailed -> ExchangeAnalyticsStatus.FailTx ExchangeStatus.Finished -> ExchangeAnalyticsStatus.Done ExchangeStatus.Refunded -> ExchangeAnalyticsStatus.Refunded ExchangeStatus.Cancelled -> ExchangeAnalyticsStatus.Cancelled + ExchangeStatus.Unknown -> ExchangeAnalyticsStatus.Unknown else -> null } } diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/BigIntegerExt.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/BigIntegerExt.kt index 4b129ed3c4..620ff5a6c2 100644 --- a/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/BigIntegerExt.kt +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/BigIntegerExt.kt @@ -5,7 +5,7 @@ import java.math.BigDecimal import java.math.BigInteger internal fun BigInteger.toBigDecimal(decimals: Int): BigDecimal { - return BigDecimal(this).movePointLeft(decimals) + return this.toBigDecimal().movePointLeft(decimals) } internal fun BigInteger.toInstant(): Instant { From 8c58005fed0d156cf28e1b32e68a136b4fc04c0e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 10 Jul 2024 16:42:11 +0200 Subject: [PATCH 04/53] Updated on 2026-08-14 --- app/build.gradle.kts | 4 ++-- .../ui/components/notifications/OkxPromoNotification.kt | 2 +- features/wallet/impl/build.gradle.kts | 2 +- .../wallet/ui/utils/ReviewManagerRequester.kt | 2 +- gradle/dependencies.toml | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d8615f2502..18fd29dc28 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -184,8 +184,8 @@ dependencies { /** Other libraries */ implementation(deps.kotlin.immutable.collections) implementation(deps.material) - implementation(deps.googlePlay.core) - implementation(deps.googlePlay.core.ktx) + implementation(deps.googlePlay.review) + implementation(deps.googlePlay.review.ktx) implementation(deps.googlePlay.services.wallet) coreLibraryDesugaring(deps.desugar) implementation(deps.timber) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/OkxPromoNotification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/OkxPromoNotification.kt index 9d6d403946..eacaa9567e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/OkxPromoNotification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/OkxPromoNotification.kt @@ -58,7 +58,7 @@ private fun Content(config: NotificationConfig) { .align(Alignment.CenterVertically), ) Column( - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2), modifier = Modifier .weight(1f) .padding(TangemTheme.dimens.spacing12), diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index dad4976a7d..ecad0b9c68 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { /** Other libraries */ implementation(deps.arrow.core) - implementation(deps.googlePlay.core) + implementation(deps.googlePlay.review) implementation(deps.jodatime) implementation(deps.kotlin.immutable.collections) implementation(deps.reKotlin) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ReviewManagerRequester.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ReviewManagerRequester.kt index a52e61dd42..1826afb74d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ReviewManagerRequester.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ReviewManagerRequester.kt @@ -3,10 +3,10 @@ package com.tangem.feature.wallet.presentation.wallet.ui.utils import android.app.Activity import android.content.Context import android.content.ContextWrapper +import com.google.android.gms.tasks.Task import com.google.android.play.core.review.ReviewInfo import com.google.android.play.core.review.ReviewManager import com.google.android.play.core.review.ReviewManagerFactory -import com.google.android.play.core.tasks.Task import timber.log.Timber internal object ReviewManagerRequester { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index e73b861d62..41817d438f 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -46,8 +46,8 @@ coroutine = "1.7.2" desugarJdkLibs = "1.1.5" firebase = "26.0.0" googleMaterialComponent = "1.6.1" -googlePlayCore = "1.10.3" -googlePlayCoreKtx = "1.8.1" +googlePlayReview = "2.0.1" +googlePlayReviewKtx = "2.0.1" googlePlayServicesWallet = "19.1.0" hilt = "2.46" hilt-navigation = "1.0.0" @@ -212,8 +212,8 @@ kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", kotlin-coroutines-rx2 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx2", version.ref = "coroutine" } kotlin-immutable-collections = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlin-immutable-collections" } desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdkLibs" } -googlePlay-core = { module = "com.google.android.play:core", version.ref = "googlePlayCore" } -googlePlay-core-ktx = { module = "com.google.android.play:core-ktx", version.ref = "googlePlayCoreKtx" } +googlePlay-review = { module = "com.google.android.play:review", version.ref = "googlePlayReview" } +googlePlay-review-ktx = { module = "com.google.android.play:review-ktx", version.ref = "googlePlayReviewKtx" } googlePlay-services-wallet = { module = "com.google.android.gms:play-services-wallet", version.ref = "googlePlayServicesWallet" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "hilt" } From 933dfb16e743da26edf199f5a7592b78b3c75a73 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Jul 2024 15:39:24 +0200 Subject: [PATCH 05/53] Updated on 2026-08-14 --- .../feature/swap/domain/models/ui/SwapState.kt | 1 - .../tangem/feature/swap/domain/SwapInteractorImpl.kt | 12 ++++++------ .../feature/swap/ui/SwapPermissionBottomSheet.kt | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index d7ba6d0ac0..2616e04be8 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -26,7 +26,6 @@ sealed interface SwapState { val permissionState: PermissionDataState = PermissionDataState.Empty, val swapDataModel: SwapDataModel? = null, val txFee: TxFeeState, - // val txFeeIncludeOtherNativeFee: TxFeeState, val warnings: List = emptyList(), val swapProvider: SwapProvider, ) : SwapState diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index c4cb77423f..c1be6a25bd 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -1529,7 +1529,7 @@ internal class SwapInteractorImpl @Inject constructor( amount = priorityFeeValue, decimals = normalFee.fee.decimals, ) - // + // endregion // region fees include otherNativeFee val feesFiatWithNative = getFormattedFiatFees( fromToken = fromToken, @@ -1548,7 +1548,7 @@ internal class SwapInteractorImpl @Inject constructor( amount = priorityFeeValue + otherNativeFeeValue, decimals = normalFee.fee.decimals, ) - // + // endregion return TxFeeState.MultipleFeeState( normalFee = TxFee( feeValue = normalFeeValue, @@ -1602,7 +1602,7 @@ internal class SwapInteractorImpl @Inject constructor( amount = normalFeeValue + otherNativeFeeValue, decimals = singleFee.fee.decimals, ) - // + // endregion return TxFeeState.SingleFeeState( fee = TxFee( feeValue = normalFeeValue, @@ -1645,7 +1645,7 @@ internal class SwapInteractorImpl @Inject constructor( // region otherNativeFee val normalFeeWithOtherNative = feeNormal + otherNativeFeeValue - val priorityFeeWithOtherNative = feeNormal + otherNativeFeeValue + val priorityFeeWithOtherNative = feePriority + otherNativeFeeValue val normalFiatValueWithNative = getFormattedFiatFees(fromToken, normalFeeWithOtherNative)[0] val priorityFiatValueWithNative = getFormattedFiatFees(fromToken, priorityFeeWithOtherNative)[0] @@ -1657,7 +1657,7 @@ internal class SwapInteractorImpl @Inject constructor( amount = priorityFeeWithOtherNative, decimals = priorityFee.amount.decimals, ) - // + // endregion TxFeeState.MultipleFeeState( normalFee = TxFee( feeValue = feeNormal, @@ -1700,7 +1700,7 @@ internal class SwapInteractorImpl @Inject constructor( amount = normalFeeWithOtherNative, decimals = this.normal.amount.decimals, ) - // + // endregion TxFeeState.SingleFeeState( fee = TxFee( feeValue = this.normal.amount.value ?: BigDecimal.ZERO, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt index 1915863d98..6c06e7e630 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt @@ -304,7 +304,7 @@ private fun Preview_AgreementBottomSheet() { private val previewData = GivePermissionBottomSheetConfig( data = SwapPermissionState.ReadyForRequest( - providerName = "1icnh", + providerName = "1inch", currency = "DAI", amount = "∞", walletAddress = "", From e7106ec7b8e3e1d21460a9d16485545ed21ce8b6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Jul 2024 20:32:55 +0300 Subject: [PATCH 06/53] Updated on 2026-08-14 --- gradle/dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index d6bb419a92..ca1addf175 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,9 +88,9 @@ markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-703" +tangemBlockchainSdk = "release-app_5.13-704" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-375" +tangemCardSdk = "release-app_5.13-376" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.21-tangem14" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From 62545c47e86fcca96e68dd7d799b995a6be599b6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Jul 2024 12:04:40 +0300 Subject: [PATCH 07/53] Updated on 2026-08-14 --- .../tangem/core/ui/components/transactions/TransactionList.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionList.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionList.kt index 6b4cc1bd3d..220b5c82ca 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionList.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionList.kt @@ -82,7 +82,9 @@ private fun LazyListScope.contentItems( when (item) { is TxHistoryState.TxHistoryItemState.GroupTitle -> item.itemKey is TxHistoryState.TxHistoryItemState.Title -> item.onExploreClick.hashCode() - is TxHistoryState.TxHistoryItemState.Transaction -> item.state.txHash + is TxHistoryState.TxHistoryItemState.Transaction -> + item.state.txHash + + ((item.state as? TransactionState.Content)?.hashCode() ?: "") } }, contentType = txHistoryItems.itemContentType { it::class.java }, From 9cb70782cf812fb1a542d8c07fe18d3d7af4466c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Jul 2024 11:33:04 +0300 Subject: [PATCH 08/53] Updated on 2026-08-14 --- .../tangem/tap/data/RuntimeUserWalletsStore.kt | 8 ++++++-- .../domain/card/DefaultDerivationsRepository.kt | 17 ++++++++--------- .../viewmodels/TokensListMigration.kt | 1 + .../viewmodels/TokensListViewModel.kt | 4 ---- .../card/DefaultDerivationsRepositoryTest.kt | 6 ++++-- .../local/userwallet/UserWalletsStore.kt | 7 +++++-- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt index 58d13616e4..1661cedb1d 100644 --- a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt +++ b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt @@ -1,5 +1,6 @@ package com.tangem.tap.data +import com.tangem.common.CompletionResult import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWallet @@ -30,7 +31,10 @@ internal class RuntimeUserWalletsStore( return userWalletsListManager.userWallets.firstOrNull() } - override suspend fun update(userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet) { - userWalletsListManager.update(userWalletId, update) + override suspend fun update( + userWalletId: UserWalletId, + update: suspend (UserWallet) -> UserWallet, + ): CompletionResult { + return userWalletsListManager.update(userWalletId, update) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt index d7bb96e4c3..64e1b31472 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt @@ -19,7 +19,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.runCatching +import kotlinx.coroutines.withContext import timber.log.Timber internal typealias Derivations = Map> @@ -48,13 +48,12 @@ internal class DefaultDerivationsRepository( tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations) .doOnSuccess { response -> - updatePublicKeys(userWalletId = userWalletId, keys = response.entries).fold( - onSuccess = { - validateDerivations(userWallet.scanResponse, derivations) + updatePublicKeys(userWalletId = userWalletId, keys = response.entries) + .doOnSuccess { + validateDerivations(scanResponse = it.scanResponse, derivations = derivations) return - }, - onFailure = { throw it }, - ) + } + .doOnFailure { throw it } } .doOnFailure { throw it } @@ -99,8 +98,8 @@ internal class DefaultDerivationsRepository( } } - private suspend fun updatePublicKeys(userWalletId: UserWalletId, keys: DerivedKeys): Result { - return runCatching(dispatchers.io) { + private suspend fun updatePublicKeys(userWalletId: UserWalletId, keys: DerivedKeys): CompletionResult { + return withContext(dispatchers.io) { userWalletsStore.update( userWalletId = userWalletId, update = { userWallet -> userWallet.updateDerivedKeys(keys) }, diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt index b10741f96c..41495924b3 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt @@ -135,6 +135,7 @@ internal class TokensListMigration( derivePublicKeysUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList) .onRight { addCryptoCurrenciesUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList) + store.dispatchNavigationAction { popTo() } } .onLeft { Timber.e(it, "Failed to derive public keys") } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt index a716c916bb..6e471d192d 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt @@ -11,8 +11,6 @@ import androidx.lifecycle.viewModelScope import androidx.paging.* import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.utils.popTo import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.core.ui.extensions.getGreyedOutIconRes @@ -26,7 +24,6 @@ import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase import com.tangem.domain.tokens.TokenWithBlockchain import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.extensions.fullNameWithoutTestnet import com.tangem.tap.common.extensions.getNetworkName import com.tangem.tap.features.customtoken.impl.presentation.models.SupportBlockchainType @@ -326,7 +323,6 @@ internal class TokensListViewModel @Inject constructor( ) uiState = state.copy(isSavingInProgress = false) - store.dispatchNavigationAction { popTo() } } } diff --git a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt index fdf0ed8db2..12be6b889d 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt @@ -12,7 +12,9 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider -import io.mockk.* +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.Test @@ -144,7 +146,7 @@ internal class DefaultDerivationsRepositoryTest { coEvery { tangemSdkManager.derivePublicKeys(null, any()) } returns CompletionResult.Success( DerivationTaskResponse(DerivedKeysMocks.ethereumDerivedKeys), ) - coEvery { userWalletsStore.update(defaultUserWalletId, any()) } just Runs + coEvery { userWalletsStore.update(defaultUserWalletId, any()) } returns CompletionResult.Success(userWallet) runCatching { repository.derivePublicKeys( diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt index 6aa2933ba2..54fe4ff322 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt @@ -1,5 +1,6 @@ package com.tangem.datasource.local.userwallet +import com.tangem.common.CompletionResult import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow @@ -14,6 +15,8 @@ interface UserWalletsStore { suspend fun getAllSyncOrNull(): List? - @Throws - suspend fun update(userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet) + suspend fun update( + userWalletId: UserWalletId, + update: suspend (UserWallet) -> UserWallet, + ): CompletionResult } \ No newline at end of file From e6d7bee87a5cd50c03570e06dd19394b4ae6876b Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Jul 2024 13:21:11 +0500 Subject: [PATCH 09/53] Updated on 2026-08-14 --- .../DefaultLegacyWalletConnectRepository.kt | 3 ++- .../domain/LegacyWalletConnectRepository.kt | 2 ++ .../domain/WalletConnectInteractor.kt | 20 +++++++++++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt index acee05571c..4f9055bfb4 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt @@ -35,7 +35,8 @@ internal class DefaultLegacyWalletConnectRepository( private val _activeSessions: MutableSharedFlow> = MutableSharedFlow() override val activeSessions: Flow> = _activeSessions - private var currentSessions: List = emptyList() + override var currentSessions: List = emptyList() + private set /** * @param projectId Project ID at https://cloud.walletconnect.com/ diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt index 9096e879e0..616643a960 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt @@ -9,6 +9,8 @@ interface LegacyWalletConnectRepository { val activeSessions: Flow> + val currentSessions: List + fun init(projectId: String) fun setUserNamespaces(userNamespaces: Map>) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index 66f8b5d16c..ae9d50d6cf 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -1,5 +1,6 @@ package com.tangem.tap.domain.walletconnect2.domain +import android.net.Uri import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.domain.tokens.model.CryptoCurrency @@ -132,6 +133,7 @@ class WalletConnectInteractor( runCatching { if (accounts.isEmpty()) return isWalletConnectReadyForDeepLinks = true + if (deeplinkStack.empty()) return val lastDeeplink = deeplinkStack.pop() store.dispatchOnMain(WalletConnectAction.OpenSession(lastDeeplink)) }.onFailure { @@ -361,6 +363,18 @@ class WalletConnectInteractor( * @param deeplink deeplink to handle */ fun addDeeplink(deeplink: String) { + val deeplinkQueries = deeplink.split(WC_SPLIT_CHAR).lastOrNull() + val sessionTopic = deeplinkQueries?.let { Uri.parse(it).getQueryParameter(WC_TOPIC_QUERY_NAME) } + + val isAlreadyActiveSessionTopic = walletConnectRepository.currentSessions.any { session -> + session.topic == sessionTopic + } + + if (isAlreadyActiveSessionTopic && deeplinkQueries != null) { + Timber.i("WC already has an active session topic: $deeplink") + return + } + if (isWalletConnectReadyForDeepLinks) { store.dispatchOnMain(WalletConnectAction.OpenSession(deeplink)) } else { @@ -406,7 +420,9 @@ class WalletConnectInteractor( return sessionRequestConverter.prepareRequest(sessionRequest, userWalletId) } - companion object { - private const val WC_SCHEME = "wc" + private companion object { + const val WC_SCHEME = "wc" + const val WC_TOPIC_QUERY_NAME = "sessionTopic" + const val WC_SPLIT_CHAR = "/" } } \ No newline at end of file From a10342f948c41b68ade7c973ee5191c104afd532 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Jul 2024 11:56:30 +0300 Subject: [PATCH 10/53] Updated on 2026-08-14 --- app/build.gradle.kts | 12 ---- .../java/com/tangem/tap/TangemApplication.kt | 10 +--- .../com/tangem/tap/common/chat/ChatManager.kt | 23 -------- .../tap/common/chat/opener/ChatOpener.kt | 8 --- .../implementation/SprinklrChatOpener.kt | 57 ------------------- .../common/feedback/LegacyFeedbackManager.kt | 42 -------------- .../tap/common/redux/global/GlobalAction.kt | 2 - .../common/redux/global/GlobalMiddleware.kt | 20 ------- .../ui/dialogs/AttestationFailedDialog.kt | 2 +- gradle/dependencies.toml | 1 - 10 files changed, 3 insertions(+), 174 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bbe71b61be..a003607888 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -27,20 +27,12 @@ configurations.all { resolutionStrategy { dependencySubstitution { - substitute(module("com.facebook.react:react-native")) - .using(module("com.facebook.react:react-android:0.72.4")) - - substitute(module("com.facebook.react:hermes-engine")) - .using(module("com.facebook.react:hermes-android:0.72.4")) - substitute(module("org.bouncycastle:bcprov-jdk15on")) .using(module("org.bouncycastle:bcprov-jdk18on:1.73")) } force( "org.bouncycastle:bcpkix-jdk15on:1.70", - "com.facebook.react:react-android:0.72.4", - "com.facebook.react:hermes-android:0.72.4", ) } } @@ -224,10 +216,6 @@ dependencies { implementation(deps.walletConnectCore) implementation(deps.walletConnectWeb3) implementation(deps.prettyLogger) - implementation("com.facebook.react:react-android:0.72.4") - implementation(deps.sprClient) { - exclude(group = "com.github.stephenc.jcip") - } /** Testing libraries */ testImplementation(deps.test.coroutine) diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 9a6577a66f..eaca995355 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -52,7 +52,6 @@ import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler -import com.tangem.tap.common.chat.ChatManager import com.tangem.tap.common.feedback.AdditionalFeedbackInfo import com.tangem.tap.common.feedback.LegacyFeedbackManager import com.tangem.tap.common.images.createCoilImageLoader @@ -316,7 +315,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private fun initWithConfigDependency(config: Config) { initAnalytics(this, config) - initFeedbackManager(this, foregroundActivityObserver, store) + initFeedbackManager(this, store) } private fun initAnalytics(application: Application, config: Config) { @@ -337,11 +336,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { // ExceptionHandler.append(blockchainExceptionHandler) TODO: [REDACTED_JIRA] } - private fun initFeedbackManager( - context: Context, - foregroundActivityObserver: ForegroundActivityObserver, - store: Store, - ) { + private fun initFeedbackManager(context: Context, store: Store) { fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo { return AdditionalFeedbackInfo().apply { appVersion = try { @@ -382,7 +377,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { val feedbackManager = LegacyFeedbackManager( infoHolder = additionalFeedbackInfo, logCollector = tangemLogCollector, - chatManager = ChatManager(foregroundActivityObserver), feedbackManagerFeatureToggles = feedbackManagerFeatureToggles, getFeedbackEmailUseCase = getFeedbackEmailUseCase, ) diff --git a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt deleted file mode 100644 index 5bbe3fc679..0000000000 --- a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.tap.common.chat - -import android.content.Context -import com.tangem.datasource.config.models.ChatConfig -import com.tangem.datasource.config.models.SprinklrConfig -import com.tangem.tap.ForegroundActivityObserver -import com.tangem.tap.common.chat.opener.ChatOpener -import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener -import java.io.File - -class ChatManager(private val foregroundActivityObserver: ForegroundActivityObserver) { - private val openers = mutableMapOf() - - fun open(config: ChatConfig, createLogsFile: (Context) -> File?, createFeedbackFile: (Context) -> File?) { - val opener = openers.getOrPut(config) { - when (config) { - is SprinklrConfig -> SprinklrChatOpener(config, foregroundActivityObserver) - } - } - - opener.open(createFeedbackFile, createLogsFile) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt deleted file mode 100644 index aff21cad05..0000000000 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.tap.common.chat.opener - -import android.content.Context -import java.io.File - -internal interface ChatOpener { - fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt deleted file mode 100644 index 0fcb2a4cf2..0000000000 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.tangem.tap.common.chat.opener.implementation - -import android.annotation.SuppressLint -import android.app.Application -import android.content.Context -import android.provider.Settings -import com.spr.messengerclient.config.SPRMessenger -import com.spr.messengerclient.config.bean.SPRMessengerConfig -import com.tangem.common.extensions.guard -import com.tangem.datasource.config.models.SprinklrConfig -import com.tangem.tap.ForegroundActivityObserver -import com.tangem.tap.common.chat.opener.ChatOpener -import timber.log.Timber -import java.io.File -import java.util.Locale - -internal class SprinklrChatOpener( - private val config: SprinklrConfig, - private val foregroundActivityObserver: ForegroundActivityObserver, -) : ChatOpener { - - override fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) { - val messenger = SPRMessenger.shared() - - if (messenger.config == null) { - initSprConfig(messenger) - } - - messenger.startApplication() - } - - private fun initSprConfig(messenger: SPRMessenger) { - val application = foregroundActivityObserver.foregroundActivity?.application.guard { - Timber.e("The SPR chat cannot be opened because there are no activities in foreground") - return - } - - messenger.takeOff(application, createSprConfig(application, config)) - } - - @SuppressLint("HardwareIds") - private fun createSprConfig(application: Application, config: SprinklrConfig): SPRMessengerConfig { - return SPRMessengerConfig().apply { - appId = config.appId - appKey = CHAT_APP_KEY - deviceId = Settings.Secure.getString(application.contentResolver, Settings.Secure.ANDROID_ID) - environment = config.environment - skin = CHAT_SKIN - locale = Locale.getDefault().language - } - } - - private companion object { - const val CHAT_APP_KEY = "com.sprinklr.messenger.release" - const val CHAT_SKIN = "MODERN" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/feedback/LegacyFeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/LegacyFeedbackManager.kt index 9f2f6bb335..e7713995ac 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/LegacyFeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/LegacyFeedbackManager.kt @@ -2,13 +2,11 @@ package com.tangem.tap.common.feedback import android.content.Context import com.tangem.core.navigation.email.EmailSender -import com.tangem.datasource.config.models.ChatConfig import com.tangem.domain.common.TapWorkarounds import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.feedback.GetFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.common.chat.ChatManager import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.sendEmail import com.tangem.tap.common.log.TangemLogCollector @@ -29,12 +27,10 @@ import java.io.StringWriter class LegacyFeedbackManager( val infoHolder: AdditionalFeedbackInfo, private val logCollector: TangemLogCollector, - private val chatManager: ChatManager, private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles, private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase, ) { - private var sessionFeedbackFile: File? = null private var sessionLogsFile: File? = null fun sendEmail(feedbackData: FeedbackData, scanResponse: ScanResponse?) { @@ -97,43 +93,6 @@ class LegacyFeedbackManager( } } - fun openChat(config: ChatConfig, feedbackData: FeedbackData) { - chatManager.open( - config = config, - createLogsFile = ::getLogFile, - createFeedbackFile = { context -> getFeedbackFile(context, feedbackData) }, - ) - } - - private fun getFeedbackFile(context: Context, feedbackData: FeedbackData): File? { - return try { - if (sessionFeedbackFile != null) { - return sessionFeedbackFile - } - val file = File(context.filesDir, FEEDBACK_FILE) - file.delete() - file.createNewFile() - - val feedback = feedbackData.run { - prepare(infoHolder) - joinTogether(context, infoHolder) - } - val fileWriter = FileWriter(file) - fileWriter.write(feedback) - fileWriter.close() - - if (file.exists()) { - sessionFeedbackFile = file - sessionFeedbackFile - } else { - null - } - } catch (ex: Exception) { - Timber.e(ex, "Can't create the logs file") - null - } - } - private fun getLogFile(context: Context): File? { return try { if (sessionLogsFile != null) { @@ -172,7 +131,6 @@ class LegacyFeedbackManager( private companion object { const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com" const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com" - const val FEEDBACK_FILE = "feedback.txt" const val LOGS_FILE = "logs.txt" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index e7fba06fb7..ade5d123b2 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -3,7 +3,6 @@ package com.tangem.tap.common.redux.global import com.tangem.common.CompletionResult import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.datasource.config.ConfigManager -import com.tangem.datasource.config.models.ChatConfig import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.models.scan.ScanResponse @@ -80,7 +79,6 @@ sealed class GlobalAction : Action { data class SetFeedbackManager(val feedbackManager: LegacyFeedbackManager) : GlobalAction() data class SendEmail(val feedbackData: FeedbackData, val scanResponse: ScanResponse?) : GlobalAction() - data class OpenChat(val feedbackData: FeedbackData, val chatConfig: ChatConfig? = null) : GlobalAction() object ExchangeManager : GlobalAction() { object Init : GlobalAction() { diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index 957b756803..9c6882deda 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -74,26 +74,6 @@ private fun handleAction(action: Action, appState: () -> AppState?) { scanResponse = action.scanResponse, ) } - is GlobalAction.OpenChat -> { - val globalState = store.state.globalState - val feedbackManager = globalState.feedbackManager.guard { - store.dispatchDebugErrorNotification("FeedbackManager not initialized") - return - } - val config = globalState.configManager?.config.guard { - store.dispatchDebugErrorNotification("Config not initialized") - return - } - - // if config not set -> try to get it based on a scanResponse.productType - val unsafeChatConfig = action.chatConfig ?: config.sprinklr - - val chatConfig = unsafeChatConfig.guard { - store.dispatchDebugErrorNotification("The chat config is not initialized") - return - } - feedbackManager.openChat(chatConfig, action.feedbackData) - } is GlobalAction.ExchangeManager.Init -> { val appStateSafe = appState() ?: return val config = appStateSafe.globalState.configManager?.config ?: return diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AttestationFailedDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AttestationFailedDialog.kt index 3e9df5885e..5b6c8b8c85 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AttestationFailedDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AttestationFailedDialog.kt @@ -13,7 +13,7 @@ internal object AttestationFailedDialog { return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply { setTitle(R.string.common_error) setMessage(R.string.issuer_signature_loading_failed) - setPositiveButton(R.string.ok) { dialog, _ -> + setPositiveButton(R.string.common_ok) { dialog, _ -> dialog.dismiss() } setOnDismissListener { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index ca1addf175..09d4ef606c 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -254,7 +254,6 @@ reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", versio walletConnectCore = { module = "com.walletconnect:android-core", version.ref = "walletConnectCore" } walletConnectWeb3 = { module = "com.walletconnect:web3wallet", version.ref = "walletConnectWeb3" } prettyLogger = { module = "com.orhanobut:logger", version.ref = "prettyLogger" } -sprClient = { module = "com.spr:messengerclient", version.ref = "spr-client" } chucker = { module = "com.github.chuckerteam.chucker:library", version.ref = "chucker" } chuckerStub = { module = "com.github.chuckerteam.chucker:library-no-op", version.ref = "chucker" } mlKit-barcodeScanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "mlKit-barcodeScanning" } From 14b9178e2b9be3f2b1621da8e787f5919b434fa5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Jul 2024 14:57:58 +0500 Subject: [PATCH 11/53] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 09d4ef606c..66427fc530 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.13-704" +tangemBlockchainSdk = "release-app_5.13-706" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.13-376" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From ec7b018eedd48080bfcfd363790bce468c177748 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Jul 2024 16:27:33 +0500 Subject: [PATCH 12/53] Updated on 2026-08-14 --- .../walletconnect2/domain/WalletConnectInteractor.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index ae9d50d6cf..1f5dce9663 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -1,6 +1,5 @@ package com.tangem.tap.domain.walletconnect2.domain -import android.net.Uri import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.domain.tokens.model.CryptoCurrency @@ -363,14 +362,15 @@ class WalletConnectInteractor( * @param deeplink deeplink to handle */ fun addDeeplink(deeplink: String) { - val deeplinkQueries = deeplink.split(WC_SPLIT_CHAR).lastOrNull() - val sessionTopic = deeplinkQueries?.let { Uri.parse(it).getQueryParameter(WC_TOPIC_QUERY_NAME) } + val deeplinkRegex = Regex(WC_PARAM_REGEX) + val matched = deeplinkRegex.findAll(deeplink) + val sessionTopic = matched.firstOrNull { it.value.contains(WC_TOPIC_QUERY_NAME) }?.groupValues?.lastOrNull() val isAlreadyActiveSessionTopic = walletConnectRepository.currentSessions.any { session -> session.topic == sessionTopic } - if (isAlreadyActiveSessionTopic && deeplinkQueries != null) { + if (isAlreadyActiveSessionTopic && sessionTopic != null) { Timber.i("WC already has an active session topic: $deeplink") return } @@ -423,6 +423,6 @@ class WalletConnectInteractor( private companion object { const val WC_SCHEME = "wc" const val WC_TOPIC_QUERY_NAME = "sessionTopic" - const val WC_SPLIT_CHAR = "/" + const val WC_PARAM_REGEX = "([a-zA-Z\\d-]+)=([a-zA-Z\\d]+)" } } \ No newline at end of file From 27fc4bd9f55b12a4ab54a654590d1e657ef4c9ba Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Jul 2024 14:56:42 +0300 Subject: [PATCH 13/53] Updated on 2026-08-14 --- .../tokendetails/ui/TokenDetailsScreen.kt | 13 +++++++++++-- .../wallet/presentation/wallet/ui/WalletScreen.kt | 4 +++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index 9ea4aa95ee..378b55e62d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.Scaffold import androidx.compose.material3.ScaffoldDefaults -import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -32,6 +31,7 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlock import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.OkxPromoNotification +import com.tangem.core.ui.components.snackbar.TangemSnackbarHost import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.txHistoryItems import com.tangem.core.ui.event.EventEffect @@ -64,7 +64,16 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) { val snackbarHostState = remember { SnackbarHostState() } Scaffold( topBar = { TokenDetailsTopAppBar(config = state.topAppBarConfig) }, - snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, + snackbarHost = { + TangemSnackbarHost( + modifier = Modifier.padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = bottomBarHeight + TangemTheme.dimens.spacing16, + ), + hostState = snackbarHostState, + ) + }, contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.navigationBars), containerColor = TangemTheme.colors.background.secondary, ) { scaffoldPaddings -> diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 41698402f7..806518dbe8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -368,7 +368,9 @@ private fun BaseScaffoldWithMarkets( WalletSnackbarHost( snackbarHostState = it, event = state.event, - modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing16), + modifier = Modifier + .padding(bottom = TangemTheme.dimens.spacing4) + .navigationBarsPadding(), ) }, containerColor = TangemTheme.colors.background.secondary, From 8fc579ba5cdc5a2198c7c07e85f462d10057a1d0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Jul 2024 15:31:02 +0300 Subject: [PATCH 14/53] Updated on 2026-08-14 --- .../models/response/ExchangeStatusResponse.kt | 6 ++ .../repository/DefaultCurrenciesRepository.kt | 49 +++++++++++++++- .../tokens/AddCryptoCurrenciesUseCase.kt | 57 +++++++++++++++++-- .../tokens/repository/CurrenciesRepository.kt | 10 ++++ .../repository/MockCurrenciesRepository.kt | 8 +++ .../converters/ExchangeStatusConverter.kt | 2 + .../domain/models/domain/ExchangeStatus.kt | 2 + .../state/SwapTransactionsState.kt | 1 + .../components/ExchangeStatusNotifications.kt | 17 ++++++ ...enDetailsSwapTransactionsStateConverter.kt | 34 ++++++++--- .../exchange/ExchangeStatusBlock.kt | 6 ++ .../viewmodels/ExchangeStatusFactory.kt | 46 +++++++++++---- .../viewmodels/TokenDetailsClickIntents.kt | 2 + .../viewmodels/TokenDetailsViewModel.kt | 15 ++++- 14 files changed, 228 insertions(+), 27 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt index 5b43c9cea5..7682c70d4f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt @@ -18,6 +18,12 @@ data class ExchangeStatusResponse( @Json(name = "error") val error: ExchangeStatusError?, + + @Json(name = "refundNetwork") + val refundNetwork: String? = null, + + @Json(name = "refundContractAddress") + val refundContractAddress: String? = null, ) enum class ExchangeStatus { diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 9459984032..214d9721e1 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -39,7 +39,7 @@ import kotlinx.coroutines.withContext import timber.log.Timber import com.tangem.blockchain.common.FeePaidCurrency as FeePaidSdkCurrency -@Suppress("LargeClass", "LongParameterList") +@Suppress("LargeClass", "LongParameterList", "TooManyFunctions") internal class DefaultCurrenciesRepository( private val tangemTechApi: TangemTechApi, private val tangemExpressApi: TangemExpressApi, @@ -53,6 +53,7 @@ internal class DefaultCurrenciesRepository( private val demoConfig = DemoConfig() private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory() + private val cryptoCurrencyFactory = CryptoCurrencyFactory() private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig) private val userTokensResponseFactory = UserTokensResponseFactory() private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility() @@ -125,7 +126,7 @@ internal class DefaultCurrenciesRepository( return newTokens .filterNot { savedCurrencies.hasCoinForToken(it) } // tokens without coins .mapNotNull { - CryptoCurrencyFactory().createCoin( + cryptoCurrencyFactory.createCoin( blockchain = getBlockchain(networkId = it.network.id), extraDerivationPath = it.network.derivationPath.value, derivationStyleProvider = getUserWallet(userWalletId).scanResponse.derivationStyleProvider, @@ -413,12 +414,54 @@ internal class DefaultCurrenciesRepository( } override fun createTokenCurrency(cryptoCurrency: CryptoCurrency.Token, network: Network): CryptoCurrency.Token { - return CryptoCurrencyFactory().createToken( + return cryptoCurrencyFactory.createToken( cryptoCurrency = cryptoCurrency, network = network, ) } + override suspend fun createTokenCurrency( + userWalletId: UserWalletId, + contractAddress: String, + networkId: String, + ): CryptoCurrency.Token { + val userWallet = getUserWallet(userWalletId) + val token = withContext(dispatchers.io) { + val foundToken = tangemTechApi.getCoins( + contractAddress = contractAddress, + networkIds = networkId, + ) + .getOrThrow() + .coins + .firstNotNullOfOrNull { coin -> + val networksWithTheSameAddress = coin.networks.filter { network -> + (network.contractAddress != null || network.decimalCount != null) && + network.contractAddress?.equals(contractAddress, ignoreCase = true) == true + } + + if (networksWithTheSameAddress.isNotEmpty()) { + coin.copy(networks = networksWithTheSameAddress) + } else { + null + } + } ?: error("Token not found") + val network = foundToken.networks.firstOrNull { it.networkId == networkId } ?: error("Network not found") + CryptoCurrencyFactory.Token( + symbol = foundToken.symbol, + name = foundToken.name, + contractAddress = contractAddress, + decimals = network.decimalCount?.toInt() ?: error("Decimals not found"), + id = foundToken.id, + ) + } + return cryptoCurrencyFactory.createToken( + token = token, + networkId = networkId, + extraDerivationPath = null, + derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider, + ) ?: error("Unable to create token") + } + private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow> { return userTokensStore.get(userWallet.walletId).map { storedTokens -> responseCurrenciesFactory.createCurrencies( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/AddCryptoCurrenciesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/AddCryptoCurrenciesUseCase.kt index e8c6f0b7a8..01223c61a3 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/AddCryptoCurrenciesUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/AddCryptoCurrenciesUseCase.kt @@ -79,13 +79,35 @@ class AddCryptoCurrenciesUseCase( .toNonEmptyListOrNull() ?: return@either - catch({ currenciesRepository.addCurrencies(userWalletId, currenciesToAdd) }) { - raise(it) - } - + addCurrencies(userWalletId, currenciesToAdd) refreshUpdatedNetworks(userWalletId, currenciesToAdd, existingCurrencies) } + suspend operator fun invoke( + userWalletId: UserWalletId, + contractAddress: String, + networkId: String, + ): Either = either { + val existingCurrencies = + catch({ currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId) }) { + raise(it) + } + val foundToken = existingCurrencies + .filterIsInstance() + .firstOrNull { + it.network.backendId == networkId && + !it.isCustom && + it.contractAddress.equals(contractAddress, true) + } + if (foundToken != null) { + return@either foundToken + } + val tokenToAdd = createTokenCurrency(userWalletId, contractAddress, networkId) + addCurrencies(userWalletId, listOf(tokenToAdd)) + refreshUpdatedNetworks(userWalletId, listOf(tokenToAdd), existingCurrencies) + tokenToAdd + } + /** * Refreshes the network statuses for tokens that have corresponding coins in the * [existingCurrencies] list. @@ -117,6 +139,33 @@ class AddCryptoCurrenciesUseCase( } } + private suspend fun Raise.createTokenCurrency( + userWalletId: UserWalletId, + contractAddress: String, + networkId: String, + ): CryptoCurrency.Token { + return catch( + block = { + currenciesRepository.createTokenCurrency( + userWalletId = userWalletId, + contractAddress = contractAddress, + networkId = networkId, + ) + }, + catch = { + raise(it) + }, + ) + } + + private suspend fun Raise.addCurrencies(userWalletId: UserWalletId, tokens: List) { + catch( + { currenciesRepository.addCurrencies(userWalletId, tokens) }, + ) { + raise(it) + } + } + /** * Determines if the [existingCurrencies] list contains a coin that corresponds * to the given [token]. diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt index 5bdf7b4067..d5daebcd8e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.Flow /** * Repository for everything related to the tokens of user wallet * */ +@Suppress("TooManyFunctions") interface CurrenciesRepository { /** @@ -210,4 +211,13 @@ interface CurrenciesRepository { * Creates token [cryptoCurrency] based on current token and [network] it`s will be added */ fun createTokenCurrency(cryptoCurrency: CryptoCurrency.Token, network: Network): CryptoCurrency.Token + + /** + * Creates token [cryptoCurrency] based on [contractAddress] and [networkId] it`s will be added + */ + suspend fun createTokenCurrency( + userWalletId: UserWalletId, + contractAddress: String, + networkId: String, + ): CryptoCurrency.Token } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt index d4d616d88a..ff73d40462 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt @@ -136,4 +136,12 @@ internal class MockCurrenciesRepository( override fun createTokenCurrency(cryptoCurrency: CryptoCurrency.Token, network: Network): CryptoCurrency.Token { return cryptoCurrency } + + override suspend fun createTokenCurrency( + userWalletId: UserWalletId, + contractAddress: String, + networkId: String, + ): CryptoCurrency.Token { + error("not implemented") + } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt index 0a566d5500..d782b606f9 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt @@ -15,6 +15,8 @@ internal class ExchangeStatusConverter : Converter Unit, val onGoToProviderClick: (String) -> Unit, ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt index ac825983b8..58b0829319 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt @@ -3,6 +3,8 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.componen import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.tokendetails.impl.R @Immutable @@ -35,4 +37,19 @@ internal sealed class ExchangeStatusNotifications(val config: NotificationConfig ), ), ) + + data class TokenRefunded( + val cryptoCurrency: CryptoCurrency, + val onGoToTokenClick: () -> Unit, + ) : ExchangeStatusNotifications( + config = NotificationConfig( + title = stringReference("TITLE FOR TOKEN REFUND"), + subtitle = stringReference("SUBTITLE FOR TOKEN REFUND"), + iconResId = R.drawable.ic_alert_triangle_20, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = stringReference("Go to token"), + onClick = onGoToTokenClick, + ), + ), + ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt index 6c1a71f7af..fba5085305 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt @@ -62,7 +62,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( }?.fiatRate?.multiply(fromAmount) val timestamp = transaction.timestamp val notifications = - getNotification(transaction.status?.status, transaction.status?.txExternalUrl) + getNotification(transaction.status?.status, transaction.status?.txExternalUrl, null) val showProviderLink = getShowProviderLink(notifications, transaction.status) result.add( SwapTransactionsState( @@ -77,10 +77,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( statuses = getStatuses(transaction.status?.status), hasFailed = transaction.status?.status == ExchangeStatus.Failed, activeStatus = transaction.status?.status, - notification = getNotification( - transaction.status?.status, - transaction.status?.txExternalUrl, - ), + notification = notifications, toCryptoCurrency = toCryptoCurrency, toCryptoAmount = BigDecimalFormatter.formatCryptoAmount( cryptoAmount = toAmount, @@ -110,10 +107,15 @@ internal class TokenDetailsSwapTransactionsStateConverter( return result.toPersistentList() } - fun updateTxStatus(tx: SwapTransactionsState, statusModel: ExchangeStatusModel?): SwapTransactionsState { + fun updateTxStatus( + tx: SwapTransactionsState, + statusModel: ExchangeStatusModel?, + refundToken: CryptoCurrency?, + isRefundTerminalStatus: Boolean, + ): SwapTransactionsState { if (statusModel == null || tx.activeStatus == statusModel.status) return tx val hasFailed = tx.hasFailed || statusModel.status == ExchangeStatus.Failed - val notifications = getNotification(statusModel.status, statusModel.txExternalUrl) + val notifications = getNotification(statusModel.status, statusModel.txExternalUrl, refundToken) val showProviderLink = getShowProviderLink(notifications, statusModel) return tx.copy( activeStatus = statusModel.status, @@ -122,6 +124,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( statuses = getStatuses(statusModel.status, hasFailed), txUrl = statusModel.txExternalUrl, showProviderLink = showProviderLink, + isRefundTerminalStatus = isRefundTerminalStatus, ) } @@ -133,7 +136,11 @@ internal class TokenDetailsSwapTransactionsStateConverter( ) } - private fun getNotification(status: ExchangeStatus?, txUrl: String?): ExchangeStatusNotifications? { + private fun getNotification( + status: ExchangeStatus?, + txUrl: String?, + refundToken: CryptoCurrency?, + ): ExchangeStatusNotifications? { if (txUrl == null) return null return when (status) { ExchangeStatus.Failed -> { @@ -152,6 +159,15 @@ internal class TokenDetailsSwapTransactionsStateConverter( clickIntents.onGoToProviderClick(txUrl) } } + ExchangeStatus.Refunded -> { + if (refundToken == null) { + null + } else { + ExchangeStatusNotifications.TokenRefunded(refundToken) { + clickIntents.onGoToRefundedTokenClick(refundToken) + } + } + } else -> null } } @@ -287,7 +303,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( status = ExchangeStatus.Refunded, text = TextReference.Res(R.string.express_exchange_status_refunded), isActive = false, - isDone = isRefunded, + isDone = false, ) else -> ExchangeStatusState( status = ExchangeStatus.Sending, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt index c715a6c6bd..ff2dd3baff 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt @@ -115,6 +115,11 @@ private fun ExchangeStatusStep( color = TangemTheme.colors.icon.warning, isDone = it.isDone, ) + it.status == ExchangeStatus.Refunded -> ExchangeStep( + iconRes = R.drawable.ic_close_24, + color = TangemTheme.colors.icon.warning, + isDone = it.isDone, + ) it.status == ExchangeStatus.Verifying -> ExchangeStep( iconRes = R.drawable.ic_exclamation_24, color = TangemTheme.colors.icon.attention, @@ -141,6 +146,7 @@ private fun ExchangeStatusStep( private fun ExchangeStatusStepText(stepStatus: ExchangeStatusState) { val textColor = when { stepStatus.status == ExchangeStatus.Cancelled -> TangemTheme.colors.icon.warning + stepStatus.status == ExchangeStatus.Refunded -> TangemTheme.colors.icon.warning stepStatus.status == ExchangeStatus.Failed && !stepStatus.isDone -> TangemTheme.colors.icon.warning stepStatus.status == ExchangeStatus.Verifying && !stepStatus.isDone -> TangemTheme.colors.icon.attention stepStatus.isDone -> TangemTheme.colors.text.primary1 diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt index 54356f4665..e4118e7141 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt @@ -4,6 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.datasource.local.swaptx.ExchangeAnalyticsStatus import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.models.analytics.TokenExchangeAnalyticsEvent @@ -38,6 +39,7 @@ internal class ExchangeStatusFactory( private val swapRepository: SwapRepository, private val quotesRepository: QuotesRepository, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, + private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, private val swapTransactionStatusStore: SwapTransactionStatusStore, private val dispatchers: CoroutineDispatcherProvider, private val clickIntents: TokenDetailsClickIntents, @@ -85,7 +87,7 @@ internal class ExchangeStatusFactory( val bottomSheetConfig = state.bottomSheetConfig?.content as? ExchangeStatusBottomSheetConfig ?: return state val selectedTx = bottomSheetConfig.value - return if (selectedTx.activeStatus.isTerminal()) { + return if (selectedTx.activeStatus.isTerminal(selectedTx.isRefundTerminalStatus)) { swapTransactionRepository.removeTransaction( userWalletId = userWalletId, fromCryptoCurrency = selectedTx.fromCryptoCurrency, @@ -104,12 +106,19 @@ internal class ExchangeStatusFactory( suspend fun updateSwapTxStatuses(swapTxList: PersistentList) = withContext(dispatchers.io) { swapTxList.map { tx -> async { - if (tx.activeStatus.isTerminal()) { + val statusModel = getExchangeStatus(tx.txId) + val isRefundTerminalStatus = statusModel?.refundNetwork == null && + statusModel?.refundContractAddress == null + if (tx.activeStatus.isTerminal(isRefundTerminalStatus)) { tx } else { - val statusModel = getExchangeStatus(tx.txId) - swapTransactionsStateConverter - .updateTxStatus(tx, statusModel) + val addedRefundToken = addRefundCurrencyIfNeeded(statusModel) + swapTransactionsStateConverter.updateTxStatus( + tx = tx, + statusModel = statusModel, + refundToken = addedRefundToken, + isRefundTerminalStatus = isRefundTerminalStatus, + ) } } } @@ -142,6 +151,20 @@ internal class ExchangeStatusFactory( } } + private suspend fun addRefundCurrencyIfNeeded(status: ExchangeStatusModel?): CryptoCurrency? { + status ?: return null + val refundNetwork = status.refundNetwork + val refundContractAddress = status.refundContractAddress + if (refundNetwork != null && refundContractAddress != null) { + return addCryptoCurrenciesUseCase( + userWalletId = userWalletId, + contractAddress = refundContractAddress, + networkId = refundNetwork, + ).getOrNull() + } + return null + } + private fun getExchangeStatusState( savedTransactions: List?, quotes: Set, @@ -156,11 +179,14 @@ internal class ExchangeStatusFactory( ) } - private fun ExchangeStatus?.isTerminal() = this == ExchangeStatus.Refunded || - this == ExchangeStatus.Finished || - this == ExchangeStatus.Cancelled || - this == ExchangeStatus.TxFailed || - this == ExchangeStatus.Unknown + private fun ExchangeStatus?.isTerminal(isRefundTerminal: Boolean): Boolean { + val needTerminalRefund = this == ExchangeStatus.Refunded && isRefundTerminal + return needTerminalRefund || + this == ExchangeStatus.Finished || + this == ExchangeStatus.Cancelled || + this == ExchangeStatus.TxFailed || + this == ExchangeStatus.Unknown + } private fun toAnalyticStatus(status: ExchangeStatus?): ExchangeAnalyticsStatus? { return when (status) { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt index 751b8dcd92..387d2f3482 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt @@ -55,4 +55,6 @@ interface TokenDetailsClickIntents { fun onCopyAddress(): TextReference? fun onAssociateClick() + + fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 4d115db24e..9fa0481605 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -96,6 +96,7 @@ internal class TokenDetailsViewModel @Inject constructor( private val getCurrencyWarningsUseCase: GetCurrencyWarningsUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, + private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, private val shouldShowSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase, private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase, private val getExtendedPublicKeyForCurrencyUseCase: GetExtendedPublicKeyForCurrencyUseCase, @@ -156,6 +157,7 @@ internal class TokenDetailsViewModel @Inject constructor( swapRepository = swapRepository, quotesRepository = quotesRepository, getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, + addCryptoCurrenciesUseCase = addCryptoCurrenciesUseCase, swapTransactionStatusStore = swapTransactionStatusStore, dispatchers = dispatchers, clickIntents = this, @@ -691,7 +693,8 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onDismissBottomSheet() { - if (internalUiState.value.bottomSheetConfig?.content is ExchangeStatusBottomSheetConfig) { + val bsContent = internalUiState.value.bottomSheetConfig?.content + if (bsContent is ExchangeStatusBottomSheetConfig) { viewModelScope.launch(dispatchers.main) { internalUiState.value = exchangeStatusFactory.removeTransactionOnBottomSheetClosed() } @@ -713,6 +716,16 @@ internal class TokenDetailsViewModel @Inject constructor( router.openUrl(url) } + override fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency) { + if (internalUiState.value.bottomSheetConfig?.content is ExchangeStatusBottomSheetConfig) { + viewModelScope.launch(dispatchers.main) { + internalUiState.value = exchangeStatusFactory.removeTransactionOnBottomSheetClosed() + } + } + internalUiState.value = stateFactory.getStateWithClosedBottomSheet() + router.openTokenDetails(userWalletId, cryptoCurrency) + } + override fun onSwapPromoDismiss() { viewModelScope.launch(dispatchers.main) { shouldShowSwapPromoTokenUseCase.neverToShow() From 6391ced12b6669f37386f2a50455e6eb6a92da7e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Jul 2024 15:11:15 +0300 Subject: [PATCH 15/53] Updated on 2026-08-14 --- .../extension/AppExtensionConfigurations.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt index d65c211b5d..1d659a2d09 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt @@ -65,9 +65,7 @@ private fun AppExtension.configureBuildTypes() { private fun AndroidBuildType.configureBuildVariant(extension: AppExtension, buildType: BuildType) { when (buildType) { - BuildType.Release, - BuildType.External, - -> { + BuildType.Release -> { isDebuggable = false isMinifyEnabled = false proguardFiles(extension.getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") @@ -76,6 +74,11 @@ private fun AndroidBuildType.configureBuildVariant(extension: AppExtension, buil isDebuggable = true isMinifyEnabled = false } + BuildType.External -> { + initWith(extension.buildTypes.getByName(BuildType.Release.id)) + matchingFallbacks.add(BuildType.Release.id) + signingConfig = extension.signingConfigs.getByName(BuildType.Debug.id) + } BuildType.Internal, BuildType.Mocked, -> { From f234915b638ee1c692ef242badecc40ba0a03260 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Jul 2024 12:45:54 +0300 Subject: [PATCH 16/53] Updated on 2026-08-14 --- .../ui/cardsettings/CardSettingsViewModel.kt | 18 +++++++--- .../details/ui/common/utils/ResetToFactory.kt | 5 ++- .../ui/resetcard/ResetCardViewModel.kt | 33 +++++++------------ .../com/tangem/common/routing/AppRoute.kt | 29 ++++++++++------ .../com/tangem/domain/models/scan/CardDTO.kt | 9 ----- .../wallet/viewmodels/WalletViewModel.kt | 2 ++ 6 files changed, 48 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index 97d4a464d9..d8b9f44ca9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -109,7 +109,10 @@ internal class CardSettingsViewModel @Inject constructor( if (isResetCardAllowed) { CardInfo.ResetToFactorySettings( - description = getResetToFactoryDescription(card.backupStatus, cardTypesResolver), + description = getResetToFactoryDescription( + isActiveBackupStatus = card.backupStatus?.isActive == true, + typesResolver = cardTypesResolver, + ), ).let(::add) } } @@ -135,10 +138,15 @@ internal class CardSettingsViewModel @Inject constructor( push( route = AppRoute.ResetToFactory( userWalletId = userWalletId, - cardSpecificInfo = AppRoute.ResetToFactory.CardSpecificInfo( - cardId = card.cardId, - backupStatus = card.backupStatus, - ), + cardId = card.cardId, + isActiveBackupStatus = card.backupStatus?.isActive == true, + backupCardsCount = when (val status = card.backupStatus) { + is CardDTO.BackupStatus.Active -> status.cardCount + is CardDTO.BackupStatus.CardLinked, + CardDTO.BackupStatus.NoBackup, + null, + -> 0 + }, ), ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt index 5be7b9eb88..b269ee6352 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt @@ -1,15 +1,14 @@ package com.tangem.tap.features.details.ui.common.utils import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.models.scan.CardDTO import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.wallet.R internal fun getResetToFactoryDescription( - backupStatus: CardDTO.BackupStatus?, + isActiveBackupStatus: Boolean, typesResolver: CardTypesResolver, ): TextReference { - return if (backupStatus?.isActive != true || typesResolver.isTangemTwins()) { + return if (!isActiveBackupStatus || typesResolver.isTangemTwins()) { TextReference.Res(R.string.reset_card_without_backup_to_factory_message) } else { TextReference.Res(R.string.reset_card_with_backup_to_factory_message) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt index 2131cd42e2..41d0535147 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -13,7 +13,6 @@ import com.tangem.domain.card.DeleteSavedAccessCodesUseCase import com.tangem.domain.card.ResetCardUseCase import com.tangem.domain.card.ResetCardUserCodeParams import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable import com.tangem.domain.wallets.models.UserWalletId @@ -65,8 +64,15 @@ internal class ResetCardViewModel @Inject constructor( // endregion // region Data of card that was scanned on CardSettings - private val primaryCardId: String - private val primaryBackupStatus: CardDTO.BackupStatus? + private val primaryCardId: String = savedStateHandle.get(AppRoute.ResetToFactory.CARD_ID) + ?: error("CardId must be provided for ResetCardViewModel") + + private val isActiveBackupPrimaryCard = + savedStateHandle.get(AppRoute.ResetToFactory.IS_ACTIVE_BACKUP_STATUS) + ?: error("IsActiveBackupCard must be provided for ResetCardViewModel") + + private val primaryBackupCardsCount = savedStateHandle.get(AppRoute.ResetToFactory.BACKUP_CARDS_COUNT) + ?: error("CardCount must be provided for ResetCardViewModel") // endregion // TODO: move logic to separate domain entity @@ -76,15 +82,6 @@ internal class ResetCardViewModel @Inject constructor( value = getInitialState(), ) - init { - val cardSpecificInfo = savedStateHandle.get(AppRoute.ResetToFactory.CARD_SPECIFIC_DATA) - ?.unbundle(AppRoute.ResetToFactory.CardSpecificInfo.serializer()) - ?: error("CardSpecificData must be provided for ResetCardViewModel") - - primaryCardId = cardSpecificInfo.cardId - primaryBackupStatus = cardSpecificInfo.backupStatus - } - private fun getInitialState(): ResetCardScreenState { val shouldShowResetPasswordButton = shouldShowResetPasswordButton() val warningsToShow = buildList { @@ -98,7 +95,7 @@ internal class ResetCardViewModel @Inject constructor( return ResetCardScreenState( resetButtonEnabled = false, descriptionText = getResetToFactoryDescription( - backupStatus = primaryBackupStatus, + isActiveBackupStatus = isActiveBackupPrimaryCard, typesResolver = currentCardTypesResolver, ), warningsToShow = warningsToShow, @@ -115,7 +112,7 @@ internal class ResetCardViewModel @Inject constructor( private fun shouldShowResetPasswordButton(): Boolean { val isTangemWallet = currentCardTypesResolver.isTangemWallet() || currentCardTypesResolver.isWallet2() - return isTangemWallet && primaryBackupStatus is CardDTO.BackupStatus.Active + return isTangemWallet && isActiveBackupPrimaryCard } private fun toggleFirstCondition(isAccepted: Boolean) { @@ -283,12 +280,6 @@ internal class ResetCardViewModel @Inject constructor( private fun getBackupCardsCount(): Int { if (!currentCardTypesResolver.isMultiwalletAllowed()) return 0 - return when (val status = primaryBackupStatus) { - is CardDTO.BackupStatus.Active -> status.cardCount - is CardDTO.BackupStatus.CardLinked, - is CardDTO.BackupStatus.NoBackup, - null, - -> 0 - } + return primaryBackupCardsCount } } \ No newline at end of file diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 8f9796de2b..8bb8395a7d 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -5,7 +5,6 @@ import com.tangem.common.routing.bundle.RouteBundleParams import com.tangem.common.routing.bundle.bundle import com.tangem.common.routing.entity.SerializableIntent import com.tangem.core.decompose.navigation.Route -import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.tokens.model.CryptoCurrency @@ -145,25 +144,35 @@ sealed class AppRoute(val path: String) : Route { data object AppSettings : AppRoute(path = "/app_settings") /** - * Reset to factory route + * Reset to factory * - * @property userWalletId user wallet id - * @property cardSpecificInfo info about card that was scanned on CardSettings + * @property userWalletId user wallet id + * @property cardId reset card id + * @property isActiveBackupStatus reset backup card status + * @property backupCardsCount backup cards count */ @Serializable data class ResetToFactory( val userWalletId: UserWalletId, - val cardSpecificInfo: CardSpecificInfo, - ) : AppRoute(path = "/reset_to_factory/${userWalletId.stringValue}/$cardSpecificInfo"), RouteBundleParams { + val cardId: String, + val isActiveBackupStatus: Boolean, + val backupCardsCount: Int, + ) : AppRoute( + path = "/reset_to_factory" + + "/${userWalletId.stringValue}" + + "/$cardId" + + "/$isActiveBackupStatus" + + "/$backupCardsCount", + ), + RouteBundleParams { override fun getBundle(): Bundle = bundle(serializer()) - @Serializable - data class CardSpecificInfo(val cardId: String, val backupStatus: CardDTO.BackupStatus?) - companion object { const val USER_WALLET_ID = "userWalletId" - const val CARD_SPECIFIC_DATA = "cardSpecificInfo" + const val CARD_ID = "cardId" + const val IS_ACTIVE_BACKUP_STATUS = "isActiveBackupStatus" + const val BACKUP_CARDS_COUNT = "backupCardsCount" } } diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt index 91ceeb0049..fcce746f21 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt @@ -8,8 +8,6 @@ import com.tangem.common.card.EncryptionMode import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.operations.attestation.Attestation -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable import java.util.Date import com.tangem.common.card.FirmwareVersion as SdkFirmwareVersion @@ -300,19 +298,12 @@ data class CardDTO( } } - @Serializable sealed class BackupStatus { - @Serializable - @SerialName("card_linked") data class CardLinked(val cardCount: Int) : BackupStatus() - @Serializable - @SerialName("active") data class Active(val cardCount: Int) : BackupStatus() - @Serializable - @SerialName("no_backup") data object NoBackup : BackupStatus() val isActive: Boolean diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index a15817a145..3a9681b918 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -341,6 +341,8 @@ internal class WalletViewModel @Inject constructor( } private suspend fun deleteWallet(action: WalletsUpdateActionResolver.Action.DeleteWallet) { + walletScreenContentLoader.cancel(action.deletedWalletId) + walletScreenContentLoader.load( userWallet = action.selectedWallet, clickIntents = clickIntents, From eecf7955cae1a851be373b522055525d5dfb9b8d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Jul 2024 14:39:05 +0300 Subject: [PATCH 17/53] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 8a80aff9f7..8758b045e3 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.utils.popTo import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable @@ -49,7 +48,7 @@ internal class LockUserWalletsTimer( start() if (shouldOpenWelcomeScreenOnResume) { - store.dispatchNavigationAction { popTo() } + store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) } settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false) } } @@ -127,7 +126,7 @@ internal class LockUserWalletsTimer( if (wasApplicationStopped) { settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true) } else { - store.dispatchNavigationAction { popTo() } + store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) } } } } From 31d44e41508b5df6d2be2cc6f0a697597d07b452 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Jul 2024 18:13:54 +0500 Subject: [PATCH 18/53] Updated on 2026-08-14 --- .../features/details/component/impl/DefaultDetailsComponent.kt | 2 +- gradle/dependencies.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/impl/DefaultDetailsComponent.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/impl/DefaultDetailsComponent.kt index 9a9c5575d1..ec33cbbaaf 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/impl/DefaultDetailsComponent.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/impl/DefaultDetailsComponent.kt @@ -24,7 +24,7 @@ internal class DefaultDetailsComponent @AssistedInject constructor( private val model: DetailsModel = getOrCreateModel(params) private val userWalletListComponent = userWalletListComponentFactory.create( - context = child(key = "user_wallet_list"), + context = child(key = "user_wallet_list_component"), ) @Composable diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 66427fc530..74fab9ccbd 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.13-706" +tangemBlockchainSdk = "release-app_5.13-707" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.13-376" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 1e29b39eec4dfb29b6a12db4066cfaf3419a8533 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Jul 2024 17:03:36 +0300 Subject: [PATCH 19/53] Updated on 2026-08-14 --- .../details/redux/DetailsMiddleware.kt | 2 +- .../features/details/utils/UserWalletSaver.kt | 63 ++++++++++++------- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 689263efaf..14a1147b89 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -247,7 +247,7 @@ class DetailsMiddleware { deleteSavedAccessCodes() store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false) - store.dispatchNavigationAction { popTo() } + store.dispatchNavigationAction { replaceAll(AppRoute.Home) } return CompletionResult.Success(Unit) } diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt index 47ae7ed4f7..80a7f98ce1 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt @@ -1,17 +1,22 @@ package com.tangem.features.details.utils -import arrow.core.raise.* -import arrow.core.recover +import androidx.compose.ui.res.stringResource +import arrow.core.raise.Raise +import arrow.core.raise.ensureNotNull +import arrow.core.raise.fold +import arrow.core.raise.recover import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.navigation.popTo import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.components.SimpleOkDialog import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.isNullOrEmpty import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.message.ContentMessage import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.models.scan.ScanResponse @@ -21,7 +26,7 @@ import com.tangem.domain.wallets.models.SaveWalletError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.wallets.usecase.SaveWalletUseCase -import com.tangem.domain.wallets.usecase.SelectWalletUseCase +import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsSyncUseCase import com.tangem.features.details.impl.R import javax.inject.Inject @@ -31,7 +36,7 @@ internal class UserWalletSaver @Inject constructor( private val scanCardProcessor: ScanCardProcessor, private val saveWalletUseCase: SaveWalletUseCase, private val generateWalletNameUseCase: GenerateWalletNameUseCase, - private val selectWalletUseCase: SelectWalletUseCase, + private val shouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase, private val reduxStateHolder: ReduxStateHolder, private val messageSender: UiMessageSender, private val router: Router, @@ -43,8 +48,6 @@ internal class UserWalletSaver @Inject constructor( val userWallet = createUserWallet(response) saveWallet(userWallet) - - router.popTo() }, recover = { error -> val message = error.message @@ -56,28 +59,44 @@ internal class UserWalletSaver @Inject constructor( ) private suspend fun Raise.saveWallet(userWallet: UserWallet) { - saveWalletUseCase(userWallet).recover { error -> - when (error) { - is SaveWalletError.WalletAlreadySaved -> selectUserWallet(userWallet) - is SaveWalletError.DataError -> { - val messageRef = ensureNotNull(error.messageId?.let(::resourceReference)) { - Error.Unknown + fold( + block = { saveWalletUseCase(userWallet).bind() }, + recover = { error -> + when (error) { + is SaveWalletError.WalletAlreadySaved -> { + if (shouldSaveUserWalletsSyncUseCase()) { + selectUserWallet() + } else { + router.popTo() + } } + is SaveWalletError.DataError -> { + val messageRef = ensureNotNull(error.messageId?.let(::resourceReference)) { + Error.Unknown + } - raise(Error.Message(messageRef)) + raise(Error.Message(messageRef)) + } } - } - }.bind() + }, + transform = { + // call only if wallet is successfully saved + reduxStateHolder.onUserWalletSelected(userWallet) - reduxStateHolder.onUserWalletSelected(userWallet) + router.popTo() + }, + ) } - private suspend fun Raise.selectUserWallet(userWallet: UserWallet) { - withError({ Error.Unknown }) { - selectWalletUseCase(userWallet.walletId).bind() - } - - router.popTo() + private fun selectUserWallet() { + messageSender.send( + message = ContentMessage { onDismiss -> + SimpleOkDialog( + message = stringResource(id = R.string.user_wallet_list_error_wallet_already_saved), + onDismissDialog = onDismiss, + ) + }, + ) } private suspend fun Raise.createUserWallet(response: ScanResponse): UserWallet { From 2cd0164d1d51500e873cba7b627b2222b6e43518 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Jul 2024 17:39:43 +0300 Subject: [PATCH 20/53] Updated on 2026-08-14 --- .../viewmodels/ExchangeStatusFactory.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt index e4118e7141..152575c441 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt @@ -13,6 +13,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.feature.swap.domain.SwapTransactionRepository import com.tangem.feature.swap.domain.api.SwapRepository +import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel @@ -108,11 +109,12 @@ internal class ExchangeStatusFactory( async { val statusModel = getExchangeStatus(tx.txId) val isRefundTerminalStatus = statusModel?.refundNetwork == null && - statusModel?.refundContractAddress == null + statusModel?.refundContractAddress == null && + tx.provider.type != ExchangeProviderType.DEX_BRIDGE if (tx.activeStatus.isTerminal(isRefundTerminalStatus)) { tx } else { - val addedRefundToken = addRefundCurrencyIfNeeded(statusModel) + val addedRefundToken = addRefundCurrencyIfNeeded(statusModel, tx.provider.type) swapTransactionsStateConverter.updateTxStatus( tx = tx, statusModel = statusModel, @@ -151,8 +153,15 @@ internal class ExchangeStatusFactory( } } - private suspend fun addRefundCurrencyIfNeeded(status: ExchangeStatusModel?): CryptoCurrency? { + /** + * For now do it only for dex-bridge provider + */ + private suspend fun addRefundCurrencyIfNeeded( + status: ExchangeStatusModel?, + type: ExchangeProviderType, + ): CryptoCurrency? { status ?: return null + if (type != ExchangeProviderType.DEX_BRIDGE) return null val refundNetwork = status.refundNetwork val refundContractAddress = status.refundContractAddress if (refundNetwork != null && refundContractAddress != null) { From 5016ee56eab6c52fc22548ca49dabe10bae7d9cd Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Jul 2024 11:14:52 +0300 Subject: [PATCH 21/53] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 1497 +++++++++-------- core/res/src/main/res/values/strings.xml | 1479 ++++++++-------- .../notifications/CurrencyNotification.kt | 125 ++ .../CurrencyNotificationConfig.kt | 35 + .../components/notifications/Notification.kt | 60 +- .../components/ExchangeStatusNotifications.kt | 83 +- ...enDetailsSwapTransactionsStateConverter.kt | 17 +- .../exchange/ExchangeStatusBottomSheet.kt | 56 +- .../viewmodels/TokenDetailsClickIntents.kt | 2 + .../viewmodels/TokenDetailsViewModel.kt | 4 + 10 files changed, 1798 insertions(+), 1560 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/notifications/CurrencyNotification.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/notifications/CurrencyNotificationConfig.kt diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 3ee8a28d64..1400f9c41a 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1,750 +1,753 @@ - Добавить токен - Валюты - Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. - Обратиться в поддержку - Эта функция недоступна в демонстрационном режиме - Причина: %s - Не могу отправить транзакцию - Выбранный кошелёк не поддерживает сеть %1$s - Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. - Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. - У вас возникли трудности со сканированием карты? - Эта карта не предназначена для работы с этим приложением - Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. - Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem - Включите биометрическую аутентификацию - Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. - При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены из приложения. - Сохранение кода доступа - Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. - Cохранение кошелька - Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. - Тёмная - Светлая - Как в системе - Тема - Настройки приложения - Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" - Больше не показывать - Понятно - Балансы скрыты - Пожалуйста, отсканируйте карту - Пожалуйста, попробуйте снова через 30 секунд или отсканируйте карту - Слишком много попыток - Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона. - Начать резервное копирование - - %d карта - %d карты - %d карт - %d карт - - Отключите эту опцию, если не хотите, чтобы эта карта использовалась для сброса кодов доступа на другие карты этого кошелька. Обратите внимание, сброс кода также не будет доступен на этой карте. - Использовать эту карту для сброса кода доступа на других картах в этом кошельке - Восстановление кода доступа - Сбросить - Вы уверены, что хотите это сделать? - Смена кода доступа - Код доступа будет изменен только на данной карте - Все карты выбранного кошелька сброшены до заводских настроек, вы можете создать новый кошелек - Сброс завершён - Хотите сбросить следующую карту от этого кошелька? - Сброс карты - Рекомендуем завершить процесс сброса всех карт кошелька - Вы сбросили не все карты - Заводские настройки - Тип безопасности - Настройки карты - Помимо сетевой комиссий, сеть Cardano взимает %1$s ADA при транзакции с токеном %2$s - Требования к транзакции Cardano - Чтобы совершить транзакцию %1$s, внесите некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA (рекомендуется 5 ADA) - Недостаточно ADA для транзакции - Вы должны поддерживать некоторое количество ADA, поскольку у вас на балансе есть токены в сети Cardano - Недостаточно ADA - Принять - Доступ запрещен - Применить - Одобрение - Внимание - Баланс: %s - Баланс - биометрическую аутентификацию - биометрией - Купить - Перейти на %1$s - Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. - Отмена - Закрыть - Продолжить - Копировать - Скопировать адрес - Создать - Удалить - Отключено - Готово - Включить - Включено - Ошибка - Обозреватель - Посмотреть историю транзакций - Обозреватель - Комиссия - Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s - Свое - Быстро - По рынку - Медленно - Скорость и комиссия - Получить адреса - К провайдеру - Импортировать - Позже - Заблокирован - Основная сеть - Сетевая комиссия - Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии - Далее - Нет - Нет адреса - OK - Основная карта - Парольная фраза - Вставить - Подробнее - Получить - Отклонить - Перезагрузить - Переименовать - Сохранить изменения - Искать - Поиск токенов - Seed-фраза - Выберите действие - Продать - Отправить - Сервер недоступен, повторите попытку позднее - Поделиться - Подписать - Подписать и отправить - Начать - Отправить - Успешно - Поддержка - Обмен - условия участия - Ошибка транзакции - Транзакции - Перевод - Я понял - Произошла ошибка. Пожалуйста, попробуйте снова. - Недоступно - Да - Адрес контракта скопирован! - Доступные сети - Добавить токен - Адрес контракта - Адрес контракта некорректен - Пожалуйста, выберите сеть - Десятичное число должно быть действительным целым числом, до %li - Своя деривация - Например m/00\'/0000\'/0\'/0/0 - Введите свою деривацию - Знаков после запятой - Путь деривации - По умолчанию - Деривация по BIP44 - Введенный путь деривации некорректен - Например, USD Coin - Название токена - Не выбрано - Сеть - Сеть - Вы можете добавить токен в ручную, если он не поддерживается Tangem - Например, USDC - Символ - Символ токена - Этот токен/сеть уже находится в вашем списке - Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить - Остерегайтесь мошеннических токенов, они могут ничего не стоить - Токены могут быть созданы кем угодно - Чат - Код доступа - Перед сканированием карты вам нужно будет ввести правильный код доступа. - Задержка сканирования - Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. - Пароль - Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. - Реферальная программа - Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы - %s хэшей - Номер карты - Обратиться в поддержку - Добавить еще карты - Валюта приложения - Скрывать балансы жестом переворота - Эмитент - Подписано - Подробности - Проверьте подключение с интернетом или переключитесь на другую сеть - Условия использования - Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. - Мои токены - У вас нет добавленных токенов. Добавьте токены для обмена - Недоступен для обмена с %s - Предоставлено - Статус - Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами - Выберите провайдера - Произошла ошибка. Код: %s - К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) - Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s) - В данный момент обмен невозможен. Попробуйте позже. (Код: %s) - Курс обмена - Обмен через %s - Чтобы вернуть ваши деньги, посетите сайт провайдера - Операция не выполнена провайдером - Посетите сайт провайдера для проверки - Провайдер запрашивает прохождение верификации - Отменен - Подтверждено - Подтверждение - Подтверждение... - Обменяно - Обмен - Обмен... - Неудачно - Депозит получен - Ожидание депозита - Ожидаем пополнения... - Возвращено - Отправляем - Отправка средств... - Отправлено - Данные провайдера. Сумма к получению может измениться в зависимости от рыночных условий. - Статус обмена - Требуется верификация - Ожидание хеша транзакции - Список токенов в вашем кошельке - Получение наилучших курсов... - Плавающая ставка - Пользуясь сервисом, вы соглашаетесь с %s - Пользуясь сервисом, вы соглашаетесь с %1$s и %2$s - Больше провайдеров на подходе.\nСледите за обновлениями! - Политикой конфиденциальности - Провайдер - Лучший курс - Доступно до %s - Доступно с %s - Недоступно для этой пары - Требуется разрешение - Рекомендовано - Условиями использования - Токены не найдены. Пожалуйста, попробуйте другой запрос - ID: %s - ID транзакции скопирован - Информация ниже не является обязательной. Вы можете стереть её, если хотите. - Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. - Скажите, пожалуйста, какая у вас карта? - Привет, команда поддержки, - Пожалуйста, расскажите нам больше о вашей проблеме. Каждая маленькая деталь может помочь. - Мои предложения - Не могу отсканировать карту - Обращение в поддержку - Обращение в поддержку Tangem - Не могу отправить транзакцию - Купить - Сканировать - Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции - Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции - Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции - Приложите карту #%s для сброса - Приложите, чтобы отсканировать - Приложите, чтобы подписать - Приложите карту - Вы обновили данные биометрии, отсканируйте свою карту для входа - Ваш баланс должен быть выше суммы комиссии для осуществления перевода - Недостаточно средств - У вас недостаточно Маны для этой транзакции. Пожалуйста, подождите, пока Мана восполнится. Ваш баланс маны равен %1$s/%2$s - Недостаточно Маны - Вы можете перевести только %s из-за ограничения Mana, установленного сетью Koinos - Лимит маны - Сеть Koinos использует Ману для оплаты комиссии сети. У вас есть %1$s/%2$s Mana - Уровень маны - Чтобы начать отслеживать свои криптоактивы и транзакции, добавьте токены - Управление токенами - Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту - Отсканируйте карту - Обменивайте свои токены с %1$s комиссии провайдера через Changelly с %2$s по %3$s февраля. - Обмен с Changelly, %s комиссии - Токены - Забронировать - Оплатите его криптой и сэкономьте **50 долларов** через нашего партнера Travala: **%1s - %2s** - Забронируйте отпуск с Tangem - Добавить - Изменить - Рыночная капитализация - Основная сеть - Не основной или основной блокчейн, на котором размещен токен - Не основные сети - Выберите сети - Кошелек - Не удалось найти этот токен, вы можете добавить его вручную. - - %1$d из %2$d кошелька - %1$d из %2$d кошельков - %1$d из %2$d кошельков - %1$d из %2$d кошельков - - например Bitcoin - Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление. - Голосовать - Выберите кошелек - Кошелёк не поддерживает более одной сети - Вам необходимо установить единый код доступа для защиты всех ваших карт - Защита - Позже вы сможете установить индивидуальный код доступа для каждой карты - Персонализация - Код доступа можно восстановить с помощью привязанной карты. Не храните все карты в одном месте. - Восстановление - Выберите любое слово, фразу или число в качестве кода доступа - Создайте код доступа - Введите код доступа еще раз, чтобы избежать ошибки - Повторно введите код доступа - Код доступа должен состоять не менее чем из 4 символов. - Введенные коды доступа не совпадают - Необходимо повторить операцию, при этом карта будет сброшена к заводским настройкам - Ошибка активации - Добавление токенов - Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? - Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. - Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. - Добавить резервную карту - Сканировать карту #%d - Создать резервную копию - Сканировать основную карту - Перейти к моему кошельку - Завершение бэкапа - Получить криптовалюту - Сканировать основную карту - Пропустить - Как это работает? - Давайте сгенерируем все ключи на вашей карте и создадим безопасный кошелек - Создать кошелек - Создать кошелек - Другие опции - Ваши ключи будут надежно сгенерированы внутри карты. Никакой seed-фразы, а это значит, что никто не может экспортировать или украсть ее. - Cоздавайте ключи приватно - Ваша карта активирована и готова к использованию - Успешно! - В этом случае вам будет необходимо начать процесс заново. - Вы хотите выйти из процесса активации? - Подготовка - Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Если на нем есть средства, пожалуйста сначала выведите их, а затем сделайте сброс до заводских настроек и используйте как резервную. - Резервная копия - Прочитать о seed-фразе - - - Запишите эти %d слова в порядке, указанном ниже, и сохраните их в надежном месте. - Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. - Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. - - Ваша seed-фраза - - - %d слова - %d слов - %d слов - - Чтобы импортировать кошелек, введите seed-фразу в поле ниже - Создать seed-фразу - Импорт кошелька - Seed-фраза — это набор слов, который дает возможность восстановить кошелек. В отличие от ключей, сгенерированных картой, seed-фраза не защищена и может быть скопирована и украдена. Используйте этот вариант на свой страх и риск. - Использовать seed-фразу - Неверная seed-фраза. Пожалуйста, проверьте порядок слов. - Неверная seed-фраза. Пожалуйста, проверьте орфографию. - Устаревший - Чтобы проверить, правильно ли вы записали seed-фразу, введите 2-е, 7-е и 11-е слова - Итак, проверим - Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. - Вы можете добавить еще одну карту или завершить процесс резервного копирования - Подготовьте резервную карту с номером %s - Отсканируйте основную карту, чтобы начать процесс резервного копирования. - Подготовьте основную карту с номером %s - Ваша карта настроена и готова к использованию. - Добавлено максимальное количество карт. Завершите процесс резервного копирования. - Активация карты - Резервная карта #%d - Нет резервных карт - Добавлена ​​одна резервная карта - Подготовьте свою карту - Добавлены две резервные карты - Пополните кошелек на любую сумму, чтобы начать пользоваться картой - Пополните кошелек более чем на %1$s %2$s, чтобы начать пользоваться картой - Купить криптовалюту - Показать адрес кошелька - Активация кошелька - Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. - Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала - Вы можете сделать резервную копию своих ключей на одной или двух других пустых картах Wallet. - Код доступа можно восстановить с помощью одной из резервных карт. - Все резервные карты являются полнофункциональными и содержат одинаковые ключи. - Вы сможете установить код доступа для защиты своих кошельков. - Резервная копия карты - Восстановление кода доступа - Идентичные карты - Код доступа - Группы - По балансу - Сортировка токенов - Список - Выбрать из галереи - Настройки - Вы не предоставили доступ к вашей камере - Доступ к камере запрещен - %1$s (%2$s) в сети %3$s - Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. - Участвовать - Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. - Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. - Грядущие выплаты - Ваши друзья купили - Меньше - Больше - Нет грядущих выплат - - за %d кошелек - за %d кошелька - за %d кошельков - за %d кошельков - - Получите ^^%1$s^^ на ваш адрес в сети %2$s %3$s ^^спустя 30 дней^^ за каждый кошелек, который купит ваш друг - Вы - Получит - при покупке кошелька на сайте tangem.com - %s скидку - Ваш друг - Персональный код скопирован! - Ваш персональный код - Купи Tangem Wallet со скидкой!\n%s - Приведи друга в Tangem - Вы приняли - Нажимая на эту кнопку, вы принимаете - в реферальной программе - - %d кошелек - %d кошелька - %d кошельков - %d кошельков - - Сбросить карту - Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку - Я понимаю, что не смогу этой картой восстановить пароль на остальных картах этого кошелька, если я его забуду - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать данную карту для восстановления кода доступа. - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек. - У вас есть карта банка другой страны, а также вид на жительство или регистрация вне РФ? - Карты банков РФ в данный момент не принимаются - Войдите в приложение и следите за своим балансом без сканирования карты - Доступ в приложение - Использовать биометрию - Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты - Код доступа - Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков - Включите биометрическую аутентификацию - Вы хотите использовать биометрию? - Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта - Сканировать - Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. - Приготовьте свою карту - Уже содержится во введенном адресе - Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. - Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? - Причина: %1$s\nКод: %2$s - Транзакция не выполнена - Сумма - Вы можете установить комиссию за транзакцию, изменив значение в поле Satoshi per vByte. - Это стоимость, которую вы готовы заплатить за каждую единицу газа. Чем выше цена газа, тем быстрее ваша транзакция будет обработана. (Приоритетная комиссия включена) - Приоритетная комиссия - Комиссия, которую пользователь может заплатить майнерам или валидаторам за ускорение включения его транзакции в блок. - %1$s, %2$s - Адрес - Код назначения - Введите адрес - Адрес совпадает с адресом кошелька - Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение. - Недопустимый Tag. Он не будет добавлен в транзакцию. - Недопустимый Memo. Он не будет добавлен в транзакцию. - Tag - Memo - Включая комиссию - Низкая - Нормальная - Приоритетная - Проверьте своё интернет соединение - Информация о комиссии сети недоступна - Из - Лимит газа - Это максимальное количество газа, которое будет потрачено на выполнение транзакции или контракта. Лимит газа предотвращает неожиданные или неограниченные расходы при выполнении транзакции. - Цена газа - Это стоимость, которую вы готовы заплатить за каждую единицу газа. Чем выше цена газа, тем быстрее ваша транзакция будет обработана. - Всё - Максимальная сумма - Комиссия не превысит - Недопустимый Memo - Покрытие сетевой комиссии - Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса - Недостаточно средств - Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе. - Экзистенциальный депозит - Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. - Установлена высокая комиссия - Ввиду особенности сети %1$s комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %2$s. - Комиссия повышена - Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению - Недопустимая сумма - Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %2$s. - Адрес получателя не активирован. \nПожалуйста, измените сумму отправки, чтобы продолжить. - Сумма отправки не может быть менее %s - Оставить %s - Уменьшить на %s - Уменьшить до %s - Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции - Возможны задержки по транзакции - Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. - Лимит транзакции - Опционально - Пожалуйста, совместите свой QR-код с квадратом, чтобы отсканировать его. Убедитесь, что вы сканируете адрес в сети %s. - Последние - Получатель - Неверный адрес - Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов. - Отправить - Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. - Мои кошельки - Способ измерения комиссии за биткоин-транзакцию. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый виртуальный байт в транзакции. Чем выше число, тем быстрее будет обработана транзакция майнерами. - Сатоши / вбайт - Отправка - Нажмите на любое поле, чтобы изменить его - Отправка %s - Вы отправляете **%1$s**, включая комиссию сети %2$s - Вы отправляете **%1$s** и %2$s - Отправка %s - Всего - %1$s и %2$s будет отправлено - ≈ %1$s (вкл. комиссию: %2$s) - %s будет отправлено - Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время - Неверный адрес - Транзакция отправлена - Забыть кошелек - Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова. - Имя - Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. - Революционный аппаратный кошелек - До трех карт с одним кошельком - Все ключи в безопасности - Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте - Тысячи криптовалют - Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. - Кошелек для каждого - Встречайте Tangem - Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах - Поддержка Web 3.0 - Обменивайте больше токенов по лучшим курсам прямо в вашем кошельке. - Новый провайдер обмена! - В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя - В сумму включена комиссия провайдера сервиса. - Комиссии - Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен. - Подтвердить - Вы отправляете - Дать разрешение - Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму. - Недостаточно средств - Подтвердить - Текущая транзакция - Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена. - Дать разрешение - Укажите лимит доступа к выбранному токену - Количество %s - Чтобы продолжить, вам нужно разрешить смарт-контракту %1$s использовать ваш %2$s - Безлимитно - В процессе - Обменять - Вы получите - Выберите токен - не доступен - Балансы скрыты - Балансы показаны - Отменить - Выбранная операция в данный момент недоступна. Попробуйте позже. - В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями. - У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. - У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. - В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. - Продажа средств станет доступной после завершения транзакции(-ий) в сети %s - Отправка средств станет доступной после завершения транзакции(-ий) в сети %s - В данный момент продажа %s недоступна. Следите за нашими обновлениями. - Сгенерировать XPUB - Скрыть - Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. - Скрыть %s - Скрыть токен - Стейкинг позволяет вам зарабатывать %1$s и получать вознаграждения каждые %2$s дней - Зарабатывайте до %s вознаграждений за стейкинг ежегодно - %1$s токен в сети %%image%% %2$s - Токен в сети %%image%% %1$s - Токен %1$s (%2$s) является основной валютой в сети %3$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети - Невозможно скрыть %s - Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля. - Обмен с Changelly, %s комиссии - Обменять - контракт: %s - У вас еще нет транзакций - Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию. - Несколько адресов - История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе. - Операция - от: %s - на: %s - Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d - Вы отсканировали не ту twin-карту. Пожалуйста, попробуйте отсканировать другую - Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. - Один кошелек. Две карты. - Сканировать карту #%s - Создание кошелька - Отсканируйте twin-карту #%s - Подготовка карты - Tangem Twin - Это действие необратимо. У вас не будет доступа к старому кошельку. - Приложите twin-карту с номером %s и не убирайте до окончания операции - Используйте %s или отсканируйте карту, чтобы получить доступ к своему кошельку - Добавить новый кошелек - Вы уверены, что хотите удалить этот кошелек? - Произошла ошибка, пожалуйста, отсканируйте свою карту для входа - Этот кошелек уже был сохранен, вы можете добавить другой - Кошелек с именем %s уже существует - Имя кошелька - Переименование кошелька - Разблокировать все - Разблокировать все с %s - Блокчейн недоступен. Попробуйте позже. - Отсканируйте карту - Запрос на подпись сообщения.\n\n%s - Dapp %1$s, запрос на\nподпись транзакции с BNB.\n\n%2$s - Торговый ордер на %1$s\nЦена: %2$s\nСумма к получению: %3$s\nСумма к оплате: %4$s - Детали транзакции:\nОт: %1$s\nК: %2$s\nСумма: %3$s - Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код - Запрос на создание транзакции для %1$s\n%2$s\n\nСумма: %3$s\nКомиссия: %4$s\nВсего: %5$s\nБаланс: %6$s - Невозможно отправить транзакцию. Недостаточно средств. - Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. - Не все токены добавлены в ваш список. Пожалуйста, добавьте их в начале, а потом попробуйте снова. Недостающие токены: \n - Не удалось подписать сообщение.\nПожалуйста, попробуйте еще раз - Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже. - Запрос на подключение через WalletConnect содержит неподдерживаемые блокчеины. Неподдерживаемые блокчеины:\n - Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. - Произошла непредвиденная ошибка. Сообщение ошибки: %s Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. - Неверная карта выбрана в приложении Tangem - Не удалось создать транзакцию из данных Dapp. Код: %s - Произошла непредвиденная ошибка. Код ошибки: %d Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. - Нет открытых сессий WalletConnect - Упс. Нет сессий. - Не удалось создать пару WalletConnect: %1$s - Вставить из буфера обмена - Сообщение для %1$s:\n%2$s - Запрос на открытие сессии для\n%1$s\n\nСЕТЬ: %2$s\n\nURL: %3$s - Операция не может быть завершена.\n\nВы уже установили сеанс WalletConnect с этими параметрами. - Сканировать новый код - Эту карту нельзя использовать с WalletConnect. - Сеть не поддерживается. Пожалуйста, выберите другую сеть. - Выберите сеть - Сессии WalletConnect - Подключение к dApps - WalletConnect - Рыночная цена %s - за 24 часа - Сеть %s - Адрес скопирован в буфер обмена - Нет соединения с интернетом - Настройки кошелька - Tangem - Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку - Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян. - Ошибка активации - По решению разработчиков сети BNB стандарт BEP-2 перестанет поддерживаться в июне 2024 года. Чтобы не потерять активы, их необходимо преобразовать в стандарт BEP-20. Используйте функцию обмена в приложении или сторонние сервисы, чтобы перевести средства в cеть BNB Smart Chain. - Отключение сети BNB Beacon Chain - Можно лучше - Нравится - Понятно! - Очень круто! - Обновить - Вы находитесь в режиме демо - Демо режим включен - Отсканированная вами карта является картой разработчика. Не используйте ее для создания своего кошелька. - Не для пользователя! - Cеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета будет ниже %2$s, то он будет деактивирован, а средства на счете уничтожены. - Для работы с сетью необходим депозит - Обмен будет доступен после завершения %s транзакции - У вас есть активная транзакция - Разрешение обмена в процессе и будет скоро завершено - Разрешение в процессе - Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s. - У вас в списке нет монет доступных для обмена с %s - Нет доступных для обмена токенов - Чтобы совершить транзакцию, вам необходимо внести немного %1$s %2$s - Невозможно покрыть комиссию %s - Сумма получения не может быть менее %s - Cервис временно недоступен - Сумма для обмена должна быть не более %s - Сумма для обмена должна быть не менее %s - Пожалуйста, измените сумму для обмена - Возможно, данная карта - образец или подделка - Ошибка проверки подлинности - Ассоциировать - Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s - Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять - Ассоциируете свой токен - Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена - На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. - Малое количество подписей - Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. - - Используйте вашу карту, чтобы получить адрес для %d сети - Используйте вашу карту, чтобы получить адреса для %d сетей - Используйте вашу карту, чтобы получить адреса для %d сетей - Используйте вашу карту, чтобы получить адреса для %d сетей - - Некоторые адреса отсутствуют - В данный момент сеть недоступна. Пожалуйста, попробуйте позже. - Сеть недоступна - Пополните ваш кошелек - Ваш кошелек не имеет резервной копии. Проведите эту процедуру сейчас, чтобы защитить ваши активы. - Резервная копия отсутствует - Эта карта ранее использовалась для подписи транзакций. Если она получена от ненадежного источника, рассмотрите возможность вывода своих средств. Если это ваша карта, дополнительных действий не требуется. - Карта уже подписывала транзакции - Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше - Нравится Tangem? - Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его - Необходима плата за аренду сети - %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. - Недостаточно %1$s для оплаты комиссии сети - Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку. - Оповещение сети Солана - Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. - Некоторые сети в настоящее время недоступны. Пожалуйста, повторите попытку позже. - Некоторые сети недоступны - Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки. - Только для целей тестирования - Отказаться - Вы не закончили резервное копирование. Хотите продолжить? - Да, возобновить - Отказаться - Если сейчас отказаться, то придётся сбрасывать карты до заводских настроек, чтобы начать заново - Возобновить резервное копирование - Это необратимое действие - Войти с %s - Сканировать карту - Используйте %s или отсканируйте карту для входа в приложение - C возвращением! + Добавить токен + Валюты + Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. + Обратиться в поддержку + Эта функция недоступна в демонстрационном режиме + Причина: %s + Не могу отправить транзакцию + Выбранный кошелёк не поддерживает сеть %1$s + Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. + Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. + У вас возникли трудности со сканированием карты? + Эта карта не предназначена для работы с этим приложением + Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. + Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem + Включите биометрическую аутентификацию + Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. + При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены из приложения. + Сохранение кода доступа + Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. + Cохранение кошелька + Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. + Тёмная + Светлая + Как в системе + Тема + Настройки приложения + Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" + Больше не показывать + Понятно + Балансы скрыты + Пожалуйста, отсканируйте карту + Пожалуйста, попробуйте снова через 30 секунд или отсканируйте карту + Слишком много попыток + Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона. + Начать резервное копирование + + %d карта + %d карты + %d карт + %d карт + + Отключите эту опцию, если не хотите, чтобы эта карта использовалась для сброса кодов доступа на другие карты этого кошелька. Обратите внимание, сброс кода также не будет доступен на этой карте. + Использовать эту карту для сброса кода доступа на других картах в этом кошельке + Восстановление кода доступа + Сбросить + Вы уверены, что хотите это сделать? + Смена кода доступа + Код доступа будет изменен только на данной карте + Все карты выбранного кошелька сброшены до заводских настроек, вы можете создать новый кошелек + Сброс завершён + Хотите сбросить следующую карту от этого кошелька? + Сброс карты + Рекомендуем завершить процесс сброса всех карт кошелька + Вы сбросили не все карты + Заводские настройки + Тип безопасности + Настройки карты + Помимо сетевой комиссий, сеть Cardano взимает %1$s ADA при транзакции с токеном %2$s + Требования к транзакции Cardano + Чтобы совершить транзакцию %1$s, внесите некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA (рекомендуется 5 ADA) + Недостаточно ADA для транзакции + Вы должны поддерживать некоторое количество ADA, поскольку у вас на балансе есть токены в сети Cardano + Недостаточно ADA + Принять + Доступ запрещен + Применить + Одобрение + Внимание + Баланс: %s + Баланс + биометрическую аутентификацию + биометрией + Купить + Перейти на %1$s + Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. + Отмена + Закрыть + Продолжить + Копировать + Скопировать адрес + Создать + Удалить + Отключено + Готово + Включить + Включено + Ошибка + Обозреватель + Посмотреть историю транзакций + Обозреватель + Комиссия + Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s + Свое + Быстро + По рынку + Медленно + Скорость и комиссия + Получить адреса + К провайдеру + Перейти в токен + Импортировать + Позже + Заблокирован + Основная сеть + Сетевая комиссия + Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии + Далее + Нет + Нет адреса + OK + Основная карта + Парольная фраза + Вставить + Подробнее + Получить + Отклонить + Перезагрузить + Переименовать + Сохранить изменения + Искать + Поиск токенов + Seed-фраза + Выберите действие + Продать + Отправить + Сервер недоступен, повторите попытку позднее + Поделиться + Подписать + Подписать и отправить + Начать + Отправить + Успешно + Поддержка + Обмен + условия участия + Ошибка транзакции + Транзакции + Перевод + Я понял + Произошла ошибка. Пожалуйста, попробуйте снова. + Недоступно + Да + Адрес контракта скопирован! + Доступные сети + Добавить токен + Адрес контракта + Адрес контракта некорректен + Пожалуйста, выберите сеть + Десятичное число должно быть действительным целым числом, до %li + Своя деривация + Например m/00\'/0000\'/0\'/0/0 + Введите свою деривацию + Знаков после запятой + Путь деривации + По умолчанию + Деривация по BIP44 + Введенный путь деривации некорректен + Например, USD Coin + Название токена + Не выбрано + Сеть + Сеть + Вы можете добавить токен в ручную, если он не поддерживается Tangem + Например, USDC + Символ + Символ токена + Этот токен/сеть уже находится в вашем списке + Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить + Остерегайтесь мошеннических токенов, они могут ничего не стоить + Токены могут быть созданы кем угодно + Чат + Код доступа + Перед сканированием карты вам нужно будет ввести правильный код доступа. + Задержка сканирования + Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. + Пароль + Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. + Реферальная программа + Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы + %s хэшей + Номер карты + Обратиться в поддержку + Добавить еще карты + Валюта приложения + Скрывать балансы жестом переворота + Эмитент + Подписано + Подробности + Проверьте подключение с интернетом или переключитесь на другую сеть + Условия использования + Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. + Мои токены + У вас нет добавленных токенов. Добавьте токены для обмена + Недоступен для обмена с %s + Предоставлено + Статус + Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами + Выберите провайдера + Произошла ошибка. Код: %s + К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) + Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s) + В данный момент обмен невозможен. Попробуйте позже. (Код: %s) + Курс обмена + Обмен через %s + Чтобы вернуть ваши деньги, посетите сайт провайдера + Операция не выполнена провайдером + Отправленные средства были возвращены в %1$s на ваш кошелек в соответствии с правилами OKX или моста обмена. %2$s + Сумма была возвращена в %1$s (%2$s сети) + Посетите сайт провайдера для проверки + Провайдер запрашивает прохождение верификации + Отменен + Подтверждено + Подтверждение + Подтверждение... + Обменяно + Обмен + Обмен... + Неудачно + Депозит получен + Ожидание депозита + Ожидаем пополнения... + Возвращено + Отправляем + Отправка средств... + Отправлено + Данные провайдера. Сумма к получению может измениться в зависимости от рыночных условий. + Статус обмена + Требуется верификация + Ожидание хеша транзакции + Список токенов в вашем кошельке + Получение наилучших курсов... + Плавающая ставка + Пользуясь сервисом, вы соглашаетесь с %s + Пользуясь сервисом, вы соглашаетесь с %1$s и %2$s + Больше провайдеров на подходе.\nСледите за обновлениями! + Политикой конфиденциальности + Провайдер + Лучший курс + Доступно до %s + Доступно с %s + Недоступно для этой пары + Требуется разрешение + Рекомендовано + Условиями использования + Токены не найдены. Пожалуйста, попробуйте другой запрос + ID: %s + ID транзакции скопирован + Информация ниже не является обязательной. Вы можете стереть её, если хотите. + Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. + Скажите, пожалуйста, какая у вас карта? + Привет, команда поддержки, + Пожалуйста, расскажите нам больше о вашей проблеме. Каждая маленькая деталь может помочь. + Мои предложения + Не могу отсканировать карту + Обращение в поддержку + Обращение в поддержку Tangem + Не могу отправить транзакцию + Купить + Сканировать + Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции + Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции + Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции + Приложите карту #%s для сброса + Приложите, чтобы отсканировать + Приложите, чтобы подписать + Приложите карту + Вы обновили данные биометрии, отсканируйте свою карту для входа + Ваш баланс должен быть выше суммы комиссии для осуществления перевода + Недостаточно средств + У вас недостаточно Маны для этой транзакции. Пожалуйста, подождите, пока Мана восполнится. Ваш баланс маны равен %1$s/%2$s + Недостаточно Маны + Вы можете перевести только %s из-за ограничения Mana, установленного сетью Koinos + Лимит маны + Сеть Koinos использует Ману для оплаты комиссии сети. У вас есть %1$s/%2$s Mana + Уровень маны + Чтобы начать отслеживать свои криптоактивы и транзакции, добавьте токены + Управление токенами + Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту + Отсканируйте карту + Обменивайте свои токены с %1$s комиссии провайдера через Changelly с %2$s по %3$s февраля. + Обмен с Changelly, %s комиссии + Токены + Забронировать + Оплатите его криптой и сэкономьте **50 долларов** через нашего партнера Travala: **%1s - %2s** + Забронируйте отпуск с Tangem + Добавить + Изменить + Рыночная капитализация + Основная сеть + Не основной или основной блокчейн, на котором размещен токен + Не основные сети + Выберите сети + Кошелек + Не удалось найти этот токен, вы можете добавить его вручную. + + %1$d из %2$d кошелька + %1$d из %2$d кошельков + %1$d из %2$d кошельков + %1$d из %2$d кошельков + + например Bitcoin + Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление. + Голосовать + Выберите кошелек + Кошелёк не поддерживает более одной сети + Вам необходимо установить единый код доступа для защиты всех ваших карт + Защита + Позже вы сможете установить индивидуальный код доступа для каждой карты + Персонализация + Код доступа можно восстановить с помощью привязанной карты. Не храните все карты в одном месте. + Восстановление + Выберите любое слово, фразу или число в качестве кода доступа + Создайте код доступа + Введите код доступа еще раз, чтобы избежать ошибки + Повторно введите код доступа + Код доступа должен состоять не менее чем из 4 символов. + Введенные коды доступа не совпадают + Необходимо повторить операцию, при этом карта будет сброшена к заводским настройкам + Ошибка активации + Добавление токенов + Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? + Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. + Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. + Добавить резервную карту + Сканировать карту #%d + Создать резервную копию + Сканировать основную карту + Перейти к моему кошельку + Завершение бэкапа + Получить криптовалюту + Сканировать основную карту + Пропустить + Как это работает? + Давайте сгенерируем все ключи на вашей карте и создадим безопасный кошелек + Создать кошелек + Создать кошелек + Другие опции + Ваши ключи будут надежно сгенерированы внутри карты. Никакой seed-фразы, а это значит, что никто не может экспортировать или украсть ее. + Cоздавайте ключи приватно + Ваша карта активирована и готова к использованию + Успешно! + В этом случае вам будет необходимо начать процесс заново. + Вы хотите выйти из процесса активации? + Подготовка + Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Если на нем есть средства, пожалуйста сначала выведите их, а затем сделайте сброс до заводских настроек и используйте как резервную. + Резервная копия + Прочитать о seed-фразе + + + Запишите эти %d слова в порядке, указанном ниже, и сохраните их в надежном месте. + Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. + Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. + + Ваша seed-фраза + + + %d слова + %d слов + %d слов + + Чтобы импортировать кошелек, введите seed-фразу в поле ниже + Создать seed-фразу + Импорт кошелька + Seed-фраза — это набор слов, который дает возможность восстановить кошелек. В отличие от ключей, сгенерированных картой, seed-фраза не защищена и может быть скопирована и украдена. Используйте этот вариант на свой страх и риск. + Использовать seed-фразу + Неверная seed-фраза. Пожалуйста, проверьте порядок слов. + Неверная seed-фраза. Пожалуйста, проверьте орфографию. + Устаревший + Чтобы проверить, правильно ли вы записали seed-фразу, введите 2-е, 7-е и 11-е слова + Итак, проверим + Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. + Вы можете добавить еще одну карту или завершить процесс резервного копирования + Подготовьте резервную карту с номером %s + Отсканируйте основную карту, чтобы начать процесс резервного копирования. + Подготовьте основную карту с номером %s + Ваша карта настроена и готова к использованию. + Добавлено максимальное количество карт. Завершите процесс резервного копирования. + Активация карты + Резервная карта #%d + Нет резервных карт + Добавлена ​​одна резервная карта + Подготовьте свою карту + Добавлены две резервные карты + Пополните кошелек на любую сумму, чтобы начать пользоваться картой + Пополните кошелек более чем на %1$s %2$s, чтобы начать пользоваться картой + Купить криптовалюту + Показать адрес кошелька + Активация кошелька + Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. + Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала + Вы можете сделать резервную копию своих ключей на одной или двух других пустых картах Wallet. + Код доступа можно восстановить с помощью одной из резервных карт. + Все резервные карты являются полнофункциональными и содержат одинаковые ключи. + Вы сможете установить код доступа для защиты своих кошельков. + Резервная копия карты + Восстановление кода доступа + Идентичные карты + Код доступа + Группы + По балансу + Сортировка токенов + Список + Выбрать из галереи + Настройки + Вы не предоставили доступ к вашей камере + Доступ к камере запрещен + %1$s (%2$s) в сети %3$s + Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. + Участвовать + Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. + Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. + Грядущие выплаты + Ваши друзья купили + Меньше + Больше + Нет грядущих выплат + + за %d кошелек + за %d кошелька + за %d кошельков + за %d кошельков + + Получите ^^%1$s^^ на ваш адрес в сети %2$s %3$s ^^спустя 30 дней^^ за каждый кошелек, который купит ваш друг + Вы + Получит + при покупке кошелька на сайте tangem.com + %s скидку + Ваш друг + Персональный код скопирован! + Ваш персональный код + Купи Tangem Wallet со скидкой!\n%s + Приведи друга в Tangem + Вы приняли + Нажимая на эту кнопку, вы принимаете + в реферальной программе + + %d кошелек + %d кошелька + %d кошельков + %d кошельков + + Сбросить карту + Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку + Я понимаю, что не смогу этой картой восстановить пароль на остальных картах этого кошелька, если я его забуду + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать данную карту для восстановления кода доступа. + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек. + У вас есть карта банка другой страны, а также вид на жительство или регистрация вне РФ? + Карты банков РФ в данный момент не принимаются + Войдите в приложение и следите за своим балансом без сканирования карты + Доступ в приложение + Использовать биометрию + Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты + Код доступа + Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков + Включите биометрическую аутентификацию + Вы хотите использовать биометрию? + Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта + Сканировать + Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. + Приготовьте свою карту + Уже содержится во введенном адресе + Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. + Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? + Причина: %1$s\nКод: %2$s + Транзакция не выполнена + Сумма + Вы можете установить комиссию за транзакцию, изменив значение в поле Satoshi per vByte. + Это стоимость, которую вы готовы заплатить за каждую единицу газа. Чем выше цена газа, тем быстрее ваша транзакция будет обработана. (Приоритетная комиссия включена) + Приоритетная комиссия + Комиссия, которую пользователь может заплатить майнерам или валидаторам за ускорение включения его транзакции в блок. + %1$s, %2$s + Адрес + Код назначения + Введите адрес + Адрес совпадает с адресом кошелька + Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение. + Недопустимый Tag. Он не будет добавлен в транзакцию. + Недопустимый Memo. Он не будет добавлен в транзакцию. + Tag + Memo + Включая комиссию + Низкая + Нормальная + Приоритетная + Проверьте своё интернет соединение + Информация о комиссии сети недоступна + Из + Лимит газа + Это максимальное количество газа, которое будет потрачено на выполнение транзакции или контракта. Лимит газа предотвращает неожиданные или неограниченные расходы при выполнении транзакции. + Цена газа + Это стоимость, которую вы готовы заплатить за каждую единицу газа. Чем выше цена газа, тем быстрее ваша транзакция будет обработана. + Всё + Максимальная сумма + Комиссия не превысит + Недопустимый Memo + Покрытие сетевой комиссии + Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса + Недостаточно средств + Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе. + Экзистенциальный депозит + Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. + Установлена высокая комиссия + Ввиду особенности сети %1$s комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %2$s. + Комиссия повышена + Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению + Недопустимая сумма + Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %2$s. + Адрес получателя не активирован. \nПожалуйста, измените сумму отправки, чтобы продолжить. + Сумма отправки не может быть менее %s + Оставить %s + Уменьшить на %s + Уменьшить до %s + Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции + Возможны задержки по транзакции + Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. + Лимит транзакции + Опционально + Пожалуйста, совместите свой QR-код с квадратом, чтобы отсканировать его. Убедитесь, что вы сканируете адрес в сети %s. + Последние + Получатель + Неверный адрес + Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов. + Отправить + Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. + Мои кошельки + Способ измерения комиссии за биткоин-транзакцию. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый виртуальный байт в транзакции. Чем выше число, тем быстрее будет обработана транзакция майнерами. + Сатоши / вбайт + Отправка + Нажмите на любое поле, чтобы изменить его + Отправка %s + Вы отправляете **%1$s**, включая комиссию сети %2$s + Вы отправляете **%1$s** и %2$s + Отправка %s + Всего + %1$s и %2$s будет отправлено + ≈ %1$s (вкл. комиссию: %2$s) + %s будет отправлено + Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время + Неверный адрес + Транзакция отправлена + Забыть кошелек + Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова. + Имя + Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. + Революционный аппаратный кошелек + До трех карт с одним кошельком + Все ключи в безопасности + Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте + Тысячи криптовалют + Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. + Кошелек для каждого + Встречайте Tangem + Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах + Поддержка Web 3.0 + Обменивайте больше токенов по лучшим курсам прямо в вашем кошельке. + Новый провайдер обмена! + В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя + В сумму включена комиссия провайдера сервиса. + Комиссии + Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен. + Подтвердить + Вы отправляете + Дать разрешение + Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму. + Недостаточно средств + Подтвердить + Текущая транзакция + Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена. + Дать разрешение + Укажите лимит доступа к выбранному токену + Количество %s + Чтобы продолжить, вам нужно разрешить смарт-контракту %1$s использовать ваш %2$s + Безлимитно + В процессе + Обменять + Вы получите + Выберите токен + не доступен + Балансы скрыты + Балансы показаны + Отменить + Выбранная операция в данный момент недоступна. Попробуйте позже. + В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями. + У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. + У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. + В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. + Продажа средств станет доступной после завершения транзакции(-ий) в сети %s + Отправка средств станет доступной после завершения транзакции(-ий) в сети %s + В данный момент продажа %s недоступна. Следите за нашими обновлениями. + Сгенерировать XPUB + Скрыть + Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. + Скрыть %s + Скрыть токен + Стейкинг позволяет вам зарабатывать %1$s и получать вознаграждения каждые %2$s дней + Зарабатывайте до %s вознаграждений за стейкинг ежегодно + %1$s токен в сети %%image%% %2$s + Токен в сети %%image%% %1$s + Токен %1$s (%2$s) является основной валютой в сети %3$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети + Невозможно скрыть %s + Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля. + Обмен с Changelly, %s комиссии + Обменять + контракт: %s + У вас еще нет транзакций + Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию. + Несколько адресов + История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе. + Операция + от: %s + на: %s + Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d + Вы отсканировали не ту twin-карту. Пожалуйста, попробуйте отсканировать другую + Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. + Один кошелек. Две карты. + Сканировать карту #%s + Создание кошелька + Отсканируйте twin-карту #%s + Подготовка карты + Tangem Twin + Это действие необратимо. У вас не будет доступа к старому кошельку. + Приложите twin-карту с номером %s и не убирайте до окончания операции + Используйте %s или отсканируйте карту, чтобы получить доступ к своему кошельку + Добавить новый кошелек + Вы уверены, что хотите удалить этот кошелек? + Произошла ошибка, пожалуйста, отсканируйте свою карту для входа + Этот кошелек уже был сохранен, вы можете добавить другой + Кошелек с именем %s уже существует + Имя кошелька + Переименование кошелька + Разблокировать все + Разблокировать все с %s + Блокчейн недоступен. Попробуйте позже. + Отсканируйте карту + Запрос на подпись сообщения.\n\n%s + Dapp %1$s, запрос на\nподпись транзакции с BNB.\n\n%2$s + Торговый ордер на %1$s\nЦена: %2$s\nСумма к получению: %3$s\nСумма к оплате: %4$s + Детали транзакции:\nОт: %1$s\nК: %2$s\nСумма: %3$s + Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код + Запрос на создание транзакции для %1$s\n%2$s\n\nСумма: %3$s\nКомиссия: %4$s\nВсего: %5$s\nБаланс: %6$s + Невозможно отправить транзакцию. Недостаточно средств. + Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. + Не все токены добавлены в ваш список. Пожалуйста, добавьте их в начале, а потом попробуйте снова. Недостающие токены: \n + Не удалось подписать сообщение.\nПожалуйста, попробуйте еще раз + Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже. + Запрос на подключение через WalletConnect содержит неподдерживаемые блокчеины. Неподдерживаемые блокчеины:\n + Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. + Произошла непредвиденная ошибка. Сообщение ошибки: %s Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. + Неверная карта выбрана в приложении Tangem + Не удалось создать транзакцию из данных Dapp. Код: %s + Произошла непредвиденная ошибка. Код ошибки: %d Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. + Нет открытых сессий WalletConnect + Упс. Нет сессий. + Не удалось создать пару WalletConnect: %1$s + Вставить из буфера обмена + Сообщение для %1$s:\n%2$s + Запрос на открытие сессии для\n%1$s\n\nСЕТЬ: %2$s\n\nURL: %3$s + Операция не может быть завершена.\n\nВы уже установили сеанс WalletConnect с этими параметрами. + Сканировать новый код + Эту карту нельзя использовать с WalletConnect. + Сеть не поддерживается. Пожалуйста, выберите другую сеть. + Выберите сеть + Сессии WalletConnect + Подключение к dApps + WalletConnect + Рыночная цена %s + за 24 часа + Сеть %s + Адрес скопирован в буфер обмена + Нет соединения с интернетом + Настройки кошелька + Tangem + Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку + Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян. + Ошибка активации + По решению разработчиков сети BNB стандарт BEP-2 перестанет поддерживаться в июне 2024 года. Чтобы не потерять активы, их необходимо преобразовать в стандарт BEP-20. Используйте функцию обмена в приложении или сторонние сервисы, чтобы перевести средства в cеть BNB Smart Chain. + Отключение сети BNB Beacon Chain + Можно лучше + Нравится + Понятно! + Очень круто! + Обновить + Вы находитесь в режиме демо + Демо режим включен + Отсканированная вами карта является картой разработчика. Не используйте ее для создания своего кошелька. + Не для пользователя! + Cеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета будет ниже %2$s, то он будет деактивирован, а средства на счете уничтожены. + Для работы с сетью необходим депозит + Обмен будет доступен после завершения %s транзакции + У вас есть активная транзакция + Разрешение обмена в процессе и будет скоро завершено + Разрешение в процессе + Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s. + У вас в списке нет монет доступных для обмена с %s + Нет доступных для обмена токенов + Чтобы совершить транзакцию, вам необходимо внести немного %1$s %2$s + Невозможно покрыть комиссию %s + Сумма получения не может быть менее %s + Cервис временно недоступен + Сумма для обмена должна быть не более %s + Сумма для обмена должна быть не менее %s + Пожалуйста, измените сумму для обмена + Возможно, данная карта - образец или подделка + Ошибка проверки подлинности + Ассоциировать + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять + Ассоциируете свой токен + Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена + На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. + Малое количество подписей + Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. + + Используйте вашу карту, чтобы получить адрес для %d сети + Используйте вашу карту, чтобы получить адреса для %d сетей + Используйте вашу карту, чтобы получить адреса для %d сетей + Используйте вашу карту, чтобы получить адреса для %d сетей + + Некоторые адреса отсутствуют + В данный момент сеть недоступна. Пожалуйста, попробуйте позже. + Сеть недоступна + Пополните ваш кошелек + Ваш кошелек не имеет резервной копии. Проведите эту процедуру сейчас, чтобы защитить ваши активы. + Резервная копия отсутствует + Эта карта ранее использовалась для подписи транзакций. Если она получена от ненадежного источника, рассмотрите возможность вывода своих средств. Если это ваша карта, дополнительных действий не требуется. + Карта уже подписывала транзакции + Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше + Нравится Tangem? + Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его + Необходима плата за аренду сети + %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. + Недостаточно %1$s для оплаты комиссии сети + Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку. + Оповещение сети Солана + Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. + Некоторые сети в настоящее время недоступны. Пожалуйста, повторите попытку позже. + Некоторые сети недоступны + Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки. + Только для целей тестирования + Отказаться + Вы не закончили резервное копирование. Хотите продолжить? + Да, возобновить + Отказаться + Если сейчас отказаться, то придётся сбрасывать карты до заводских настроек, чтобы начать заново + Возобновить резервное копирование + Это необратимое действие + Войти с %s + Сканировать карту + Используйте %s или отсканируйте карту для входа в приложение + C возвращением! diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index ffdb3de5f8..28ae458dd2 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1,741 +1,744 @@ - Add custom token - Manage tokens - Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. - Request support - This feature is disabled in Demo mode - Reason: %s - Can\'t send a transaction - The selected does not support the %1$s network - To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. - Tokens in %1$s network are not supported by this card due to firmware limitation. - Are you having difficulty scanning your card? - This card is not designed to work with this app - Default Fee - Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary. - Go to settings to enable biometric authentication in the Tangem App - Enable biometric authentication - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - Removing the saved card deletes all the saved wallets and their access codes from the app. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Dark - Light - System default - Theme - App settings - To hide or show your balances, simply flip your device screen down, or switch it off in Settings - Don\'t show again - Got it - Balances are hidden - Please scan the card - Please try again in 30 seconds or scan the card - Too many attempts - You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings. - Start backup process - - %d card - %d cards - - Disable this option if you don\'t want this card to be used to reset access codes on other cards in this wallet. Please note that this will also prevent you from resetting the access code on this card. - Allows you to use this card to reset access code on other cards in this wallet - Access code recovery - Reset - Are you sure you want to do this? - Change Access Code - Access code will be changed on this card only - All cards in the selected wallet have been reset to factory settings. You can now create a new wallet. - Reset complete - Do you want to reset the next card in this wallet? - Card reset - We recommend completing the reset process for all cards in this wallet - You haven\'t reset all your cards - Reset to Factory Settings - Security Mode - Card settings - In addition to network fee, the Cardano network charges %1$s ADA when transacting with the %2$s token - Cardano transaction requirements - To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value (5 ADA recommended) - Insufficient ADA for token transfer - You must maintain some ADA because you have some tokens on the Cardano blockchain - Not enough ADA - Accept - Access denied - Apply - Approval - Attention - Balance: %s - Balance - biometric authentication - biometrics - Buy - Go to %1$s - You have not given access to your camera, please adjust your privacy settings - Cancel - Close - Continue - Copy - Copy address - Create - Delete - Disabled - Done - Enable - Enabled - Error - Explore - Explore transaction history - Explorer - Fee - Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s - Custom - Fast - Market - Slow - Speed and fee - Get addresses - Go to provider - Import - Later - Locked - Main network - Network fee - Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level - Next - No - No address - OK - Primary Card - Passphrase - Paste - Read more - Receive - Reject - Reload - Rename - Save changes - Search - Search tokens - Seed phrase - Select action - Sell - Send - The server is not available, please try again later - Share - Sign - Sign and send - Start - Submit - Success - Support - Swap - terms and conditions - Transaction failed - Transactions - Transfer - I understand - There was an error. Please try again. - Unreachable - Yes - Contract address copied! - Available networks - Add token - Contract address - Contract address is invalid - Please select the network - Decimal must be a valid integer, up to %li - Custom derivation - E. g. m/00\'/0000\'/0\'/0/0 - Enter custom derivation - Decimals - Derivation Path - Default - BIP44 coin type - The derivation path you\'ve entered is not valid - E.g. USD Coin - Name - Not selected - Network - Token network - You can manually add a token that is not natively supported by Tangem - E.g. USDC - Symbol - Token symbol - This token/network has already been added to your list - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - Be aware of adding scam tokens, they can cost nothing - Note that tokens can be created by anyone - Chat - Access code - You will have to submit the correct access code before scanning the card - Long Tap - This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. - Passcode - Before executing any command entailing a change of the card state, you will have to enter the passcode. - Referral program - Flip your device screen down to quickly hide and show balances - %s hashes - Card ID - Contact support - Link More Cards - App Currency - Flip-to-Hide Balances - Issuer - Signed - Details - Check your internet connection or switch to a different network - Terms of service - You have used a card from another wallet. Tap the card associated with this wallet - My tokens - You haven\'t added any tokens yet. Add tokens via Market to swap - Cannot be swapped for %s - Provided by - Status - Tangem offers token swaps via 3rd-party providers according to each provider\'s terms - Choose provider - An error occurred. Code: %s - Oops! Swapping the selected pair through the chosen provider is temporarily unavailable. Please try again later. (Code: %s) - Selected provider is unavailable at the moment. Please try again later. (Code: %s) - Swaps are unavailable at the moment. Please try again later. (Code: %s) - Estimated amount - Exchange by %s - Visit provider’s website to refund your money - Operation failed by provider - Visit provider’s website for verification - KYC verification required by provider - Canceled - Confirmed - Confirming - Confirming... - Exchanged - Exchanging - Exchanging... - Failed - Deposit received - Awaiting deposit - Awaiting deposit... - Refunded - Sending to you - Sending to you... - Sent - Provider-sourced data. Estimated amount subject to change due to market conditions. - Exchange status - Verification required - Awaiting transaction hash - List of all tokens added to your wallet - Fetching best rates... - Floating rate - By using swap functionality, you agree with provider’s %s - By using swap functionality, you agree with provider’s %1$s and %2$s - More providers are coming soon.\nStay tuned! - Privacy Policy - Provider - Best rate - Available up to %s - Available from %s - Unavailable for this pair - Permission Required - Recommended - Terms of Use - No tokens found. Please try another request - ID: %s - Transaction ID copied - The following information is optional. You can erase it if you don\'t want to share it. - Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have - Hi support team, - Please tell us more about your issue. Every small detail can help. - My suggestions - Can\'t scan a card - Feedback - Tangem feedback - Can\'t send a transaction - Order card - Scan card - To change the access code tap the card as shown above and do not remove until the end of the operation - To change the passcode tap the card as shown above and do not remove until the end of the operation - To create the wallet tap the card as shown above and do not remove until the end of the operation - Tap the card #%s of the wallet - Tap to scan - Tap to sign - Tap the card - You have updated biometrics, scan your card to enter - Your balance should be higher than the fee value to make a transfer - Not enough balance - You don\'t have enough Mana for this transaction. Please wait until the Mana is refilled. Your Mana balance is %1$s/%2$s - Not enough Mana - You can transfer only %s due to the Mana limit imposed by the Koinos network - Mana limit - The Koinos network requires Mana for network fees. Your have %1$s/%2$s Mana - Mana level - To begin tracking your crypto assets and transactions, add tokens - Manage tokens - To access all the networks you need to scan the card - Scan your card - Enjoy %1$s service fees on swaps via Changelly from February %2$s-%3$s - Swap with Changelly, %s fees - Tokens - Book now - Save **$50** while booking via our partner Travala: **%1s - %2s** - Book your holidays with Tangem and pay in crypto - Add - Edit - Coin market cap - Blockchain the cryptocurrency was initially created - Native network - Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk. - Not original or primary blockchain the token is hosted - Non-native networks - Choose networks - Wallet - Couldn’t find this token, you can add it manually - - %1$d of %2$d wallet - %1$d of %2$d wallets - - e.g. BTC I trust, hodl I must - The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it. - Upvote - Choose wallet - The wallet doesn\'t support more than one network - You have to set up a single access code to protect all your cards - Protect - You can set up an individual access code on each card later - Personalize - The access code can be restored with a linked card. Don’t keep all cards at one place. - Restore - Choose any word, phrase, or number you want as your access code - Create Access Code - Enter your access code one more time to avoid a mistake - Re-enter your Access Code - Access code must be at least 4 characters long - Entered access code didn\'t match the initial access code - Please repeat the operation. The card will be reset to factory settings. - Activation error - Add tokens - You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? - The backup process is partly complete. You can\'t exit it now. - The passphrase is an advanced security feature that crypto wallets use. It adds an extra word or phrase of your own choosing to your already existing recovery phrase to unlock a brand-new set of addresses. - Add a backup card - Scan the card #%d - Backup now - Scan the primary card - Continue to my wallet - Finalize the backup - Receive crypto - Scan primary card - Skip for later - How does it work? - Let\'s generate all the keys on your card and create a secure wallet - Create wallet - Create a wallet - Other options - Your keys will be securely generated inside the card. There is no seed phrase, which means nobody can export or steal it. - Generate keys privately - Your card is activated and ready to be used - Success! - In this case, you will need to start from the beginning. - Do you want to exit the activation process? - Getting started - Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup. - Creating a backup - Read more about seed phrase - - - Write these %d words down in the order given below and store them in a safe and secret place. - - Your seed phrase - - - %d words - - To import your wallet, enter your seed phrase in the field below - Generate seed phrase - Import wallet - A seed phrase is a series of words that allows you to recover your wallet. Unlike the keys generated by the card, seed phrases are unprotected and can be copied and stolen. Use this option at your own risk. - Use seed phrase - Invalid seed phrase. Please check the word order. - Invalid seed phrase. Please check your spelling. - Legacy - To check whether you’ve written down your seed phrase correctly, please enter the 2nd, 7th and 11th words - So, let’s check - To start the backup process add up to two backup cards. - You can add one more card or finalize the backup process - Prepare the backup card with number %s - Scan the primary card to start the backup process. - Prepare the primary card with number %s - Your wallet card is configured and ready for use. - Max number of cards added. Finalize the backup process. - Activating card - Backup card #%d - No backup cards - One backup card added - Prepare your card - Two backup cards added - To get started, simply top up the wallet with any amount - To get started, simply top up the wallet with more than %1$s %2$s - Buy crypto - Show the wallet\'s address - Activate a wallet - The twinning process is partly complete. You can\'t exit it now. - If the process of creating the wallet gets interrupted in any way, you\'ll have to start over - You can backup your keys up to two other blank Tangem Wallet cards. - Access code can be restored with one of backup cards. - All the backup cards can be used as full-functional with the identical keys. - You will be able to set an access code to protect your wallets. - Backup wallet - Access code restore - Identical cards - Access code - Group - By balance - Organize tokens - Ungroup - Select from the gallery - Settings - You have not given access to your camera - Camera access denied - %1$s (%2$s) on %3$s network - Send only %s to this address. Sending any other currency will result in its irreversible loss. - Participate - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Error code: %s. Please try again later. - Upcoming payments - Your friends bought - Less - More - No upcoming payments - - for %d wallet - for %d wallets - - Will get ^^%1$s^^ for each wallet bought by your friend on your %2$s network address %3$s ^^30 days after^^ that - You - Will get a - when buying a wallet on tangem.com - %s discount - Your friend - Personal code copied! - Your personal code - Buy Tangem Wallet with discount!\n%s - Refer your friends to Tangem - You\'ve accepted - By tapping this button you accept - of the referral program - - %d wallet - %d wallets - - Reset the Card - I understand that after performing this action, I will no longer have access to the current wallet - I realize that I can\'t use this card to recover my access code on the other cards of the current wallet - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. - Do you have a bank card from another country and a residence permit or registration outside the Russian Federation? - Russian bank cards are not currently accepted - Log into the app and check your balance without scanning the card - Access the app - Allow to use biometrics - Biometrics will be requested instead of the access code for interactions with your wallet - Access code - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable biometric authorization - Would you like to use biometrics? - Note that making a transaction with your funds will still require your card - Scan Card - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Get your card ready! - Already included in the entered address - The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. - You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? - Reason: %1$s\nCode: %2$s - The transaction is not completed - Amount - You can set your transaction fee by adjusting the value in the Satoshi per vByte field. - Max fee - This is the cost you are willing to pay for each unit of gas. The higher the gas price, the faster your transaction will be processed. (Priority fee included) - Priority fee - The fee that a user can pay to miners or validators to expedite the inclusion of their transaction in a block. - %1$s, %2$s - Address - Destination Tag - Enter address - Address is the same as wallet address - The fee that will be charged for your transaction. You can set your own value. - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Include fee - Low - Normal - Priority - Check your network connection - Network fee info unreachable - From - Gas limit - This is the maximum amount of gas that will be spent to complete a transaction or contract. A gas limit prevents unexpected or unlimited charges when executing a transaction. - Gas price - This is the cost you are willing to pay for each unit of gas. The higher the gas price, the faster your transaction will be processed. - Max - Maximum amount - Fee up to - Invalid Memo - Network fee coverage - Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance - Total exceeds balance - The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance. - Existential deposit - The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. - Custom fee is high - Due to the peculiarities of the %1$s network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %2$s. - The fee is higher - The included commission exceeds the transfer amount, leading to a negative value - Invalid amount - The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s. - Target account is not created. Please change the amount to send. - The amount to send must be at least %s - Leave %s - Reduce by %s - Reduce to %s - Kindly be aware that your transaction may experience delays under specific fee settings - Transaction delays are possible - Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount. - Transaction limitation - Optional - Please align your QR code with the square to scan it. Ensure you scan %s network address. - Recent - Recipient - Not a valid address - Ensure the receiving wallet address is on the %s network to avoid losing your tokens - Send to - A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds - My wallets - A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners. - Satoshi / vByte - Sending... - Tap any field to change it - Send %s - You are sending **%1$s** including a network fee of %2$s - You are sending **%1$s** and %2$s - Sending %s - Total - %1$s and %2$s will be sent - ≈ %1$s (inc. fee: %2$s) - %s will be sent - Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while - Invalid address - %1$s (%2$s) - Transaction sent - Forget wallet - This will remove the wallet from the application. The wallet itself can be added again. - Name - Store your crypto assets secure while keeping private keys contained in your card - Revolutionary Hardware Wallet - Up to 3 physical cards to one wallet - Ultra Secure Backup - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - Thousands of Currencies - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - The Wallet for Everyone - Meet Tangem - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - Web 3.0 Compatible - Exchange more tokens at better rates directly in your wallet. - New Swap Provider Available! - The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. - The amount includes the service provider\'s fee. - Fees - All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. - Approve - You swap - Give Permission - Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. - Insufficient funds - Approve - Current transaction - The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. - Give Permission - Specify the approve limit for the selected token - Amount %s - To continue, grant %1$s smart contracts permission to use your %2$s - Unlimited - In progress - Swap - You receive - Choose token - not available - Balances hidden - Balances shown - Undo - This operation is currently unavailable. Please try again later. - Buying %s is not available at the moment. Please check our updates. - You do not have funds to sell. Top up your account to be able to sell funds from it. - You do not have funds to send. Top up your account to be able to send funds from it. - Swapping %s is not available at the moment. Please check our updates. - Selling funds will be available once the pending transaction(s) in network %s is complete - Sending funds will be available once the pending transaction(s) in network %s is complete - Selling %s is not available at the moment. Please check our updates. - Generate XPUB - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Hide %s - Hide token - Staking allows you to earn %1$s and get rewards every %2$s days - Earn up to %s staking rewards yearly - %1$s token in %%image%% %2$s network - Token in %%image%% %1$s network - The %1$s (%2$s) token is the main currency on the %3$s network and cannot be hidden as long as you have other tokens on this network in the list - Unable to hide %s - Exchange this token for another at %1$s service fees from February %2$s-%3$s. - Swap with Changelly, %s fees - Swap now - contract: %s - You don\'t have any transactions yet - Failed to load transaction history.\nClick on reload button to update the information. - Multiple addresses - Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer. - Operation - from: %s - to: %s - You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d - You\'ve scanned wrong twin card. Please try another one - This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. - One wallet. Two cards. - Scan the card #%s - Creating wallet - Scan the #%s twin card - Preparing card - Tangem Twin - This action is irreversible. You will not have access to the old wallet. - Tap the twin card with number %s and do not remove until the end of the operation - Use %s or scan a card to have an access to your wallet - Add new wallet - Are you sure you want to delete this wallet? - An error has occurred, please scan your card to log in - This wallet has already been saved, you can add another one - The wallet with name %s already exists - Wallet name - Rename Wallet - Unlock all - Unlock all with %s - Blockchain is unreachable. Try later - Scan the card - Requesting to sign a message.\n\n%s - Dapp %1$s, requesting to\nsign BNB transaction.\n\n%2$s - Trade order for %1$s\nPrice: %2$s\nAmount to receive: %3$s\nAmount to pay: %4$s - Transaction details:\nFrom: %1$s\nTo: %2$s\nAmount: %3$s - Clipboard contain WalletConnect code. Use copied value or scan QR-code - Request to create transaction for %1$s\n%2$s\n\nAmount: %3$s\nFee: %4$s\nTotal: %5$s\nBalance: %6$s - Can\'t send transaction. Not enough funds. - Failed to establish WalletConnect session. Please, try again later. - Not all tokens were added to your list. Please add them first and try again. Missing tokens:\n - Failed to sign message.\nPlease, try again - Failed to establish WalletConnect session: timeout error. Please, try again later. - Session request contains unsupported blockchains for WalletConnect connection. Unsupported blockchains:\n - Connection with this Dapp cannot be established due to its technical implementation. - We\'ve encountered unknown error. Error message: %s. If the problem persists — feel free to contact our support - Wrong card selected in Tangem App - Failed to create transaction from Dapp data. Code: %s - We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support - No opened WalletConnect sessions - Ooops. No Sessions. - Failed to pairing WalletConnect session: %1$s - Paste from clipboard - Message for %1$s:\n%2$s - Request to start a session for\n%1$s\n\nNETWORK: %2$s\n\nURL: %3$s - The operation couldn\'t be completed.\n\nYou have already established a WalletConnect session with this parameters. - Scan new code - This card can\'t be used to establish WalletConnect session - This network is not supported. Please select another network. - Select network - WalletConnect Sessions - Connect to dApps - WalletConnect - %s Market Price - last 24h - %s network - Address was copied to clipboard - No internet connection - Wallet settings - Tangem - Use %s or scan a card to unlock access to your wallet - Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost. - Activation error - According to BNB network developers, support for the BEP-2 standard will end in June 2024. To avoid losing assets with this standard, please convert them to the BEP-20 standard. Use our swap service or third-party services to transfer funds to the BNB Smart Chain network. - BNB Beacon Chain will shut down - Could be better - Like it - Ok, Got it! - Really cool! - Refresh - You are currently in the Demo mode - Demo mode active - The card you scanned is a developer card. Do not use it to create your wallet. - Not for users! - %1$s network requires an Existential Deposit. If your account drops below %2$s, it will be deactivated, and any remaining funds will be destroyed. - Network requires Existential Deposit - Swap will be available after the %s transaction is complete - You have active transaction - Swap approval is underway and will be completed shortly - Approval in progress - The minimum swapping amount is %1$s. Please ensure that the remaining balance after the swap will not be less than %2$s. - You do not have any %s exchangeable coins in your list - No available tokens to swap - To make a transaction you need to deposit some %1$s %2$s - Unable to cover %s fee - The amount to receive must be at least %s - Service temporarily unavailable - The amount of tokens to be swapped must not exceed %s - The amount to swap must be at least %s - Please change the amount to swap - This card might be a production sample or counterfeit - Authenticity check failed - Associate - This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s - This token must be associated with your Hedera account before you can receive it - Associate your token - Not enough %s. Top up your Hedera account to associate this token - Only %s signatures are left on this card. You must withdraw all of your funds. - Low signature count - Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. - - Use your card to get an address for %d network - Use your card to get an addresses for %d networks - - Some addresses are missing - The network is currently unreachable. Please try again later. - Network is unreachable - Top up your wallet - Your wallet hasn\'t been backed up. Carry out this procedure to protect your assets now. - Missing backup - This card has been previously used for transactions. If received from an untrusted source, consider withdrawing all funds. If it\'s your card, no action is required. - Card has already signed transactions - Your review keeps us motivated to make Tangem Wallet even better - Enjoying Tangem? - You must associate your token before receiving tokens - Network rent fee required - %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. - Insufficient %1$s to cover network fee - The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction. - Solana Network Alert - Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. - Some networks currently are unreachable. Please try again later. - Some networks are unreachable - This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes. - For testing purposes only - Discard - You have an interrupted backup. Do you want to resume? - Yes, resume - Discard - If you will discard the backup now, then you will have to reset the cards to factory settings to start over again - Resume backup - This is an irreversible action - Log in with %s - Scan card - Use %s or scan a card to access the app - Welcome back! + Add custom token + Manage tokens + Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. + Request support + This feature is disabled in Demo mode + Reason: %s + Can\'t send a transaction + The selected does not support the %1$s network + To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. + Tokens in %1$s network are not supported by this card due to firmware limitation. + Are you having difficulty scanning your card? + This card is not designed to work with this app + Default Fee + Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary. + Go to settings to enable biometric authentication in the Tangem App + Enable biometric authentication + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + Removing the saved card deletes all the saved wallets and their access codes from the app. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Dark + Light + System default + Theme + App settings + To hide or show your balances, simply flip your device screen down, or switch it off in Settings + Don\'t show again + Got it + Balances are hidden + Please scan the card + Please try again in 30 seconds or scan the card + Too many attempts + You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings. + Start backup process + + %d card + %d cards + + Disable this option if you don\'t want this card to be used to reset access codes on other cards in this wallet. Please note that this will also prevent you from resetting the access code on this card. + Allows you to use this card to reset access code on other cards in this wallet + Access code recovery + Reset + Are you sure you want to do this? + Change Access Code + Access code will be changed on this card only + All cards in the selected wallet have been reset to factory settings. You can now create a new wallet. + Reset complete + Do you want to reset the next card in this wallet? + Card reset + We recommend completing the reset process for all cards in this wallet + You haven\'t reset all your cards + Reset to Factory Settings + Security Mode + Card settings + In addition to network fee, the Cardano network charges %1$s ADA when transacting with the %2$s token + Cardano transaction requirements + To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value (5 ADA recommended) + Insufficient ADA for token transfer + You must maintain some ADA because you have some tokens on the Cardano blockchain + Not enough ADA + Accept + Access denied + Apply + Approval + Attention + Balance: %s + Balance + biometric authentication + biometrics + Buy + Go to %1$s + You have not given access to your camera, please adjust your privacy settings + Cancel + Close + Continue + Copy + Copy address + Create + Delete + Disabled + Done + Enable + Enabled + Error + Explore + Explore transaction history + Explorer + Fee + Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s + Custom + Fast + Market + Slow + Speed and fee + Get addresses + Go to provider + Go to token + Import + Later + Locked + Main network + Network fee + Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level + Next + No + No address + OK + Primary Card + Passphrase + Paste + Read more + Receive + Reject + Reload + Rename + Save changes + Search + Search tokens + Seed phrase + Select action + Sell + Send + The server is not available, please try again later + Share + Sign + Sign and send + Start + Submit + Success + Support + Swap + terms and conditions + Transaction failed + Transactions + Transfer + I understand + There was an error. Please try again. + Unreachable + Yes + Contract address copied! + Available networks + Add token + Contract address + Contract address is invalid + Please select the network + Decimal must be a valid integer, up to %li + Custom derivation + E. g. m/00\'/0000\'/0\'/0/0 + Enter custom derivation + Decimals + Derivation Path + Default + BIP44 coin type + The derivation path you\'ve entered is not valid + E.g. USD Coin + Name + Not selected + Network + Token network + You can manually add a token that is not natively supported by Tangem + E.g. USDC + Symbol + Token symbol + This token/network has already been added to your list + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + Be aware of adding scam tokens, they can cost nothing + Note that tokens can be created by anyone + Chat + Access code + You will have to submit the correct access code before scanning the card + Long Tap + This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. + Passcode + Before executing any command entailing a change of the card state, you will have to enter the passcode. + Referral program + Flip your device screen down to quickly hide and show balances + %s hashes + Card ID + Contact support + Link More Cards + App Currency + Flip-to-Hide Balances + Issuer + Signed + Details + Check your internet connection or switch to a different network + Terms of service + You have used a card from another wallet. Tap the card associated with this wallet + My tokens + You haven\'t added any tokens yet. Add tokens via Market to swap + Cannot be swapped for %s + Provided by + Status + Tangem offers token swaps via 3rd-party providers according to each provider\'s terms + Choose provider + An error occurred. Code: %s + Oops! Swapping the selected pair through the chosen provider is temporarily unavailable. Please try again later. (Code: %s) + Selected provider is unavailable at the moment. Please try again later. (Code: %s) + Swaps are unavailable at the moment. Please try again later. (Code: %s) + Estimated amount + Exchange by %s + Visit provider’s website to refund your money + Operation failed by provider + The transaction amount was refunded in %1$s to your wallet due to OKX or bridge rules. %2$s + The amount was refunded in %1$s (%2$s network) + Visit provider’s website for verification + KYC verification required by provider + Canceled + Confirmed + Confirming + Confirming... + Exchanged + Exchanging + Exchanging... + Failed + Deposit received + Awaiting deposit + Awaiting deposit... + Refunded + Sending to you + Sending to you... + Sent + Provider-sourced data. Estimated amount subject to change due to market conditions. + Exchange status + Verification required + Awaiting transaction hash + List of all tokens added to your wallet + Fetching best rates... + Floating rate + By using swap functionality, you agree with provider’s %s + By using swap functionality, you agree with provider’s %1$s and %2$s + More providers are coming soon.\nStay tuned! + Privacy Policy + Provider + Best rate + Available up to %s + Available from %s + Unavailable for this pair + Permission Required + Recommended + Terms of Use + No tokens found. Please try another request + ID: %s + Transaction ID copied + The following information is optional. You can erase it if you don\'t want to share it. + Tell us what functions you are missing, and we will try to help you. + Please tell us what card do you have + Hi support team, + Please tell us more about your issue. Every small detail can help. + My suggestions + Can\'t scan a card + Feedback + Tangem feedback + Can\'t send a transaction + Order card + Scan card + To change the access code tap the card as shown above and do not remove until the end of the operation + To change the passcode tap the card as shown above and do not remove until the end of the operation + To create the wallet tap the card as shown above and do not remove until the end of the operation + Tap the card #%s of the wallet + Tap to scan + Tap to sign + Tap the card + You have updated biometrics, scan your card to enter + Your balance should be higher than the fee value to make a transfer + Not enough balance + You don\'t have enough Mana for this transaction. Please wait until the Mana is refilled. Your Mana balance is %1$s/%2$s + Not enough Mana + You can transfer only %s due to the Mana limit imposed by the Koinos network + Mana limit + The Koinos network requires Mana for network fees. Your have %1$s/%2$s Mana + Mana level + To begin tracking your crypto assets and transactions, add tokens + Manage tokens + To access all the networks you need to scan the card + Scan your card + Enjoy %1$s service fees on swaps via Changelly from February %2$s-%3$s + Swap with Changelly, %s fees + Tokens + Book now + Save **$50** while booking via our partner Travala: **%1s - %2s** + Book your holidays with Tangem and pay in crypto + Add + Edit + Coin market cap + Blockchain the cryptocurrency was initially created + Native network + Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk. + Not original or primary blockchain the token is hosted + Non-native networks + Choose networks + Wallet + Couldn’t find this token, you can add it manually + + %1$d of %2$d wallet + %1$d of %2$d wallets + + e.g. BTC I trust, hodl I must + The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it. + Upvote + Choose wallet + The wallet doesn\'t support more than one network + You have to set up a single access code to protect all your cards + Protect + You can set up an individual access code on each card later + Personalize + The access code can be restored with a linked card. Don’t keep all cards at one place. + Restore + Choose any word, phrase, or number you want as your access code + Create Access Code + Enter your access code one more time to avoid a mistake + Re-enter your Access Code + Access code must be at least 4 characters long + Entered access code didn\'t match the initial access code + Please repeat the operation. The card will be reset to factory settings. + Activation error + Add tokens + You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? + The backup process is partly complete. You can\'t exit it now. + The passphrase is an advanced security feature that crypto wallets use. It adds an extra word or phrase of your own choosing to your already existing recovery phrase to unlock a brand-new set of addresses. + Add a backup card + Scan the card #%d + Backup now + Scan the primary card + Continue to my wallet + Finalize the backup + Receive crypto + Scan primary card + Skip for later + How does it work? + Let\'s generate all the keys on your card and create a secure wallet + Create wallet + Create a wallet + Other options + Your keys will be securely generated inside the card. There is no seed phrase, which means nobody can export or steal it. + Generate keys privately + Your card is activated and ready to be used + Success! + In this case, you will need to start from the beginning. + Do you want to exit the activation process? + Getting started + Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup. + Creating a backup + Read more about seed phrase + + + Write these %d words down in the order given below and store them in a safe and secret place. + + Your seed phrase + + + %d words + + To import your wallet, enter your seed phrase in the field below + Generate seed phrase + Import wallet + A seed phrase is a series of words that allows you to recover your wallet. Unlike the keys generated by the card, seed phrases are unprotected and can be copied and stolen. Use this option at your own risk. + Use seed phrase + Invalid seed phrase. Please check the word order. + Invalid seed phrase. Please check your spelling. + Legacy + To check whether you’ve written down your seed phrase correctly, please enter the 2nd, 7th and 11th words + So, let’s check + To start the backup process add up to two backup cards. + You can add one more card or finalize the backup process + Prepare the backup card with number %s + Scan the primary card to start the backup process. + Prepare the primary card with number %s + Your wallet card is configured and ready for use. + Max number of cards added. Finalize the backup process. + Activating card + Backup card #%d + No backup cards + One backup card added + Prepare your card + Two backup cards added + To get started, simply top up the wallet with any amount + To get started, simply top up the wallet with more than %1$s %2$s + Buy crypto + Show the wallet\'s address + Activate a wallet + The twinning process is partly complete. You can\'t exit it now. + If the process of creating the wallet gets interrupted in any way, you\'ll have to start over + You can backup your keys up to two other blank Tangem Wallet cards. + Access code can be restored with one of backup cards. + All the backup cards can be used as full-functional with the identical keys. + You will be able to set an access code to protect your wallets. + Backup wallet + Access code restore + Identical cards + Access code + Group + By balance + Organize tokens + Ungroup + Select from the gallery + Settings + You have not given access to your camera + Camera access denied + %1$s (%2$s) on %3$s network + Send only %s to this address. Sending any other currency will result in its irreversible loss. + Participate + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Error code: %s. Please try again later. + Upcoming payments + Your friends bought + Less + More + No upcoming payments + + for %d wallet + for %d wallets + + Will get ^^%1$s^^ for each wallet bought by your friend on your %2$s network address %3$s ^^30 days after^^ that + You + Will get a + when buying a wallet on tangem.com + %s discount + Your friend + Personal code copied! + Your personal code + Buy Tangem Wallet with discount!\n%s + Refer your friends to Tangem + You\'ve accepted + By tapping this button you accept + of the referral program + + %d wallet + %d wallets + + Reset the Card + I understand that after performing this action, I will no longer have access to the current wallet + I realize that I can\'t use this card to recover my access code on the other cards of the current wallet + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Do you have a bank card from another country and a residence permit or registration outside the Russian Federation? + Russian bank cards are not currently accepted + Log into the app and check your balance without scanning the card + Access the app + Allow to use biometrics + Biometrics will be requested instead of the access code for interactions with your wallet + Access code + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable biometric authorization + Would you like to use biometrics? + Note that making a transaction with your funds will still require your card + Scan Card + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Get your card ready! + Already included in the entered address + The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. + You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? + Reason: %1$s\nCode: %2$s + The transaction is not completed + Amount + You can set your transaction fee by adjusting the value in the Satoshi per vByte field. + Max fee + This is the cost you are willing to pay for each unit of gas. The higher the gas price, the faster your transaction will be processed. (Priority fee included) + Priority fee + The fee that a user can pay to miners or validators to expedite the inclusion of their transaction in a block. + %1$s, %2$s + Address + Destination Tag + Enter address + Address is the same as wallet address + The fee that will be charged for your transaction. You can set your own value. + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Include fee + Low + Normal + Priority + Check your network connection + Network fee info unreachable + From + Gas limit + This is the maximum amount of gas that will be spent to complete a transaction or contract. A gas limit prevents unexpected or unlimited charges when executing a transaction. + Gas price + This is the cost you are willing to pay for each unit of gas. The higher the gas price, the faster your transaction will be processed. + Max + Maximum amount + Fee up to + Invalid Memo + Network fee coverage + Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance + Total exceeds balance + The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance. + Existential deposit + The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. + Custom fee is high + Due to the peculiarities of the %1$s network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %2$s. + The fee is higher + The included commission exceeds the transfer amount, leading to a negative value + Invalid amount + The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s. + Target account is not created. Please change the amount to send. + The amount to send must be at least %s + Leave %s + Reduce by %s + Reduce to %s + Kindly be aware that your transaction may experience delays under specific fee settings + Transaction delays are possible + Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount. + Transaction limitation + Optional + Please align your QR code with the square to scan it. Ensure you scan %s network address. + Recent + Recipient + Not a valid address + Ensure the receiving wallet address is on the %s network to avoid losing your tokens + Send to + A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds + My wallets + A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners. + Satoshi / vByte + Sending... + Tap any field to change it + Send %s + You are sending **%1$s** including a network fee of %2$s + You are sending **%1$s** and %2$s + Sending %s + Total + %1$s and %2$s will be sent + ≈ %1$s (inc. fee: %2$s) + %s will be sent + Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while + Invalid address + %1$s (%2$s) + Transaction sent + Forget wallet + This will remove the wallet from the application. The wallet itself can be added again. + Name + Store your crypto assets secure while keeping private keys contained in your card + Revolutionary Hardware Wallet + Up to 3 physical cards to one wallet + Ultra Secure Backup + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + Thousands of Currencies + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + The Wallet for Everyone + Meet Tangem + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + Web 3.0 Compatible + Exchange more tokens at better rates directly in your wallet. + New Swap Provider Available! + The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. + The amount includes the service provider\'s fee. + Fees + All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. + Approve + You swap + Give Permission + Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. + Insufficient funds + Approve + Current transaction + The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. + Give Permission + Specify the approve limit for the selected token + Amount %s + To continue, grant %1$s smart contracts permission to use your %2$s + Unlimited + In progress + Swap + You receive + Choose token + not available + Balances hidden + Balances shown + Undo + This operation is currently unavailable. Please try again later. + Buying %s is not available at the moment. Please check our updates. + You do not have funds to sell. Top up your account to be able to sell funds from it. + You do not have funds to send. Top up your account to be able to send funds from it. + Swapping %s is not available at the moment. Please check our updates. + Selling funds will be available once the pending transaction(s) in network %s is complete + Sending funds will be available once the pending transaction(s) in network %s is complete + Selling %s is not available at the moment. Please check our updates. + Generate XPUB + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Hide %s + Hide token + Staking allows you to earn %1$s and get rewards every %2$s days + Earn up to %s staking rewards yearly + %1$s token in %%image%% %2$s network + Token in %%image%% %1$s network + The %1$s (%2$s) token is the main currency on the %3$s network and cannot be hidden as long as you have other tokens on this network in the list + Unable to hide %s + Exchange this token for another at %1$s service fees from February %2$s-%3$s. + Swap with Changelly, %s fees + Swap now + contract: %s + You don\'t have any transactions yet + Failed to load transaction history.\nClick on reload button to update the information. + Multiple addresses + Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer. + Operation + from: %s + to: %s + You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d + You\'ve scanned wrong twin card. Please try another one + This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. + One wallet. Two cards. + Scan the card #%s + Creating wallet + Scan the #%s twin card + Preparing card + Tangem Twin + This action is irreversible. You will not have access to the old wallet. + Tap the twin card with number %s and do not remove until the end of the operation + Use %s or scan a card to have an access to your wallet + Add new wallet + Are you sure you want to delete this wallet? + An error has occurred, please scan your card to log in + This wallet has already been saved, you can add another one + The wallet with name %s already exists + Wallet name + Rename Wallet + Unlock all + Unlock all with %s + Blockchain is unreachable. Try later + Scan the card + Requesting to sign a message.\n\n%s + Dapp %1$s, requesting to\nsign BNB transaction.\n\n%2$s + Trade order for %1$s\nPrice: %2$s\nAmount to receive: %3$s\nAmount to pay: %4$s + Transaction details:\nFrom: %1$s\nTo: %2$s\nAmount: %3$s + Clipboard contain WalletConnect code. Use copied value or scan QR-code + Request to create transaction for %1$s\n%2$s\n\nAmount: %3$s\nFee: %4$s\nTotal: %5$s\nBalance: %6$s + Can\'t send transaction. Not enough funds. + Failed to establish WalletConnect session. Please, try again later. + Not all tokens were added to your list. Please add them first and try again. Missing tokens:\n + Failed to sign message.\nPlease, try again + Failed to establish WalletConnect session: timeout error. Please, try again later. + Session request contains unsupported blockchains for WalletConnect connection. Unsupported blockchains:\n + Connection with this Dapp cannot be established due to its technical implementation. + We\'ve encountered unknown error. Error message: %s. If the problem persists — feel free to contact our support + Wrong card selected in Tangem App + Failed to create transaction from Dapp data. Code: %s + We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support + No opened WalletConnect sessions + Ooops. No Sessions. + Failed to pairing WalletConnect session: %1$s + Paste from clipboard + Message for %1$s:\n%2$s + Request to start a session for\n%1$s\n\nNETWORK: %2$s\n\nURL: %3$s + The operation couldn\'t be completed.\n\nYou have already established a WalletConnect session with this parameters. + Scan new code + This card can\'t be used to establish WalletConnect session + This network is not supported. Please select another network. + Select network + WalletConnect Sessions + Connect to dApps + WalletConnect + %s Market Price + last 24h + %s network + Address was copied to clipboard + No internet connection + Wallet settings + Tangem + Use %s or scan a card to unlock access to your wallet + Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost. + Activation error + According to BNB network developers, support for the BEP-2 standard will end in June 2024. To avoid losing assets with this standard, please convert them to the BEP-20 standard. Use our swap service or third-party services to transfer funds to the BNB Smart Chain network. + BNB Beacon Chain will shut down + Could be better + Like it + Ok, Got it! + Really cool! + Refresh + You are currently in the Demo mode + Demo mode active + The card you scanned is a developer card. Do not use it to create your wallet. + Not for users! + %1$s network requires an Existential Deposit. If your account drops below %2$s, it will be deactivated, and any remaining funds will be destroyed. + Network requires Existential Deposit + Swap will be available after the %s transaction is complete + You have active transaction + Swap approval is underway and will be completed shortly + Approval in progress + The minimum swapping amount is %1$s. Please ensure that the remaining balance after the swap will not be less than %2$s. + You do not have any %s exchangeable coins in your list + No available tokens to swap + To make a transaction you need to deposit some %1$s %2$s + Unable to cover %s fee + The amount to receive must be at least %s + Service temporarily unavailable + The amount of tokens to be swapped must not exceed %s + The amount to swap must be at least %s + Please change the amount to swap + This card might be a production sample or counterfeit + Authenticity check failed + Associate + This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s + This token must be associated with your Hedera account before you can receive it + Associate your token + Not enough %s. Top up your Hedera account to associate this token + Only %s signatures are left on this card. You must withdraw all of your funds. + Low signature count + Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. + + Use your card to get an address for %d network + Use your card to get an addresses for %d networks + + Some addresses are missing + The network is currently unreachable. Please try again later. + Network is unreachable + Top up your wallet + Your wallet hasn\'t been backed up. Carry out this procedure to protect your assets now. + Missing backup + This card has been previously used for transactions. If received from an untrusted source, consider withdrawing all funds. If it\'s your card, no action is required. + Card has already signed transactions + Your review keeps us motivated to make Tangem Wallet even better + Enjoying Tangem? + You must associate your token before receiving tokens + Network rent fee required + %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. + Insufficient %1$s to cover network fee + The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction. + Solana Network Alert + Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. + Some networks currently are unreachable. Please try again later. + Some networks are unreachable + This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes. + For testing purposes only + Discard + You have an interrupted backup. Do you want to resume? + Yes, resume + Discard + If you will discard the backup now, then you will have to reset the cards to factory settings to start over again + Resume backup + This is an irreversible action + Log in with %s + Scan card + Use %s or scan a card to access the app + Welcome back! diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/CurrencyNotification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/CurrencyNotification.kt new file mode 100644 index 0000000000..7c646add3d --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/CurrencyNotification.kt @@ -0,0 +1,125 @@ +package com.tangem.core.ui.components.notifications + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.colorspace.ColorSpaces +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.components.SpacerW +import com.tangem.core.ui.components.currency.tokenicon.TokenIcon +import com.tangem.core.ui.components.currency.tokenicon.TokenIconState +import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview + +/** + * Currency notification component from Design system. + * + * @param config component config + * @param modifier modifier + * @param containerColor container color + * + * @see Figma component + */ +@Composable +fun CurrencyNotification( + config: CurrencyNotificationConfig, + modifier: Modifier = Modifier, + containerColor: Color? = null, +) { + NotificationBaseContainer( + buttonsState = config.buttonsState, + onClick = null, + onCloseClick = null, + modifier = modifier, + containerColor = containerColor, + ) { + MainContent( + tokenIconState = config.tokenIconState, + title = config.title, + subtitle = config.subtitle, + ) + } +} + +@Composable +private fun MainContent( + tokenIconState: TokenIconState, + title: TextReference, + subtitle: CurrencyNotificationConfig.AnnotatedSubtitle, +) { + Row { + TokenIcon( + state = tokenIconState, + modifier = Modifier.align(alignment = Alignment.CenterVertically), + ) + + SpacerW(width = TangemTheme.dimens.spacing6) + + TextsBlock(title = title, subtitle = subtitle) + } +} + +@Composable +private fun TextsBlock(title: TextReference, subtitle: CurrencyNotificationConfig.AnnotatedSubtitle) { + Column(verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing2)) { + Text( + text = title.resolveReference(), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.button, + ) + + val subtitleValue = subtitle.valueProvider() + ClickableText( + text = subtitleValue, + onClick = { subtitle.onClick(subtitleValue, it) }, + style = TangemTheme.typography.caption2, + ) + } +} + +@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_Notification() { + TangemThemePreview { + CurrencyNotification( + config = CurrencyNotificationConfig( + title = resourceReference( + R.string.express_exchange_notification_refund_title, + wrappedList("USDT", "Polygon"), + ), + subtitle = CurrencyNotificationConfig.AnnotatedSubtitle( + valueProvider = { + buildAnnotatedString { + append("Your transaction amount was refunded in USDT to your wallet due to OKX") + } + }, + onClick = { _, _ -> }, + ), + tokenIconState = TokenIconState.TokenIcon( + url = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/usd-coin.png", + networkBadgeIconResId = R.drawable.ic_polygon_22, + isGrayscale = false, + showCustomBadge = false, + fallbackTint = Color(1.0f, 1.0f, 1.0f, 1.0f, ColorSpaces.Srgb), + fallbackBackground = Color(0.23529412f, 0.28627452f, 0.6117647f, 1.0f, ColorSpaces.Srgb), + ), + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = stringReference("Go to token"), + onClick = {}, + ), + ), + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/CurrencyNotificationConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/CurrencyNotificationConfig.kt new file mode 100644 index 0000000000..0fa3aadd26 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/CurrencyNotificationConfig.kt @@ -0,0 +1,35 @@ +package com.tangem.core.ui.components.notifications + +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.AnnotatedString +import com.tangem.core.ui.components.currency.tokenicon.TokenIconState +import com.tangem.core.ui.extensions.TextReference + +/** + * Currency notification component state + * + * @property title title + * @property subtitle subtitle + * @property buttonsState buttons state + * @property tokenIconState token icon state + * +[REDACTED_AUTHOR] + */ +data class CurrencyNotificationConfig( + val title: TextReference, + val subtitle: AnnotatedSubtitle, + val tokenIconState: TokenIconState, + val buttonsState: NotificationConfig.ButtonsState, +) { + + /** + * Subtitle as [AnnotatedString] + * + * @property valueProvider composable function that provides [AnnotatedString] + * @property onClick lambda be invoked when text in specified position is clicked + */ + data class AnnotatedSubtitle( + val valueProvider: @Composable () -> AnnotatedString, + val onClick: (value: AnnotatedString, position: Int) -> Unit, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index c5b3d10a9b..f25c4014cf 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -30,17 +30,19 @@ import com.tangem.core.ui.components.* import com.tangem.core.ui.components.buttons.common.TangemButtonSize import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState as NotificationButtonsState /** * Notification component from Design system. * Use this for Notification with title, subtitle, clickable or not. * - * @param config component config - * @param modifier modifier - * @param iconTint icon tint + * @param config component config + * @param modifier modifier + * @param containerColor container color + * @param iconTint icon tint + * @param isEnabled flag that defines if component is clickable * * @see Figma component @@ -53,44 +55,33 @@ fun Notification( iconTint: Color? = null, isEnabled: Boolean = true, ) { - BaseContainer( + NotificationBaseContainer( buttonsState = config.buttonsState, onClick = config.onClick, + onCloseClick = config.onCloseClick, modifier = modifier, containerColor = containerColor, isEnabled = isEnabled, ) { - Column( - modifier = Modifier.padding(all = TangemTheme.dimens.spacing12), - verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12), - ) { - MainContent( - iconResId = config.iconResId, - iconTint = iconTint, - title = config.title, - subtitle = config.subtitle, - isClickableComponent = isEnabled && config.onClick != null, - ) - - Buttons(state = config.buttonsState, isEnabled = isEnabled) - } - - CloseableIconButton( - onClick = config.onCloseClick, - modifier = Modifier.align(alignment = Alignment.TopEnd), - isEnabled = isEnabled, + MainContent( + iconResId = config.iconResId, + iconTint = iconTint, + title = config.title, + subtitle = config.subtitle, + isClickableComponent = isEnabled && config.onClick != null, ) } } @Composable -private fun BaseContainer( +internal fun NotificationBaseContainer( buttonsState: NotificationConfig.ButtonsState?, onClick: (() -> Unit)?, + onCloseClick: (() -> Unit)?, modifier: Modifier = Modifier, isEnabled: Boolean = true, containerColor: Color? = null, - content: @Composable BoxScope.() -> Unit, + content: @Composable ColumnScope.() -> Unit, ) { val tempContainerColor by rememberUpdatedState( newValue = if (buttonsState != null || onClick != null) { @@ -109,7 +100,22 @@ private fun BaseContainer( shape = TangemTheme.shapes.roundedCornersXMedium, color = containerColor ?: tempContainerColor, ) { - Box(content = content) + Box { + Column( + modifier = Modifier.padding(all = TangemTheme.dimens.spacing12), + verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12), + ) { + content() + + Buttons(state = buttonsState, isEnabled = isEnabled) + } + + CloseableIconButton( + onClick = onCloseClick, + modifier = Modifier.align(alignment = Alignment.TopEnd), + isEnabled = isEnabled, + ) + } } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt index 58b0829319..fd9625e5c3 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt @@ -1,38 +1,43 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.components import androidx.compose.runtime.Immutable +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle +import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter +import com.tangem.core.ui.components.notifications.CurrencyNotificationConfig import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.tokendetails.impl.R @Immutable -internal sealed class ExchangeStatusNotifications(val config: NotificationConfig) { +internal sealed interface ExchangeStatusNotifications { - data class NeedVerification( - val onGoToProviderClick: () -> Unit, - ) : ExchangeStatusNotifications( + sealed class CommonNotification(val config: NotificationConfig) : ExchangeStatusNotifications + + data class NeedVerification(val onGoToProviderClick: () -> Unit) : CommonNotification( config = NotificationConfig( - title = TextReference.Res(R.string.express_exchange_notification_verification_title), - subtitle = TextReference.Res(R.string.express_exchange_notification_verification_text), + title = resourceReference(R.string.express_exchange_notification_verification_title), + subtitle = resourceReference(R.string.express_exchange_notification_verification_text), iconResId = R.drawable.ic_alert_triangle_20, buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( - text = TextReference.Res(R.string.common_go_to_provider), + text = resourceReference(R.string.common_go_to_provider), onClick = onGoToProviderClick, ), ), ) - data class Failed( - val onGoToProviderClick: () -> Unit, - ) : ExchangeStatusNotifications( + data class Failed(val onGoToProviderClick: () -> Unit) : CommonNotification( config = NotificationConfig( - title = TextReference.Res(R.string.express_exchange_notification_failed_title), - subtitle = TextReference.Res(R.string.express_exchange_notification_failed_text), + title = resourceReference(R.string.express_exchange_notification_failed_title), + subtitle = resourceReference(R.string.express_exchange_notification_failed_text), iconResId = R.drawable.ic_alert_circle_24, buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( - text = TextReference.Res(R.string.common_go_to_provider), + text = resourceReference(R.string.common_go_to_provider), onClick = onGoToProviderClick, ), ), @@ -40,16 +45,48 @@ internal sealed class ExchangeStatusNotifications(val config: NotificationConfig data class TokenRefunded( val cryptoCurrency: CryptoCurrency, + val onReadMoreClick: () -> Unit, val onGoToTokenClick: () -> Unit, - ) : ExchangeStatusNotifications( - config = NotificationConfig( - title = stringReference("TITLE FOR TOKEN REFUND"), - subtitle = stringReference("SUBTITLE FOR TOKEN REFUND"), - iconResId = R.drawable.ic_alert_triangle_20, + ) : ExchangeStatusNotifications { + + val config = CurrencyNotificationConfig( + title = resourceReference( + id = R.string.express_exchange_notification_refund_title, + formatArgs = wrappedList(cryptoCurrency.symbol, cryptoCurrency.network.name), + ), + subtitle = CurrencyNotificationConfig.AnnotatedSubtitle( + valueProvider = { + val linkText = stringResource(R.string.common_read_more) + val fullString = stringResource( + R.string.express_exchange_notification_refund_text, + cryptoCurrency.symbol, + linkText, + ) + + val linkTextPosition = fullString.length - linkText.length + + buildAnnotatedString { + withStyle(SpanStyle(TangemTheme.colors.text.tertiary)) { + append(fullString.substring(0, linkTextPosition)) + } + + withStyle(SpanStyle(TangemTheme.colors.text.accent)) { + append(fullString.substring(linkTextPosition, fullString.length)) + } + } + }, + onClick = { value, position -> + val readMoreStyle = requireNotNull(value.spanStyles.getOrNull(1)) + if (position in readMoreStyle.start..readMoreStyle.end) { + onReadMoreClick() + } + }, + ), + tokenIconState = CryptoCurrencyToIconStateConverter().convert(cryptoCurrency), buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( - text = stringReference("Go to token"), + text = resourceReference(R.string.common_go_to_token), onClick = onGoToTokenClick, ), - ), - ) + ) + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt index fba5085305..54d15b83ea 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt @@ -25,6 +25,7 @@ import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import java.math.BigDecimal +import java.util.Locale internal class TokenDetailsSwapTransactionsStateConverter( private val clickIntents: TokenDetailsClickIntents, @@ -163,9 +164,11 @@ internal class TokenDetailsSwapTransactionsStateConverter( if (refundToken == null) { null } else { - ExchangeStatusNotifications.TokenRefunded(refundToken) { - clickIntents.onGoToRefundedTokenClick(refundToken) - } + ExchangeStatusNotifications.TokenRefunded( + cryptoCurrency = refundToken, + onReadMoreClick = { clickIntents.onOpenUrlClick(url = getAboutCrossChainBridgesLink()) }, + onGoToTokenClick = { clickIntents.onGoToRefundedTokenClick(refundToken) }, + ) } } else -> null @@ -316,4 +319,12 @@ internal class TokenDetailsSwapTransactionsStateConverter( isDone = isSendingDone, ) } + + private fun getAboutCrossChainBridgesLink(): String { + return if (Locale.getDefault().country == "RU") { + "https://tangem.com/ru/blog/post/an-overview-of-cross-chain-bridges/" + } else { + "https://tangem.com/en/blog/post/an-overview-of-cross-chain-bridges/" + } + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt index a1a7c595a5..d3372936b1 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt @@ -17,11 +17,12 @@ import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.core.ui.components.notifications.Notification +import com.tangem.core.ui.components.notifications.CurrencyNotification import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState +import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications @Composable internal fun ExchangeStatusBottomSheet(config: TangemBottomSheetConfig) { @@ -29,16 +30,13 @@ internal fun ExchangeStatusBottomSheet(config: TangemBottomSheetConfig) { config = config, containerColor = TangemTheme.colors.background.tertiary, ) { content: ExchangeStatusBottomSheetConfig -> - ExchangeStatusBottomSheetContent(content = content) + ExchangeStatusBottomSheetContent(config = content.value) } } @Composable -private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetConfig) { - val config = content.value - Column( - modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), - ) { +private fun ExchangeStatusBottomSheetContent(config: SwapTransactionsState) { + Column(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16)) { SpacerH10() Text( text = stringResource(id = R.string.express_exchange_status_title), @@ -80,25 +78,39 @@ private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetC showLink = config.showProviderLink, onClick = { config.onGoToProviderClick(config.txUrl.orEmpty()) }, ) - AnimatedContent( - targetState = config.notification, - label = "Exchange Status Notification Change", - ) { - it?.let { - val tint = when (config.activeStatus) { - ExchangeStatus.Verifying -> TangemTheme.colors.icon.attention - ExchangeStatus.Failed -> TangemTheme.colors.icon.warning - else -> null - } - Notification( - config = it.config, - iconTint = tint, + if (config.notification != null) { + Notification(state = config.notification, activeStatus = config.activeStatus) + } + SpacerH24() + } +} + +@Composable +private fun Notification(state: ExchangeStatusNotifications, activeStatus: ExchangeStatus?) { + AnimatedContent( + targetState = state, + modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), + label = "Exchange Status Notification Change", + ) { notification -> + when (notification) { + is ExchangeStatusNotifications.CommonNotification -> { + com.tangem.core.ui.components.notifications.Notification( + config = notification.config, + iconTint = when (activeStatus) { + ExchangeStatus.Verifying -> TangemTheme.colors.icon.attention + ExchangeStatus.Failed -> TangemTheme.colors.icon.warning + else -> null + }, + containerColor = TangemTheme.colors.background.action, + ) + } + is ExchangeStatusNotifications.TokenRefunded -> { + CurrencyNotification( + config = notification.config, containerColor = TangemTheme.colors.background.action, - modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), ) } } - SpacerH24() } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt index 387d2f3482..9781e7cca9 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt @@ -57,4 +57,6 @@ interface TokenDetailsClickIntents { fun onAssociateClick() fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency) + + fun onOpenUrlClick(url: String) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 9fa0481605..37e0da4d40 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -726,6 +726,10 @@ internal class TokenDetailsViewModel @Inject constructor( router.openTokenDetails(userWalletId, cryptoCurrency) } + override fun onOpenUrlClick(url: String) { + router.openUrl(url) + } + override fun onSwapPromoDismiss() { viewModelScope.launch(dispatchers.main) { shouldShowSwapPromoTokenUseCase.neverToShow() From 1a2eb895be9f3cabffcfcda1e39ed8e15a30285c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 29 Jul 2024 15:20:47 +0500 Subject: [PATCH 22/53] Updated on 2026-08-14 --- .../features/disclaimer/impl/ui/DisclaimerScreen.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt index b3740659cc..208feec00e 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt @@ -59,7 +59,11 @@ internal fun DisclaimerScreen(state: DisclaimerUM) { .background(backgroundColor) .statusBarsPadding(), ) { - Column(modifier = Modifier.padding(bottom = bottomPadding)) { + Column( + modifier = Modifier + .padding(bottom = bottomPadding) + .fillMaxSize(), + ) { TangemTopAppBar( title = resourceReference(R.string.disclaimer_title), startButton = TopAppBarButtonUM( @@ -110,6 +114,7 @@ private fun DisclaimerContent(url: String, isTosAccepted: Boolean) { it.setBackgroundColor(backgroundColor.toArgb()) }, client = remember { DisclaimerWebViewClient() }, + modifier = Modifier.fillMaxSize(), ) AnimatedVisibility( @@ -117,6 +122,9 @@ private fun DisclaimerContent(url: String, isTosAccepted: Boolean) { label = "Loading state change animation", enter = fadeIn(), exit = fadeOut(), + modifier = Modifier + .fillMaxSize() + .background(backgroundColor), ) { Box( modifier = Modifier From 5ef6a4167db23f8a0357efded0bd7c99f4da58a8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 29 Jul 2024 15:32:22 +0300 Subject: [PATCH 23/53] Updated on 2026-08-14 --- .../features/onboarding/OnboardingHelper.kt | 11 +++++----- .../products/note/OnboardingNoteFragment.kt | 9 +++++++- .../note/redux/OnboardingNoteReducer.kt | 2 +- .../note/redux/OnboardingNoteState.kt | 8 ++----- .../products/twins/redux/TwinCardsState.kt | 6 ----- .../twins/ui/OnboardingTwinsFragment.kt | 22 +++++++++++++------ .../exchangeServices/BuyExchangeService.kt | 4 +++- .../exchangeServices/CardExchangeRules.kt | 5 +++-- .../CurrencyExchangeManager.kt | 6 +++-- .../exchangeServices/DefaultRampManager.kt | 4 +++- .../exchangeServices/ExchangeService.kt | 7 +++--- .../mercuryo/MercuryoService.kt | 3 ++- .../moonpay/MoonPayService.kt | 3 ++- .../domain/exchange/RampStateManager.kt | 3 ++- .../tokens/GetCryptoCurrencyActionsUseCase.kt | 7 +++--- 15 files changed, 59 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index d6c4adff57..404a469233 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -32,26 +32,27 @@ import timber.log.Timber */ object OnboardingHelper { suspend fun isOnboardingCase(response: ScanResponse): Boolean { - val onboardingManager = store.state.globalState.onboardingState.onboardingManager + val onboardingManager = + store.state.globalState.onboardingState.onboardingManager ?: OnboardingManager(response) val cardId = response.card.cardId return when { response.cardTypesResolver.isTangemTwins() -> { if (!response.twinsIsTwinned()) { true } else { - onboardingManager?.isActivationInProgress(cardId) ?: false + onboardingManager.isActivationInProgress(cardId) ?: false } } response.cardTypesResolver.isWallet2() || response.cardTypesResolver.isShibaWallet() -> { val emptyWallets = response.card.wallets.isEmpty() - val activationInProgress = onboardingManager?.isActivationInProgress(cardId) + val activationInProgress = onboardingManager.isActivationInProgress(cardId) val isNoBackup = response.card.backupStatus == CardDTO.BackupStatus.NoBackup && !DemoHelper.isDemoCard(response) - emptyWallets || activationInProgress == true || isNoBackup + emptyWallets || activationInProgress || isNoBackup } - response.card.wallets.isNotEmpty() -> onboardingManager?.isActivationInProgress(cardId) ?: false + response.card.wallets.isNotEmpty() -> onboardingManager.isActivationInProgress(cardId) ?: false else -> true } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt index 7ac3806126..757ec91454 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt @@ -11,12 +11,14 @@ import coil.load import com.tangem.blockchain.common.Blockchain import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.ShareElement +import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget import com.tangem.tap.common.transitions.InternalNoteLayoutTransition import com.tangem.tap.features.addBackPressHandler +import com.tangem.tap.features.onboarding.OnboardingWalletBalance import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState @@ -134,7 +136,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } private fun setupTopUpWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) { - if (state.isBuyAllowed) { + if (availableForBuy(state.scanResponse, state.walletBalance)) { btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto) btnMainAction.icon = null btnMainAction.setOnClickListener { @@ -217,6 +219,11 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } } + private fun availableForBuy(scanResponse: ScanResponse?, walletBalance: OnboardingWalletBalance): Boolean { + scanResponse ?: return false + return store.state.globalState.exchangeManager.availableForBuy(scanResponse, walletBalance.currency) + } + override fun handleOnBackPressed() { store.dispatch(OnboardingNoteAction.OnBackPressed) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt index 5552fc8b50..14f7989391 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt @@ -13,7 +13,7 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingNoteSt when (action) { is GlobalAction.Onboarding.Start -> { - state = OnboardingNoteState() + state = OnboardingNoteState(scanResponse = action.scanResponse) } is OnboardingNoteAction.SetArtworkUrl -> { state = state.copy(cardArtworkUrl = action.artworkUrl) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt index ae7a2db594..3d659e39da 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt @@ -1,11 +1,10 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.blockchain.common.WalletManager +import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.domain.TapError import com.tangem.tap.features.onboarding.OnboardingWalletBalance -import com.tangem.tap.store import org.rekotlin.StateType -import kotlin.properties.ReadOnlyProperty /** [REDACTED_AUTHOR] @@ -20,14 +19,11 @@ data class OnboardingNoteState( val currentStep: OnboardingNoteStep = OnboardingNoteStep.None, val steps: List = OnboardingNoteStep.values().toList(), val showConfetti: Boolean = false, + val scanResponse: ScanResponse? = null, ) : StateType { val progress: Int get() = steps.indexOf(currentStep) - - val isBuyAllowed: Boolean by ReadOnlyProperty { _, _ -> - store.state.globalState.exchangeManager.availableForBuy(walletBalance.currency) - } } enum class OnboardingNoteStep { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt index fcc38388ac..a8867a1a2c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt @@ -6,9 +6,7 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.domain.TapError import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.features.onboarding.OnboardingWalletBalance -import com.tangem.tap.store import org.rekotlin.StateType -import kotlin.properties.ReadOnlyProperty /** [REDACTED_AUTHOR] @@ -56,10 +54,6 @@ data class TwinCardsState( val twinningInProgress: Boolean get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet - - val isBuyAllowed: Boolean by ReadOnlyProperty { _, _ -> - store.state.globalState.exchangeManager.availableForBuy(walletBalance.currency) - } } enum class CreateTwinWalletMode { CreateWallet, RecreateWallet } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt index e1e0a58536..69db8e0120 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt @@ -27,6 +27,7 @@ import com.tangem.tap.common.transitions.InternalNoteLayoutTransition import com.tangem.tap.domain.twins.TwinsCardWidget import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.onboarding.OnboardingMenuProvider +import com.tangem.tap.features.onboarding.OnboardingWalletBalance import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction @@ -65,12 +66,7 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment( } override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider( - scanResponseProvider = Provider { - store.state.twinCardsState.welcomeOnlyScanResponse - ?: store.state.globalState.onboardingState.onboardingManager?.scanResponse - ?: store.state.detailsState.scanResponse - ?: error("ScanResponse must be not null") - }, + scanResponseProvider = Provider { getActualScanResponse() }, ) @Suppress("MagicNumber") @@ -342,7 +338,7 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment( else -> {} } - if (state.isBuyAllowed) { + if (availableForBuy(getActualScanResponse(), state.walletBalance)) { btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto) btnMainAction.setOnClickListener { store.dispatch(TwinCardsAction.TopUp) @@ -424,6 +420,18 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment( } } + private fun availableForBuy(scanResponse: ScanResponse?, walletBalance: OnboardingWalletBalance): Boolean { + scanResponse ?: return false + return store.state.globalState.exchangeManager.availableForBuy(scanResponse, walletBalance.currency) + } + + private fun getActualScanResponse(): ScanResponse { + return store.state.twinCardsState.welcomeOnlyScanResponse + ?: store.state.globalState.onboardingState.onboardingManager?.scanResponse + ?: store.state.detailsState.scanResponse + ?: error("ScanResponse must be not null") + } + override fun handleOnBackPressed() { store.dispatch( TwinCardsAction.OnBackPressed { should, popAction -> diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt index e6c523e666..a8861eed2f 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt @@ -1,5 +1,6 @@ package com.tangem.tap.network.exchangeServices +import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.tap.domain.model.Currency import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService @@ -32,7 +33,8 @@ internal class BuyExchangeService( override fun isSellAllowed(): Boolean = currentService.isSellAllowed() - override fun availableForBuy(currency: Currency): Boolean = currentService.availableForBuy(currency) + override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = + currentService.availableForBuy(scanResponse, currency) override fun availableForSell(currency: Currency): Boolean = currentService.availableForSell(currency) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt index 0f4fd42f7d..696f9288bc 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt @@ -2,6 +2,7 @@ package com.tangem.tap.network.exchangeServices import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.domain.model.Currency import com.tangem.tap.features.demo.isDemoCard @@ -38,8 +39,8 @@ class CardExchangeRules( } } - override fun availableForBuy(currency: Currency): Boolean { - val card = cardProvider() ?: return false + override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean { + val card = scanResponse.card return when { card.isDemoCard() -> true diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt index e962f7ebf4..fb01befe82 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt @@ -8,6 +8,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.safeUpdate @@ -38,8 +39,9 @@ class CurrencyExchangeManager( override fun isBuyAllowed(): Boolean = primaryRules.isBuyAllowed() && buyService.isBuyAllowed() override fun isSellAllowed(): Boolean = primaryRules.isSellAllowed() && sellService.isSellAllowed() - override fun availableForBuy(currency: Currency): Boolean { - return primaryRules.availableForBuy(currency) && buyService.availableForBuy(currency) + override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean { + return primaryRules.availableForBuy(scanResponse, currency) && + buyService.availableForBuy(scanResponse, currency) } override fun availableForSell(currency: Currency): Boolean { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index 76de7375da..904e22c80e 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -1,13 +1,15 @@ package com.tangem.tap.network.exchangeServices import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency class DefaultRampManager(private val exchangeService: ExchangeService?) : RampStateManager { private val cryptoCurrencyConverter = CryptoCurrencyConverter() - override fun availableForBuy(cryptoCurrency: CryptoCurrency): Boolean { + override fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean { return exchangeService?.availableForBuy( + scanResponse, currency = cryptoCurrencyConverter.convertBack(cryptoCurrency), ) ?: false } diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt index e9b17488e3..1cfd8502ed 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt @@ -1,5 +1,6 @@ package com.tangem.tap.network.exchangeServices +import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.tap.common.feature.Feature import com.tangem.tap.domain.model.Currency @@ -7,7 +8,7 @@ import com.tangem.tap.domain.model.Currency interface Exchanger { fun isBuyAllowed(): Boolean fun isSellAllowed(): Boolean - fun availableForBuy(currency: Currency): Boolean + fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean fun availableForSell(currency: Currency): Boolean } @@ -20,7 +21,7 @@ interface ExchangeService : Feature, Exchanger, ExchangeUrlBuilder { override suspend fun update() {} override fun isBuyAllowed(): Boolean = false override fun isSellAllowed(): Boolean = false - override fun availableForBuy(currency: Currency): Boolean = false + override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false override fun availableForSell(currency: Currency): Boolean = false override fun getUrl( action: CurrencyExchangeManager.Action, @@ -45,7 +46,7 @@ interface ExchangeRules : Feature, Exchanger { override fun featureIsSwitchedOn(): Boolean = false override fun isBuyAllowed(): Boolean = false override fun isSellAllowed(): Boolean = false - override fun availableForBuy(currency: Currency): Boolean = false + override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false override fun availableForSell(currency: Currency): Boolean = false } } diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt index d653e2babe..8bcc8168e9 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt @@ -6,6 +6,7 @@ import com.tangem.common.extensions.calculateSha512 import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.common.services.performRequest +import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.tap.domain.model.Currency import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager @@ -28,7 +29,7 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E override fun isSellAllowed(): Boolean = false - override fun availableForBuy(currency: Currency): Boolean { + override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean { if (!isBuyAllowed()) return false val mercuryoNetwork = currency.blockchain.mercuryoNetwork() diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt index c004c38b21..de34810420 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -7,6 +7,7 @@ import com.tangem.common.services.Result import com.tangem.common.services.performRequest import com.tangem.datasource.api.common.createRetrofitInstance import com.tangem.domain.common.extensions.withIOContext +import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.tap.domain.model.Currency import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager @@ -83,7 +84,7 @@ class MoonPayService( return status?.responseUserStatus?.isSellAllowed ?: false } - override fun availableForBuy(currency: Currency): Boolean = false + override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false override fun availableForSell(currency: Currency): Boolean { if (!isSellAllowed()) return false diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt index ac9b4fa892..2536d78ea9 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt @@ -1,5 +1,6 @@ package com.tangem.domain.exchange +import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency /** @@ -7,7 +8,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency */ interface RampStateManager { - fun availableForBuy(cryptoCurrency: CryptoCurrency): Boolean + fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean fun availableForSell(cryptoCurrency: CryptoCurrency): Boolean } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 16083adcbb..c83af879ba 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -106,7 +106,7 @@ class GetCryptoCurrencyActionsUseCase( return listOf(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None)) } if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Unreachable) { - return getActionsForUnreachableCurrency(cryptoCurrencyStatus, needAssociateAsset) + return getActionsForUnreachableCurrency(userWallet, cryptoCurrencyStatus, needAssociateAsset) } val activeList = mutableListOf() @@ -168,7 +168,7 @@ class GetCryptoCurrencyActionsUseCase( } // buy - if (rampManager.availableForBuy(cryptoCurrency)) { + if (rampManager.availableForBuy(userWallet.scanResponse, cryptoCurrency)) { activeList.add(TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.None)) } else { disabledList.add( @@ -222,6 +222,7 @@ class GetCryptoCurrencyActionsUseCase( } private fun getActionsForUnreachableCurrency( + userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus, needAssociateAsset: Boolean, ): List { @@ -230,7 +231,7 @@ class GetCryptoCurrencyActionsUseCase( if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { actionsList.add(TokenActionsState.ActionState.CopyAddress(ScenarioUnavailabilityReason.None)) } - if (rampManager.availableForBuy(cryptoCurrencyStatus.currency)) { + if (rampManager.availableForBuy(userWallet.scanResponse, cryptoCurrencyStatus.currency)) { actionsList.add(TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.None)) } else { actionsList.add( From 297eb15571612408957c03718be826dff0a01514 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 30 Jul 2024 13:18:35 +0300 Subject: [PATCH 24/53] Updated on 2026-08-14 --- .../datasource/api/tangemTech/TangemTechApi.kt | 1 + .../tangem/data/promo/DefaultPromoRepository.kt | 16 +++++++--------- .../repository/DefaultCurrenciesRepository.kt | 16 +++------------- ...TokenDetailsSwapTransactionsStateConverter.kt | 3 ++- 4 files changed, 13 insertions(+), 23 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index eb9e2a434c..9fe41bf3a5 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -18,6 +18,7 @@ interface TangemTechApi { @Query("contractAddress") contractAddress: String? = null, @Query("exchangeable") exchangeable: Boolean? = null, @Query("networkIds") networkIds: String? = null, + @Query("networkId") networkId: String? = null, @Query("active") active: Boolean? = null, @Query("searchText") searchText: String? = null, @Query("offset") offset: Int? = null, diff --git a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt index e48f0e6933..94a184fb73 100644 --- a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt +++ b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt @@ -33,19 +33,17 @@ internal class DefaultPromoRepository( } override suspend fun getOkxPromoBanner(): PromoBanner? { - // TODO disabled for 5.12, enable for 5.12.1 - return null - // return runCatching(dispatchers.io) { - // promoResponseConverter.convert( - // tangemApi.getPromotionInfo(OKX) - // .getOrThrow(), - // ) - // }.getOrNull() + return runCatching(dispatchers.io) { + promoResponseConverter.convert( + tangemApi.getPromotionInfo(OKX) + .getOrThrow(), + ) + }.getOrNull() } private companion object { private const val CHANGELLY_NAME = "changelly" private const val TRAVALA = "travala" - // private const val OKX = "okx" + private const val OKX = "okx" } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 214d9721e1..653b731d38 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -429,22 +429,12 @@ internal class DefaultCurrenciesRepository( val token = withContext(dispatchers.io) { val foundToken = tangemTechApi.getCoins( contractAddress = contractAddress, - networkIds = networkId, + networkId = networkId, ) .getOrThrow() .coins - .firstNotNullOfOrNull { coin -> - val networksWithTheSameAddress = coin.networks.filter { network -> - (network.contractAddress != null || network.decimalCount != null) && - network.contractAddress?.equals(contractAddress, ignoreCase = true) == true - } - - if (networksWithTheSameAddress.isNotEmpty()) { - coin.copy(networks = networksWithTheSameAddress) - } else { - null - } - } ?: error("Token not found") + .firstOrNull() + ?: error("Token not found") val network = foundToken.networks.firstOrNull { it.networkId == networkId } ?: error("Network not found") CryptoCurrencyFactory.Token( symbol = foundToken.symbol, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt index 54d15b83ea..12a65e1e6b 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt @@ -142,9 +142,9 @@ internal class TokenDetailsSwapTransactionsStateConverter( txUrl: String?, refundToken: CryptoCurrency?, ): ExchangeStatusNotifications? { - if (txUrl == null) return null return when (status) { ExchangeStatus.Failed -> { + if (txUrl == null) return null ExchangeStatusNotifications.Failed { analyticsEventsHandlerProvider().send( TokenExchangeAnalyticsEvent.GoToProviderFail(cryptoCurrency.symbol), @@ -153,6 +153,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( } } ExchangeStatus.Verifying -> { + if (txUrl == null) return null ExchangeStatusNotifications.NeedVerification { analyticsEventsHandlerProvider().send( TokenExchangeAnalyticsEvent.GoToProviderKYC(cryptoCurrency.symbol), From f4b545f348bb92b4204d3aba165e752d06ebf288 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 30 Jul 2024 16:16:14 +0300 Subject: [PATCH 25/53] Updated on 2026-08-14 --- .../ui/components/SaveWalletScreenContent.kt | 23 +-- core/res/src/main/res/values-de/strings.xml | 148 ++++++++++++++++-- core/res/src/main/res/values-fr/strings.xml | 15 +- core/res/src/main/res/values-ja/strings.xml | 7 +- core/res/src/main/res/values-ru/strings.xml | 7 + .../src/main/res/values-uk-rUA/strings.xml | 74 ++++++++- .../src/main/res/values-zh-rTW/strings.xml | 8 +- core/res/src/main/res/values/strings.xml | 14 +- .../features/details/utils/ItemsBuilder.kt | 2 +- .../walletsettings/utils/ItemsBuilder.kt | 2 +- 10 files changed, 259 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt index c7679a466d..4206df1904 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt @@ -2,13 +2,7 @@ package com.tangem.tap.features.saveWallet.ui.components import android.content.res.Configuration import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.* import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text @@ -20,16 +14,10 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.PrimaryButton -import com.tangem.core.ui.components.SpacerH16 -import com.tangem.core.ui.components.SpacerH32 -import com.tangem.core.ui.components.SpacerH4 -import com.tangem.core.ui.components.SpacerHHalf -import com.tangem.core.ui.components.SpacerW24 -import com.tangem.core.ui.components.SpacerW8 +import com.tangem.core.ui.components.* import com.tangem.core.ui.components.atoms.Hand -import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.wallet.R @Composable @@ -37,10 +25,7 @@ internal fun SaveWalletScreenContent(showProgress: Boolean, onSaveWalletClick: ( Column(horizontalAlignment = Alignment.CenterHorizontally) { Header(onCloseClick = onCloseClick) SpacerHHalf() - Title( - modifier = Modifier - .widthIn(max = TangemTheme.dimens.size200), - ) + Title(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing22)) SpacerH32() Description( modifier = Modifier diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 3f0832f3fb..12e30ed235 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1,8 +1,10 @@ + Netzwerk wählen Benutzerdefiniertes Token hinzufügen Token verwalten Sende nur %1$s ( %2$s ) vom %3$s -Netzwerk an diese Adresse. Die Verwendung anderer Token und Netzwerke kann zum Verlust von Geldern führen. + So scannt man Hilfe anfordern Erneut versuchen Diese Funktion ist im Demomodus deaktiviert @@ -21,7 +23,7 @@ Durch das Entfernen der gespeicherten Karte werden alle gespeicherten Wallets und deren Zugangscodes aus der App gelöscht. Zugangscode speichern Bei Interaktionen mit deiner Karte wird anstelle des Zugangscodes eine biometrische Authentifizierung abgefragt. - Behalten die Wallet in der App + Behalte die Wallet in der App Aktiviere die Verknüpfung aller Wallets mit der Tangem-App. Die biometrische Authentifizierung ist zum Entsperren der App erforderlich. Das Signieren von Transaktionen erfordert das Antippen deiner Tangem-Karte. Dunkel Hell @@ -37,6 +39,7 @@ Zu viele Versuche Du hast die biometrische Authentifizierung auf deinem Telefon deaktiviert und kannst keine Wallets in der App speichern. Um Wallets zu speichern, aktiviere bitte die biometrische Authentifizierung in deinen Telefoneinstellungen. Backup-Prozess starten + Von deiner Fiat-Karte oder deinem Bankkonto %d Karte %d Karten @@ -65,6 +68,8 @@ Nicht genug ADA Akzeptieren Zugang verweigert + Alle + Erlauben Anwenden Genehmigung Genehmigen @@ -77,12 +82,17 @@ Gehe zu %1$s D hast keinen Zugang zur Kamera erteilt, bitte passe deine Datenschutzeinstellungen an Abbrechen + Stakingbelohnungen beanstpruchen Schließen Weitermachen Kopieren Adresse kopieren Erstellen Benutzerdefiniert + + Tag + Tage + Entfernen Deaktiviert Erledigt @@ -100,6 +110,7 @@ Geschwindigkeit und Gebühr Adressen abrufen Zum Anbieter gehen + Zum Token Importieren Später Gesperrt @@ -109,6 +120,7 @@ Weiter Nein Keine Adresse + Jetzt OK Primär Karte Passphrase @@ -139,12 +151,14 @@ Unterstützung Tauschen Allgemeine Geschäftsbedingungen + Heute Transaktion fehlgeschlagen Transaktionen - Überweisen + Überweisung Ich verstehe Es ist ein Fehler aufgetreten. Bitte versuche es erneut. Nicht erreichbar + staking beenden Ja Vertragsadresse kopiert! Verfügbare Netzwerke @@ -212,6 +226,8 @@ Getauscht von %s Besuche die Website des Anbieters, um dein Geld zurückzuerhalten Fehler beim Vorgang durch Anbieter + Der Transaktionsbetrag wurde aufgrund von OKX- oder Bridge-Regeln in %1$s auf deine Wallet zurückerstattet. %2$s + Der Betrag wurde in %1$s (%2$s Netzwerk) zurückerstattet. Besuche die Website des Anbieters zur Überprüfung KYC-Überprüfung durch den Anbieter erforderlich Abgebrochen @@ -232,6 +248,7 @@ Daten stammen vom Anbieter. Der geschätzte Betrag kann sich aufgrund der Marktbedingungen ändern. Status des Tausches Verifizierung erforderlich + Wartet auf Transaktions-Hash Liste aller Token, die deiner Wallet hinzugefügt wurden Beste Preise werden abgerufen … Variabler Zinssatz @@ -245,10 +262,12 @@ Erhältlich bei %s Für dieses Paar nicht verfügbar Erlaubnis erforderlich + Empfohlen Nutzungsbedingungen Keine Token gefunden. Bitte versuche eine andere Anfrage ID: %s Transaktions-ID kopiert + Aus einer anderen Währung in deiner Wallet Die folgenden Angaben sind freiwillig. Du kannst diese löschen, wenn du sie nicht weitergeben möchtest. Teile uns mit, welche Funktionen du vermisst, und wir werden versuchen, dir zu helfen. Bitte sag uns, welche Karte du hast @@ -263,6 +282,8 @@ Das Netzwerk erhebt eine Token-Genehmigungsgebühr, um zu überprüfen, ob Sie die Verwendung Ihres Tokens für den Swap genehmigen. Gib das Genehmigungslimit für das ausgewählte Token an Betrag %s + Die Genehmigungsfunktion ist erforderlich, um einer anderen Adresse die Berechtigung zur Verwendung einer bestimmten Menge Ihrer Token zu erteilen. Standardmäßig können Smart Contracts nicht auf deine Token zugreifen, es sei denn, du stimmen zu. Indem du deine Token \"freischaltest\", autorisierst du den StakeKit Smart Contract, sie zu verwenden. Die Miner des Netzwerks erhalten eine Gasgebühr (von dir bezahlt), um diese Aktion in der Blockchain aufzuzeichnen. Du kannst deine Token einsetzen, nachdem du die Genehmigung erteilt hast. + Um fortzufahren, musst du StakeKit Smart Contract erlauben, deine %s zu verwenden Um fortzufahren, erteile %1s Smart Contracts die Berechtigung, dein zu %2s verwenden. Erlaubnis erteilen Unbegrenzt @@ -306,11 +327,85 @@ %1$d von %2$d Wallet %1$d von %2$d Wallets + Entfernen z.B. BTC vertraue ich, hodl muss ich + Dein Portfolio wurde aktualisiert Der ausgewählte Token ist derzeit nicht für Aktionen innerhalb der Krypto-Wallet verfügbar. Aber keine Sorge, du kannst dein Interesse bekunden, indem du den Token hochstufen. Hochstimmen Wallet auswählen Das Wallet unterstützt nicht mehr als ein Netzwerk + Um mit dem Kauf, Tausch oder Erhalt dieses Vermögenswerts zu beginnen, füge diesen Token zu mindestens 1 Netzwerk hinzu + Dieses Asset ist nicht verfügbar + Zum Portfolio hinzufügen + Token hinzufügen + Verfügbare Netzwerke + Mein Portfolio + Markt + Um Adressen für ausgewählte Netzwerke zu generieren, musst du eine Tangem-Karte scannen/ einsetzen + Die Daten konnten nicht geladen werden... + Schnelle Aktionen + Ergebnis + Token unter 100k Marktkapitalisierung anzeigen + Token anzeigen + Kein Ergebnis + Netzwerk auswählen + Wallet auswählen + 1M + 1 Y + 24H + 3M + 6M + 7T + Alle + Erfahrene Käufer + Bewertung + Sortieren nach + Top-Gewinner + Top-Verlierer + Beliebt + Über %s + + Bewertung, basierend auf %d + Bewertungen, basierend auf %d + + Blockchain-Site + Kaufdruck + Die Differenz zwischen Käufer- und Verkäufervolumen + Umlaufmenge + Die Gesamtzahl der Coins, die für den Handel verfügbar sind und auf dem Markt zirkulieren + Erfahrene Käufer + Nettokäufer mit der zusätzlichen Anforderung, mindestens 100 ausgehende Transaktionen zu haben + Vollständig verwässerte Bewertung + Der theoretische Gesamtwert einer Kryptowährung, wenn alle Coins, die existieren könnten, im Umlauf sind, einschließlich derjenigen, die derzeit nicht im Umlauf sind + Entstehungsdatum + Leer + Hoch + Inhaber/ Halter + Die Änderung der Anzahl der Token-Inhaber innerhalb eines bestimmten Zeitraums + Einblicke + Links + Liquidität + Die Änderung der Liquidität, die dem Token während des angegebenen Zeitraums zur Verfügung steht + Liquiditätsindex + Leer + Niedrig + Marktkapitalisierung + Der Gesamtmarktwert einer Kryptowährung, berechnet durch Multiplikation des aktuellen Preises der Münze mit der Gesamtzahl der im Umlauf befindlichen Münzen + Marktbewertung + Position im Krypto-Rating zwischen allen Coins basierend auf der Marktkapitalisierung + Maximale Versorgung + Leer + Metriken + Offizielle Links + Preisleistung + Aufbewahrungsort + Sicherheitsbewertung + Leer + Soziales + Gesamtangebot + Die maximale Anzahl von Coins oder Tokens, die jemals für eine bestimmte Kryptowährung existieren können + Handelsvolumen (24h) + Der Gesamtbetrag einer Kryptowährung, der innerhalb der letzten 24 Stunden gehandelt wurde, wobei das Aktivitäts- und Liquiditätsniveau auf dem Markt angegeben wird Du musst einen einzigen Zugangscode einrichten, um alle deine Karten zu schützen Schützen Du kannst später auf jeder Karte einen individuellen Zugangscode einrichten @@ -334,7 +429,7 @@ Jetzt sichern Scannen der Hauptkarte Weiter zu meiner Wallet - Abschließen der Sicherung + Backup abschließen Krypto empfangen Primärkarte scannen Für später überspringen @@ -344,7 +439,7 @@ Erstelle eine Wallet Andere Optionen Deine Schlüssel(private-keys) werden sicher im Inneren der Karte generiert. Es gibt keine Seed-Phrase, d. h. niemand kann sie exportieren oder stehlen. - Schlüssel privat generieren + Schlüssel anonym generieren Deine Karte ist aktiviert und einsatzbereit Erfolgreich! In diesem Fall musst du ganz von vorne anfangen. @@ -369,19 +464,20 @@ Seed-Phrase verwenden Ungültige Seed-Phrase. Bitte überprüfe die Wortreihenfolge. Ungültige Seed-Phrase. Bitte überprüfe die Rechtschreibung. - Altbestand + veralteter Standard Um zu überprüfen, ob du deine Seed-Phrase richtig aufgeschrieben hast, gib bitte das 2., 7. und 11 Wort ein. - Also, lass uns das überprüfen + Eine letzte Prüfung! Um den Sicherungsvorgang zu starten, füge bis zu zwei Sicherungskarten hinzu. Du kannst eine weitere Karte hinzufügen oder den Sicherungsvorgang abschließen Bereite die Sicherungskarte mit der Nummer %s vor. Scanne die primär-Karte, um den Sicherungsvorgang zu starten. Bereite die primäre Karte mit der Nummer %s vor. - Deine Wallet-Karte ist konfiguriert und einsatzbereit. + Deine Tangem-Karte ist konfiguriert und einsatzbereit. Maximale Anzahl an Karten hinzugefügt. Schließe den Sicherungsvorgang ab. Karte aktivieren Sicherungskarte Nr. %d Keine Sicherungskarten + Benachrichtigungen Eine Backup-Karte hinzugefügt Bereite deine Karte vor Zwei Backup-Karten hinzugefügt @@ -410,6 +506,7 @@ Kamerazugriff verweigert %1$s ( %2$s ) im %3$s Netzwerk Sende nur %s an diese Adresse. Der Versand einer anderen Währung führt zu ihrem unwiderruflichen Verlust. + QR-Code anzeigen oder Adresse teilen Teilnehmen Die Informationen zum Empfehlungsprogramm konnten nicht geladen werden. Bitte versuche es später noch einmal. Die Informationen über das Empfehlungsprogramm konnten nicht geladen werden. Fehlercode: %s. Bitte versuche es später noch einmal. @@ -470,6 +567,8 @@ Dies sind die Kosten, die du für jede Gaseinheit zu zahlen bereit bist. Je höher der Gaspreis ist, desto schneller wird deine Transaktion bearbeitet. (Vorzugsgebühr inbegriffen) Prioritätsgebühr Die Gebühr, die ein Nutzer an Miner oder Validierer zahlen kann, um die Aufnahme seiner Transaktion in einen Block zu beschleunigen. + Die Gebühr, die für die Nutzung jeder nicht ausgegebenen Transaktionsausgabe (UTXO) im Kaspa-Netzwerk erforderlich ist. Je mehr UTXOs du in einer Transaktion verwenden, desto höher ist die Gebühr. + KAS per UTXO %1$s, %2$s Adresse Ziel-Tag @@ -530,31 +629,53 @@ Berühre an beliebiger Stelle für Änderungen Versende %s Du sendest **%1$s** inklusive der Netzwerkgebühr %2$s - Du sendest ** %1$s ** und %2$s + Du sendest **%1$s** und %2$s Senden %s Gesamt %1$s und %2$s werden gesendet ≈ %1$s (inkl. Gebühr: %2$s) %s wird gesendet Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert + %1$s ist ein Vermögenswert im Tron-Netzwerk. Um die Gebühr zu berechnen und eine Transaktion durchzuführen, musst du etwas Tron (TRX) auf deinem Konto einzahlen. Ungültige Adresse %1$s (%2$s) Transaktion gesendet + Bereite das Scannen der Karte vor, die du einrichten möchtest. Entferne diese Wallet Hiermit wird die Wallet aus der Anwendung entfernt. Die Wallet selbst kann wieder hinzugefügt werden. Name + Aktiv + Um deine Kryptos zu unstaken, klick hier. + Die Anzahl der zu stakenden Krypros muss mindesten %s betragen + Effektiver Jahreszins + Jährliche prozentuale Rendite + Die jährliche prozentuale Rendite, die du durch die Teilnahme am Staking erzielen kannst. Verfügbar Durchschnittliche Belohnungsquote %s geschätzter Profit + Marktbewertung + Metriken Mindestanforderungen Keine Belohnungen zu beanspruchen. Gestaked Belohnungen beanspruchen + Eine Möglichkeit, Staking-Belohnungen zu erhalten. Es kann automatisch oder manuell beansprucht werden. Belohnungszeitplan + Dabei handelt es sich um einen Zeitplan, der festlegt, wann die Teilnehmer am Staking ihre Belohnungen erhalten. + Belohnungen, die du beanspruchen kannst: %s Staking %s Entbindungsdauer + Der Zeitraum, den du nach der Beantragung der Abhebung von Geldern aus dem Staking warten musst, bevor die Token verfügbar werden. Aufwärmphase + Die zugewiesene Zeit für die Aktivierung der Teilnahme am Staking. + Natives Staking + Mit Staking kannst du %1s verdienen. Deine Staking-Belohnungen kommen alle ~%2s Tage. + Verdiene Staking-Belohnungen Belohnungen + Mehr staken + unstaken + Prüfe, was nicht eingesetzt wurde, um dein Vermögen zu beanspruchen + Validator/ Prüfer Bewahre deine Krypto-Assets sicher auf, während die privaten Schlüssel auf deiner Karte bleiben Revolutionäre Hardware-Wallet Bis zu 3 physische Karten pro Wallet @@ -566,11 +687,14 @@ Lerne Tangem kennen Tausche, kaufen Sie NFTs, vergebe Kredite und tätige Einlagen bei mehr als 100 verschiedenen dezentralen Diensten Web 3.0-kompatibel + Tausche mehr Token zu besseren Kursen direkt in deiner Brieftasche. + Neuer Swap-Anbieter verfügbar! Der Betrag umfasst:\n- Gebühr des Dienstanbieters\n- Netzgebühr für die Rücksendung von %s von der Vermittlungsstelle an die Adresse des Nutzers. Der Betrag enthält die Gebühren des Dienstleisters. Gebühren Alle dezentralen Börsen benötigen Genehmigungen, um zu verhindern, dass intelligente Verträge ohne Ihre Erlaubnis auf Ihre Geldbörse zugreifen. Smart Contracts können nicht auf Ihre Token zugreifen, wenn Sie nicht zustimmen. Indem Sie Ihre Token \"freischalten\", ermächtigen Sie den 1-Zoll-Smart-Contract, sie auszugeben. Die Miner des Netzwerks erhalten eine (von Ihnen bezahlte) Gasgebühr, um diese Aktion in der Blockchain aufzuzeichnen. Sie können Ihre Token tauschen, nachdem Sie Ihre Zustimmung gegeben haben. Genehmigen + Fehler bei der Gebührenschätzung. Bitte sende dein Feedback an den Support. Du wechselst Der Tausch dieser Menge ausgewählter Token hat erhebliche Auswirkungen auf den Preis und verringert dein Ergebnis. Unzureichende Mittel @@ -614,6 +738,7 @@ Operation von: %s zu: %s + Versuche es erneut Du hast dieselbe Karte gescannt. Um ein Zwillings-Wallet zu erstellen, musst du die Karte mit der Nummer %d scannen. Du hast die falsche Doppelkarte gescannt. Bitte versuche eine andere Karte Die, die du in den Hand hältst, und die andere mit der Nummer %s.\n\nBeide Karten können verwendet werden, um Geld aus dieser Wallet zu versenden. @@ -626,6 +751,9 @@ Diese Aktion ist unumkehrbar. Du hast keinen Zugriff mehr auf die alte Wallet. Tippe auf die Doppelkarte mit der Nummer %s und entferne sie erst am Ende des Vorgangs. Verwende %s oder scanne eine Karte, um Zugriff auf deine Wallet zu erhalten. + Bleib auf dem Laufenden mit den neuesten Funktionen und Neuigkeiten + Sei der Erste, der von neuen Aktionen erfährt + Möchtest du Push-Benachrichtigungen verwenden? Neues Wallet hinzufügen Möchtest du diese Wallet wirklich löschen? Es ist ein Fehler aufgetreten, bitte scanne deine Karte, um sich anzumelden @@ -667,6 +795,7 @@ WalletConnect-Sitzungen Mit dApps verbinden WalletConnect + Das Herstellen der Verbindung kann einige Sekunden dauern %s Marktpreis letzte 24h %s Netzwerk @@ -675,12 +804,13 @@ Wallet-Einstellungen Tangem Verwende %s oder scanne eine Karte, um den Zugriff auf deine Wallet freizuschalten. + Es scheint, dass die Aktivierung der Karte nicht korrekt abgeschlossen wurde. Dies kann an einem Problem mit dem NFC-Modul deines Gerätes oder an einem falschen Tippen der Karte auf dein Gerät liegen. Bitte wende dich an unser Support-Team, um Unterstützung zu erhalten. Aktivierungsfehler Laut den Entwicklern des BNB-Netzes wird die Unterstützung für den BEP-2-Standard im Juni 2024 enden. Um den Verlust von Vermögenswerten mit diesem Standard zu vermeiden, konvertiere bitte in den BEP-20 Standard. Nutze gerne unseren Swap-Service, um sie auf das BNB Smart Chain Netzwerk zu übertragen. BNB Beacon Chain wird abgeschaltet Könnte besser sein Gefällt mir - OK, ich hab\'s! + OK, habe ich verstanden! Echt toll! Aktualisieren Du befindest sich derzeit im Demo-Modus diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index a07a86a3ab..8a104e2052 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -87,6 +87,10 @@ Copier l\'adresse Créer Personnalisé + + %d jour + %d jours + Supprimer Désactivé Exécuté @@ -272,8 +276,8 @@ Pour continuer, accordez aux smart contracts de %1s l\'autorisation d\'utiliser votre %2s Donner l\'autorisation Illimité - Commander une carte - Scannez la carte + Commandez + Scannez Touchez, pour modifier le code d\'accès Touchez, pour modifier le mot de passe Pour créer le portefeuille, appuyez sur la carte comme indiqué ci-dessus et ne la retirez pas jusqu\'à la fin de l\'opération @@ -324,6 +328,7 @@ Ajouter au portfolio Mon portfolio Marché + Pour générer des adresses pour les réseaux sélectionnés, vous devez scanner votre carte Tangem. Sélectionnez un portefeuille Trier par Idées @@ -490,6 +495,8 @@ Il s\'agit du coût que vous êtes prêt à payer pour chaque unité de gaz. Plus le prix du gaz est élevé, plus votre transaction sera traitée rapidement. (Frais de priorité inclus) Frais de priorité Les frais qu\'un utilisateur peut payer aux mineurs ou aux validateurs pour accélérer l\'inclusion de leur transaction dans un bloc. + Les frais requis pour l\'utilisation de chaque sortie de transaction non dépensée (UTXO) dans le réseau Kaspa. Plus vous utilisez d’UTXO dans une transaction, plus les frais seront élevés. + KAS pour UTXO %1$s, %2$s Adresse Destination Tag @@ -581,7 +588,7 @@ Un moyen de recevoir des récompenses de staking. Il peut être réclamé automatiquement ou manuellement. Calendrier de récompenses Il s\'agit d\'un calendrier qui détermine le moment où les participants au staking reçoivent leurs récompenses. - Récompenses à réclamer : %s + Récompenses à réclamer: %s Staking %s Période de détachement La période que vous devez attendre après avoir demandé le retrait des fonds du staking avant que les jetons ne soient disponibles. @@ -667,6 +674,7 @@ Appuyez sur la carte jumelle avec le numéro %s et ne la retirez pas jusqu\'à la fin de l\'opération Utilisez %s ou scannez une carte pour avoir accès à votre portefeuille Restez à jour avec les dernières fonctionnalités et actualités + Soyez le premier informé des nouvelles promotions Souhaitez-vous utiliser les notifications push? Ajouter un nouveau portefeuille Êtes-vous sûr de vouloir supprimer ce portefeuille ? @@ -718,6 +726,7 @@ Paramètres du portefeuille Tangem Utilisez %s ou scannez une carte pour déverrouiller l\'accès à votre portefeuille + Il semble que l\'activation de la carte ne se soit pas déroulée correctement. Cela peut être dû à un problème avec le module NFC de votre appareil ou à une mauvaise connexion de la carte sur votre appareil. Veuillez contacter notre équipe de support pour obtenir de l’aide. Erreur d\'activation Selon les développeurs du réseau BNB, le support de la norme BEP-2\nprendra fin en juin 2024. Pour éviter de perdre des actifs avec cette norme, veuillez les convertir à la norme BEP-20. Utilisez notre service de d\'échange pour les transférer sur le réseau BNB Smart Chain. BNB Beacon Chain va s\'arrêter de fonctionner diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index e004bcbd28..a9ecf96c93 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -108,6 +108,7 @@ 速度と料金 アドレスを取得する プロバイダーへ移動 + トークンへ移動 インポート 後で ロックされています @@ -223,6 +224,8 @@ %sによる交換 返金を受けるには、プロバイダーのウェブサイトにアクセスしてください。 プロバイダーによる操作が失敗しました。 + OKXまたはブリッジのルールにより、取引金額は%1$sでウォレットに返金されました。%2$s + 金額は %1$s(%2$sネットワーク)で返金されました 確認するには、プロバイダーのウェブサイトにアクセスしてください。 プロバイダーによる本人確認手続きが必要です。 キャンセルされました @@ -613,7 +616,7 @@ 送金中... 変更するには任意の箇所をタップしてください %sを送金する - ** %1$s ** を送金する (ネットワーク手数料%2$sを含む) + **%1$s** を送金する (ネットワーク手数料%2$sを含む) **%1$s** と %2$s を送金しています。 %sを送信しています 合計 @@ -621,6 +624,7 @@ ≈ %1$s (%2$s: 手数料を含む) %sが送信されます 取引は正常に署名され、ブロックチェーンノードに送信されました。ウォレットの残高はしばらくして更新されます。 + %1$sはTronネットワークのアセットです。手数料を計算して取引を行うには、アカウントにTron(TRX)を入金する必要があります。 無効なアドレス %1$s ( %2$s ) 取引が送信されました @@ -788,6 +792,7 @@ ウォレット設定 Tangem %sを使用するか、カードをスキャンしてウォレットにアクセスしてください + カードのアクティベーションが正しく完了しませんでした。デバイスの NFCモジュールに問題があるか、カードをデバイスに正しくタップしていないことが原因かもしれません。サポートチームにお問い合わせください。 アクティベーションに失敗しました BNBネットワーク開発者によると、BEP-2規格のサポートは2024年6月に終了します。この規格の資産を失わないために、BEP-20規格に変換してください。BNBスマートチェーンネットワークへ移行するには、Tangemのスワップサービスをご利用ください。 BNBビーコンチェーンは閉鎖されます。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index e67145ee33..8fef85b5aa 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -110,6 +110,7 @@ Скорость и комиссия Получить адреса К провайдеру + Перейти в токен Импортировать Позже Заблокирован @@ -124,6 +125,7 @@ Основная карта Парольная фраза Вставить + %1$s-%2$s Подробнее Получить Отклонить @@ -222,6 +224,8 @@ Обмен через %s Чтобы вернуть ваши деньги, посетите сайт провайдера Операция не выполнена провайдером + Отправленные средства были возвращены в %1$s на ваш кошелек в соответствии с правилами OKX или моста обмена. %2$s + Сумма была возвращена в %1$s (%2$s сети) Посетите сайт провайдера для проверки Провайдер запрашивает прохождение верификации Отменен @@ -326,6 +330,7 @@ Голосовать Выберите кошелек Кошелёк не поддерживает более одной сети + Чтобы создать адреса для выбранных сетей, необходимо отсканировать свой кошелек Tangem. Ссылки Метрики Вам необходимо установить единый код доступа для защиты всех ваших карт @@ -571,9 +576,11 @@ Забыть кошелек Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова. Имя + Сумма для стейкинга должна быть не менее %s APY Годовой процентный доход, который вы можете получить от участия в стейкинге. Доступно + %s Способ возраграждения Способ получения вознаграждений за стейкинг. Он может быть автоматическим или ручным. Период возрагражения 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 c6e93b561a..320017ffd8 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -39,6 +39,7 @@ Забагато спроб Ви вимкнули біометричну автентифікацію на своєму телефоні і не зможете зберігати гаманці в додатку. Щоб зберегти гаманці, будь ласка, увімкніть функцію біометричної автентифікації в налаштуваннях телефону. Почніть процес резервного копіювання + З вашої фіатної картки або банківського рахунку %d картка %d картки @@ -83,6 +84,7 @@ Перейдіть до %1$s Ви не надали доступ до камери, будь ласка, змініть налаштування конфіденційності Скасувати + Отримати винагороди Закрити Продовжити Копіювати @@ -112,6 +114,7 @@ Швидкість та комісія Отримати адреси Перейти до провайдера + Перейти до токену Імпортувати Пізніше Заблокований @@ -159,6 +162,7 @@ Я зрозумів Виникла помилка. Будь ласка, спробуйте ще раз. Недоступно + Скасувати стейкінг Так Адреса контракту скопійована! Доступні мережі @@ -226,6 +230,8 @@ Обмін через %s Щоб повернути ваші кошти, відвідайте сайт провайдера Операція не виконана провайдером + Сума транзакції була повернута в %1$s на ваш гаманець відповідно до правил OKX або мосту обміну. %2$s + Сума була повернута в %1$s (%2$s мережі) Відвідайте сайт провайдера для перевірки Провайдер вимагає проходження KYC верифікації Скасовано @@ -246,6 +252,7 @@ Дані провайдера. Орієнтовна сума може бути змінена через ринкові умови. Статус обміну Потрібна верифікація + Очікування хешу транзакції Список токенів у вашому гаманцю Шукаємо найвигідніший курс... Плаваюча ставка @@ -259,10 +266,12 @@ Доступно від %s Недоступно для цієї пари Потрібен дозвіл + Рекомендовано Умовами використання Токенів не знайдено. Будь ласка, спробуйте інший запит ID: %s ID транзакції скопійовано + З іншої валюти у вашому гаманці Інформація нижче не є обов\'язковою. Ви можете стерти її, якщо бажаєте. Розкажіть, яких функцій вам не вистачає, і ми спробуємо вам допомогти. Розкажіть, будь ласка, яку картку ви маєте? @@ -277,6 +286,8 @@ Мережа стягує комісію за схвалення токену за підтвердження, що саме ви дозволяєте використовувати ваш токен для обміну. Вкажіть ліміт доступу для обраного токена Кількість %s + Функція підтвердження необхідна для надання дозволу іншій адресі на використання певної кількості ваших токенів. За задумом, смарт-контракти не можуть отримати доступ до ваших токенів без вашого схвалення. \"Розблоковуючи\" свої токени, ви дозволяєте смарт-контракту StakeKit використовувати їх. Майнери мережі отримують плату за газ (сплачену вами), щоб зафіксувати цю дію в блокчейні. Ви зможете застейкати свій токен після того, як дасте дозвіл. + Щоб продовжити, вам потрібно дозволити смарт-контракту StakeKit використовувати ваш %s Щоб продовжити, вам потрібно надати дозвіл смарт-контракту %1s використовувати ваш %2s Надати дозвіл Необмежено @@ -332,20 +343,73 @@ Щоб почати купувати, обмінювати або отримувати цей актив, додайте цей токен принаймні в 1 мережу Цей актив недоступний Додати в портфоліо + Додати токен + Доступні мережі Моє портфоліо Маркет + Щоб згенерувати адреси для обраних мереж, потрібно прикласти картку Tangem Не вдалося завантажити дані... + Швидкі дії Результат Переглянути токени до 100к ринкової капіталізації Показати токени Жодного результату + Виберіть мережу Оберіть гаманець + 1 міс. + 1 рік + 24 год. + 3 міс. + 6 міс. + 7 днів + Увесь + Досвідчені покупці + За рейтингом Сортувати за + Лідери росту + Лідери падіння + В тренді + Про %s + + На основі %d оцінки + На основі %d оцінок + На основі %d оцінок + На основі %d оцінок + + Блокчейн сайт + Давлення покупця + Різниця між обсягом покупців та обсягом продавців + Циркуляційний запас + Загальна кількість монет, які доступні для торгівлі та перебувають в обігу на ринку + Досвідчені покупці + Мережеві покупці з додатковою вимогою мати не менше 100 вихідних транзакцій + Повністю розведена ринкова капіталізація + Загальна теоретична вартість криптовалюти, якщо всі монети, які могли б існувати, перебувають в обігу, включаючи ті, що не перебувають в обігу в даний час + Дата створення + Високий + Тримачі + Зміна кількості власників токенів протягом певного періоду часу Інсайти Посилання + Ліквідність + Зміна того, скільки ліквідності доступно для токена протягом зазначеного періоду часу + Індекс ліквідності + Низький + Ринкова капіталізація + Загальна ринкова вартість криптовалюти, що розраховується шляхом множення поточної ціни монети на загальну кількість монет в обігу + Рейтинг ринку + Позиція в крипторейтингу між усіма монетами на основі ринкової капіталізації + Максимальна пропозиція Метрики + Офіційні посилання Цінова ефективність + Репозиторій Оцінка безпеки + Соцмережі + Загальна пропозиція + Максимальна кількість монет або токенів, яка може коли-небудь існувати для певної криптовалюти + Обсяг торгів (24г) + Загальна сума криптовалюти, якою торгували протягом останніх 24 годин, що вказує на рівень активності та ліквідності на ринку Вам потрібно встановити єдиний код доступу для захисту всіх ваших карток Захист Пізніше ви зможете налаштувати індивідуальний код доступу до кожної картки @@ -450,6 +514,7 @@ Доступ до камери заборонено %1$s (%2$s) у мережі %3$s Надсилайте лише %s на цю адресу. Надсилання будь-якої іншої валюти призведе до її незворотної втрати. + Покажіть QR-код або поділіться своєю адресою Взяти участь Не вдалося завантажити інформацію по реферальній програмі. Будь ласка, спробуйте пізніше. Не вдалося завантажити інформацію по реферальній програмі. Код помилки: %s. Будь ласка, спробуйте пізніше. @@ -575,14 +640,15 @@ Надсилання... Торкніться будь-якого поля, щоб змінити його Надіслати %s - Ви надсилаєте ** %1$s **, включно з комісію мережі %2$s - Ви надсилаєте ** %1$s ** і %2$s + Ви надсилаєте **%1$s**, включно з комісію мережі %2$s + Ви надсилаєте **%1$s** і %2$s Надсилання %s Всього %1$s та %2$s буде надіслано ≈ %1$s (вкл. комісію: %2$s ) %s буде надіслано Транзакція успішно підписана і відправлена до блокчейну. Баланс гаманця буде оновлено через деякий час + %1$s — це монета у мережі Tron. Щоб розрахувати комісію та здійснити транзакцію, вам необхідно внести певну кількість Tron(TRX) на свій рахунок. Недійсна адреса %1$s (%2$s) Трансакцію надіслано @@ -614,6 +680,7 @@ Період, який ви повинні чекати після запиту на виведення коштів зі стейкінгу, перш ніж токени стануть доступними. Період блокування Відведений час для активації участі в стейкінгу. + Стейкінг %s Нативний стейкінг Стейкінг дозволяє заробляти %1s. Ваші винагороди за стейкінг надходять кожні ~%2s днів. Отримуйте винагороду за стейкінг @@ -640,6 +707,7 @@ Комісії Всі децентралізовані біржі вимагають схвалення, щоб запобігти доступу смарт-контрактів до вашого гаманця без вашого дозволу. За задумом смарт-контракти не можуть отримати доступ до ваших токенів без вашого схвалення. \"Розблоковуючи\" свої токени, ви дозволяєте смарт-контракту 1inch витрачати ваші активи. Майнери мережі отримують плату за газ (сплачену вами), щоб зафіксувати цю дію в блокчейні. Ви можете обміняти свій токен після того, як дасте дозвіл. Підтвердити + Помилка при розрахунку комісії. Будь ласка, надішліть відгук до служби підтримки. Ви обмінюєте Обмін цієї кількості обраних токенів призведе до значного впливу на ціну і зменшить вашу кінцеву суму. Недостатньо коштів @@ -697,6 +765,7 @@ Прикладіть twin-картку з номером %s та не прибирайте її до завершення операції Використовуйте %s або відскануйте картку, щоб отримати доступ до свого гаманця Будьте в курсі останніх функцій та новин + Дізнавайтеся першими про нові акції Бажаєте використовувати Push-повідомлення? Додати новий гаманець Ви впевнені, що хочете видалити цей гаманець? @@ -748,6 +817,7 @@ Налаштування гаманця Tangem Використовуйте %s або відскануйте картку, щоб розблокувати доступ до гаманця + Схоже, що активація картки була виконана неправильно. Це може бути пов\'язано з проблемою з модулем NFC вашого пристрою або неправильним прикладанням картки до пристрою. Зверніться за допомогою до нашої служби підтримки. Помилка активації За рішенням розробників мережі BNB стандарт BEP-2 перестане підтримуватись у червні 2024 року. Щоб не втратити свої активи, їх необхідно конвертувати у стандарт BEP-20. Використовуйте функцію обміну, щоб перевести їх у мережу BNB Smart Chain. Відключення мережі BNB Beacon Chain 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 28f111ab8b..ae1e771935 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -4,6 +4,7 @@ 管理代幣 僅將 %1$s (%2$s) 從 %3$s 網絡發送到此地址。使用其他代幣和網絡可能會導致資金損失 請求支持 + 再試一次 此功能不在展示模式中提供 原因:%s 無法發送交易 @@ -36,6 +37,7 @@ 安全模式 卡片設置 接受 + 允許 注意 餘額: %s 餘額 @@ -127,6 +129,9 @@ 反饋 Tangem反饋 無法發送交易 + 數量 %s + 要繼續,您需要允許 %1s 智能合約使用您的 %2s + 賦予權限 掃描卡片 要更改訪問密碼,請完全按照上圖所示連接手機和卡片 要更改密碼,請完全按照上圖所示連接手機和卡 @@ -171,7 +176,6 @@ 這此情況,您必須要重新開始 您想要離開啟用程序嗎? 開始 - 您要添加的卡上已經創建了另一個錢包。你想重置它並將卡用於新錢包嗎? 創建備份 閱讀更多關於助記詞的訊息 @@ -288,7 +292,7 @@ 您即將在主屏幕上隱藏此代幣。您可以隨時通過管理代幣頁面將其添加回來。 隱藏 %s 隱藏代幣 - %1$s 代幣是 %2$s 網絡上的主要貨幣,只要列表中還有該網絡上的其他代幣,它就無法被隱藏。 + %1$s (%2$s) 代幣是 %3$s 網絡上的主要貨幣,只要列表中還有該網絡上的其他代幣,它就無法被隱藏。 無法隱藏 %s 您還沒有任何交易 無法加載交易 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 276eb46615..87ea9c3841 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -82,6 +82,7 @@ Go to %1$s You have not given access to your camera, please adjust your privacy settings Cancel + Claim rewards Close Continue Copy @@ -109,6 +110,7 @@ Speed and fee Get addresses Go to provider + Go to token Import Later Locked @@ -156,6 +158,7 @@ I understand There was an error. Please try again. Unreachable + Unstake Yes Contract address copied! Available networks @@ -223,6 +226,8 @@ Exchange by %s Visit provider’s website to refund your money Operation failed by provider + The transaction amount was refunded in %1$s to your wallet due to OKX or bridge rules. %2$s + The amount was refunded in %1$s (%2$s network) Visit provider’s website for verification KYC verification required by provider Canceled @@ -336,7 +341,7 @@ Available networks My portfolio Market - To generate addresses for selected networks, you need to attach a Tangem card + To generate addresses for selected networks, you must scan your Tangem Wallet card. Unable to load the data… Quick actions Result @@ -497,7 +502,7 @@ Camera access denied %1$s (%2$s) on %3$s network Send only %s to this address. Sending any other currency will result in its irreversible loss. - Show QR-code or share your address + Show a QR-code or share your address Participate Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Error code: %s. Please try again later. @@ -637,6 +642,7 @@ Name Active To unstake your assets, click here. + The amount to stake must be at least %s APR APY The annual percentage return you can earn from participating in staking. @@ -658,13 +664,15 @@ The period you must wait after requesting to withdraw funds from staking before the tokens become available. Warmup period The allocated time for activating participation in staking. + Stake %s Native staking Staking allow you to earn %1s. Your staking rewards arrive every ~%2s days. Earn staking rewards Rewards Stake more - Unstacked + Unstaked Check unstaked to claim your assets + Unstaking Validator Store your crypto assets secure while keeping private keys contained in your card Revolutionary Hardware Wallet diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt index 7461386ac3..f14c204327 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt @@ -87,7 +87,7 @@ internal class ItemsBuilder @Inject constructor( DetailsItemUM.Basic.Item( id = "send_feedback", block = BlockUM( - text = resourceReference(R.string.details_send_feedback), + text = resourceReference(R.string.details_row_title_contact_to_support), iconRes = R.drawable.ic_comment_24, onClick = onClick, ), diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt index deb049021a..0e065183b2 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt @@ -51,7 +51,7 @@ internal class ItemsBuilder @Inject constructor( if (isReferralAvailable) { BlockUM( - text = resourceReference(R.string.referral_title), + text = resourceReference(R.string.details_referral_title), iconRes = R.drawable.ic_add_friends_24, onClick = { router.push(AppRoute.ReferralProgram(userWalletId)) }, ).let(::add) From f8ac204948b6f14684df17e7e927135a5160fb9c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 30 Jul 2024 17:28:17 +0300 Subject: [PATCH 26/53] Updated on 2026-08-14 --- .../java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 8402e1f8c0..9e7fd0aa87 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -1271,7 +1271,7 @@ internal class SwapInteractorImpl @Inject constructor( val feeToCheckFunds = feeByPriority + (otherNativeFee ?: BigDecimal.ZERO) val isBalanceIncludeFeeEnough = isBalanceEnough(fromToken, amount, feeToCheckFunds) val feeState = getFeeState( - fee = feeByPriority, + fee = feeToCheckFunds, spendAmount = amount, networkId = networkId, fromTokenStatus = fromToken, From 6ee898a262738678dd96ba25df362cbac06bf3f1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 30 Jul 2024 17:48:08 +0300 Subject: [PATCH 27/53] Updated on 2026-08-14 --- .../tangem/tap/features/home/redux/HomeMiddleware.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 92efb060a0..42f21e542d 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -1,5 +1,7 @@ package com.tangem.tap.features.home.redux +import com.google.firebase.analytics.ktx.analytics +import com.google.firebase.ktx.Firebase import com.tangem.common.doOnFailure import com.tangem.common.doOnResult import com.tangem.common.doOnSuccess @@ -63,8 +65,13 @@ private fun handleHomeAction(action: Action) { } is HomeAction.GoToShop -> { Analytics.send(Shop.ScreenOpened()) - store.dispatchOpenUrl(NEW_BUY_WALLET_URL) - + Firebase.analytics.appInstanceId + .addOnSuccessListener { + store.dispatchOpenUrl("$NEW_BUY_WALLET_URL&app_instance_id=$it") + } + .addOnFailureListener { + store.dispatchOpenUrl(NEW_BUY_WALLET_URL) + } // disabled for now in task [REDACTED_JIRA] // when (action.userCountryCode) { // RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> store.dispatchOpenUrl(BUY_WALLET_URL) From d4a1d04023ad5a29ea24b69f88169bfbce8afd89 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 31 Jul 2024 10:46:36 +0500 Subject: [PATCH 28/53] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 13 +++++++++++++ core/res/src/main/res/values-fr/strings.xml | 11 +++++++++++ core/res/src/main/res/values-it/strings.xml | 1 + core/res/src/main/res/values-ja/strings.xml | 11 +++++++++++ core/res/src/main/res/values-ru/strings.xml | 11 +++++++++++ core/res/src/main/res/values-uk-rUA/strings.xml | 13 ++++++++++++- core/res/src/main/res/values-zh-rTW/strings.xml | 7 +++++++ core/res/src/main/res/values/strings.xml | 17 ++++++++++++++--- 8 files changed, 80 insertions(+), 4 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 12e30ed235..67cef4ccd7 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -13,6 +13,9 @@ Das gewählte unterstützt nicht das %1$s Netzwerk Um die kryptografische Verschlüsselung der %1$s Blockchain zu aktivieren, musst Du die Wallet auf die Werkseinstellungen zurücksetzen. Bitte heb vorher dein Guthaben ab, um sicherzustellen, dass du nichts verlierst, und führe dann den Reset-Vorgang durch. Nach dem Zurücksetzen ist der Zugriff auf die aktuelle Wallet nicht mehr möglich. Tokens im %1$s -Netzwerk werden von dieser Karte aufgrund einer Firmware-Einschränkung nicht unterstützt. + Vielen Dank für dein Feedback. Wir werden so schnell wie möglich antworten + Deine Vorschläge wurden übermittelt + Bitte versuche, die Karte genau wie in der Animation gezeigt anzutippen, oder lese unsere einfache Anleitung, oder forder Support an. Wenn das Problem weiterhin besteht, wende dich bitte an den Support. Hast du Probleme beim Scannen deiner Karte? Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet Standardgebühr @@ -28,6 +31,8 @@ Dunkel Hell Systemstandard + Wenn das System ausgewählt ist, passt sich die App automatisch an die Systemeinstellungen deines Geräts an + System Thema App Einstellungen Um deine Kontostände ein- oder auszublenden, flippe einfach das Display deines Geräts nach unten, oder schalte es in den Einstellungen aus. @@ -95,6 +100,7 @@ Entfernen Deaktiviert + Trennen Erledigt Aktivieren Aktiviert @@ -131,6 +137,7 @@ Ablehnen Neu laden Umbenennen + Wiederholen Speichern Änderungen speichern Suchen @@ -159,6 +166,7 @@ Es ist ein Fehler aufgetreten. Bitte versuche es erneut. Nicht erreichbar staking beenden + Warnung Ja Vertragsadresse kopiert! Verfügbare Netzwerke @@ -206,6 +214,7 @@ Flipp um Guthaben auszublenden Aussteller Signiert + Wenn du den Code vergisst, verlierst du den Zugriff auf dein Geld. Eine Codewiederherstellung ist nicht möglich. Gib uns eine Rückmeldung Details Überprüfe deine Internetverbindung oder wechseln zu einem anderen Netzwerk @@ -572,6 +581,7 @@ %1$s, %2$s Adresse Ziel-Tag + Möchtest du den Sendebildschirm wirklich schließen? Adresse eingeben Die Adresse stimmt mit der Adresse Ihrer Brieftasche überein Ungültiges Tag. Es wird der Transaktion nicht hinzugefügt. @@ -668,6 +678,7 @@ Der Zeitraum, den du nach der Beantragung der Abhebung von Geldern aus dem Staking warten musst, bevor die Token verfügbar werden. Aufwärmphase Die zugewiesene Zeit für die Aktivierung der Teilnahme am Staking. + Stake %s Natives Staking Mit Staking kannst du %1s verdienen. Deine Staking-Belohnungen kommen alle ~%2s Tage. Verdiene Staking-Belohnungen @@ -675,6 +686,7 @@ Mehr staken unstaken Prüfe, was nicht eingesetzt wurde, um dein Vermögen zu beanspruchen + Staking beenden Validator/ Prüfer Bewahre deine Krypto-Assets sicher auf, während die privaten Schlüssel auf deiner Karte bleiben Revolutionäre Hardware-Wallet @@ -716,6 +728,7 @@ Die Geldsendung wird verfügbar, sobald die ausstehende(n) Transaktion(en) im Netzwerk %s abgeschlossen ist/sind. Der Verkauf von %s ist im Moment nicht verfügbar. Bitte prüfe später ob es Updates gibt. Staking %s ist aktuell nicht verfügbar. Prüfe bitte ob es ein neues Update gibt. + Adresse wählen Generiere XPUB Ausblenden Du bist dabei, dieses Token vom Hauptbildschirm auszublenden. Du kannst es jederzeit über die Seite „Token verwalten“ wieder hinzufügen. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 8a104e2052..30baffe42f 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -13,6 +13,9 @@ Le sélectionné ne prend pas en charge le réseau %1$s Pour activer le cryptage de la blockchain %1$s, vous devrez réinitialiser le portefeuille aux paramètres d\'usine. Veuillez retirer vos fonds avant de le faire pour vous assurer de ne pas les perdre, puis terminez le processus de réinitialisation. L\'accès au portefeuille actuel ne sera pas possible après la réinitialisation. Les jetons du réseau %1$s ne sont pas pris en charge par cette carte en raison d\'une limitation du micrologiciel. + Merci pour votre retour. Nous vous répondrons dès que possible + Vos suggestions ont été envoyées + Veuillez essayer d\'appuyer sur la carte exactement comme indiqué dans l\'animation ou lire notre guide simple, ou demander de l\'aide. Si le problème persiste, veuillez demander de l\'aide. Avez-vous des difficultés à scanner votre carte ? Cette carte n\'est pas conçue pour fonctionner avec Tangem Frais par défaut @@ -28,6 +31,8 @@ Sombre Clair Par défaut du système + Si le système est sélectionné, l\'application s\'ajustera automatiquement en fonction des paramètres système de votre appareil + Système Thème Paramètres de l\'application Pour masquer ou afficher vos soldes, il suffit de retourner l\'écran de votre appareil vers le bas ou de le désactiver dans les paramètres @@ -93,6 +98,7 @@ Supprimer Désactivé + Se déconnecter Exécuté Activer Activé @@ -128,6 +134,7 @@ Rejeter Recharger Renommer + Réessayer Enregistrer Sauvegarder les modifications Rechercher @@ -155,6 +162,7 @@ Je comprends Il y avait une erreur. Veuillez réessayer. Inaccessible + Alerte Oui Adresse du contrat copiée ! Réseaux disponibles @@ -202,6 +210,7 @@ Retourner pour masquer les soldes Emetteur Signé + Si vous oubliez le code, vous perdrez l\'accès à vos fonds. La récupération du code n\'est pas possible. Envoyer un commentaire Référénces Vérifiez votre connexion Internet ou passez à un réseau différent @@ -500,6 +509,7 @@ %1$s, %2$s Adresse Destination Tag + Êtes-vous sûr de vouloir fermer l\'écran d\'envoi ? Entrez l\'adresse L\'adresse est la même que celle de votre portefeuille Tag invalide. Il ne sera pas ajouté à la transaction. @@ -639,6 +649,7 @@ L\'envoi de fonds sera disponible une fois la ou les transactions en attente dans le réseau %s terminées. La vente de %s n\'est pas disponible pour le moment. Veuillez consulter nos mises à jour. Le staking %s n’est pas disponible pour le moment. Veuillez consulter nos mises à jour. + Choisissez une adresse Générer XPUB Masquer Vous êtes sur le point de masquer ce jeton de l\'écran principal. Vous pouvez le rajouter à tout moment via la page de gestion des jetons. diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index e7caa7829e..b8860bd7d8 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -14,6 +14,7 @@ Mantieni le modifiche Invia Con successo + Avviso Codice di accesso Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto Mantenimento della carta diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index a9ecf96c93..c0913ede01 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -13,6 +13,9 @@ 選択したものは%1$sネットワークをサポートしていません。 %1$sブロックチェーンの暗号化を有効にするには、ウォレットを工場出荷時の設定にリセットする必要があります。リセットする前に出金して、資金が失われないようにしてから、リセット処理を完了してください。リセット後は、現在のウォレットにアクセスできなくなります。 %1$s ネットワークのトークンは、ファームウェアの制限により、このカードではサポートされていません。 + ご意見ありがとうございます。できるだけ早くご返信いたします。 + あなたの提案が送信されました + アニメーションに表示されているとおりにカードをタップするか、簡単なガイドをお読みください。それでも問題が解決しない場合は、サポートをご依頼ください。 カードのスキャンに問題がありますか? このカードはこのアプリでは使用できません。 デフォルト手数料 @@ -28,6 +31,8 @@ ダーク ライト システムのデフォルト + システムを選択した場合、アプリはデバイスのシステム設定に基づいて自動調整されます。 + システム テーマ アプリ設定 残高を表示または非表示にするには、デバイスの画面を下向きにするか、設定でオフにしてください。 @@ -93,6 +98,7 @@ 削除 無効 + 切断 完了 有効にする 有効 @@ -129,6 +135,7 @@ 拒否 リロード 名前を変更 + リトライ 保存 変更内容を保存 検索 @@ -157,6 +164,7 @@ エラーが発生しました。もう一度お試しください。 アクセスできません ステーキング解除 + 警告 はい コントラクトアドレスをコピーしました! 利用可能なネットワーク @@ -204,6 +212,7 @@ フリップして残高を非表示にする 発行者 署名済み + コードを忘れた場合、資金にアクセスできなくなります。コードの回復は不可能です。 フィードバックを送信 詳細 インターネット接続を確認するか、別のネットワークに切り替えてください。 @@ -560,6 +569,7 @@ %1$s 、 %2$s アドレス 宛先タグ + 送金画面を閉じてもよろしいですか? アドレスを入力 アドレスはウォレットアドレスと同じです 無効なタグです。取引には追加されません。 @@ -704,6 +714,7 @@ ネットワーク%s内の保留中の取引が完了すると、送金が可能になります。 現在、 %sの売却はご利用いただけません。アップデート情報をご確認ください。 %s のステーキングは現在ご利用いただけません。最新情報をご確認ください。 + アドレスを選択 XPUBを生成する 非表示 このトークンをメイン画面から非表示にします。トークンの管理ページからいつでも再度追加できます。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 8fef85b5aa..741704103a 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -13,6 +13,9 @@ Выбранный кошелёк не поддерживает сеть %1$s Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. + Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки. + Ваши предложения отправлены + Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, либо прочтите руководство по сканированию. Если проблема осталась, то запросите поддержку. У вас возникли трудности со сканированием карты? Эта карта не предназначена для работы с этим приложением Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. @@ -27,6 +30,8 @@ Тёмная Светлая Как в системе + При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства + Системная Тема Настройки приложения Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" @@ -95,6 +100,7 @@ Удалить Отключено + Отключить Готово Включить Включено @@ -131,6 +137,7 @@ Отклонить Перезагрузить Переименовать + Повторить Сохранить Сохранить изменения Искать @@ -157,6 +164,7 @@ Я понял Произошла ошибка. Пожалуйста, попробуйте снова. Недоступно + Предупреждение Да Адрес контракта скопирован! Доступные сети @@ -204,6 +212,7 @@ Скрывать балансы жестом переворота Эмитент Подписано + Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно. Отправить отзыв Подробности Проверьте подключение с интернетом или переключитесь на другую сеть @@ -505,6 +514,7 @@ %1$s, %2$s Адрес Код назначения + Вы действительно хотите закрыть экран отправки транзакции? Введите адрес Адрес совпадает с адресом кошелька Недопустимый Tag. Он не будет добавлен в транзакцию. @@ -629,6 +639,7 @@ Отправка средств станет доступной после завершения транзакции(-ий) в сети %s В данный момент продажа %s недоступна. Следите за нашими обновлениями. В данный момент стейкинг монеты %s недоступен. Следите за нашими обновлениями. + Выберите адрес Сгенерировать XPUB Скрыть Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. 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 320017ffd8..1c150d0e88 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -13,6 +13,9 @@ Обраний гаманець не підтримує мережу %1$s Щоб активувати криптографічне шифрування блокчейну %1$s, вам потрібно скинути налаштування гаманця до заводських. Зніміть свої кошти перед цим, щоб переконатися, що ви їх не втратите, а потім завершіть процес скидання. Вхід до поточного гаманця буде неможливий після скидання. Токени в мережі %1$s не підтримуються цією карткою через обмеження прошивки. + Дякуємо за ваш відгук. Ми відповімо якнайшвидше. + Ваші пропозиції надіслано + Будь ласка, спробуйте прикласти картку точно так, як показано на анімації, або прочитайте наш простий посібник. Якщо проблема залишилася, зверніться до служби підтримки. У вас виникли труднощі зі скануванням картки? Ця картка не призначена для роботи з цим додатком Комісія за замовчуванням @@ -28,6 +31,8 @@ Темна Світла Системна + При виборі \"Системна\" застосунок буде використовувати тему відповідно до налаштувань вашого пристрою + Системна Тема Налаштування застосунку Щоб приховати або показати свій баланс, просто переверніть екран пристрою вниз або вимкніть його в налаштуваннях @@ -99,6 +104,7 @@ Видалити Вимкнуто + Від\'єднати Готово Увімкнути Увімкнено @@ -135,6 +141,7 @@ Відхилити Перезавантажити Перейменувати + Повторити Зберегти Зберегти зміни Шукати @@ -163,6 +170,7 @@ Виникла помилка. Будь ласка, спробуйте ще раз. Недоступно Скасувати стейкінг + Увага Так Адреса контракту скопійована! Доступні мережі @@ -210,6 +218,7 @@ Приховувати баланси жестом перевороту Емітент Підписано + Якщо ви забудете код, ви втратите доступ до своїх коштів. Відновлення коду неможливе. Надіслати відгук Деталі Перевірте підключення до інтернету або змініть мережу @@ -584,6 +593,7 @@ %1$s, %2$s Адреса Тег призначення + Ви дійсно хочете закрити екран надсилання транзакції? Введіть адресу Адреса збігається з адресою гаманця Недопустимий Tag. Він не буде доданий у транзакцію. @@ -648,7 +658,7 @@ ≈ %1$s (вкл. комісію: %2$s ) %s буде надіслано Транзакція успішно підписана і відправлена до блокчейну. Баланс гаманця буде оновлено через деякий час - %1$s — це монета у мережі Tron. Щоб розрахувати комісію та здійснити транзакцію, вам необхідно внести певну кількість Tron(TRX) на свій рахунок. + %1$s — це монета у мережі Tron. Щоб розрахувати комісію та здійснити транзакцію, вам необхідно внести певну кількість Tron (TRX) на свій рахунок. Недійсна адреса %1$s (%2$s) Трансакцію надіслано @@ -729,6 +739,7 @@ Надсилання коштів стане доступним після завершення транзакції(-ій) в мережі %s Наразі продаж %s недоступний. Слідкуйте за нашими оновленнями. Стейкінг %s зараз недоступний. Будь ласка, слідкуйте за нашими оновленнями. + Оберіть адресу Згенерувати XPUB Приховати Ви збираєтеся приховати цей токен з головного екрану. Ви можете додати його назад будь-коли на сторінці керування токенами. 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 ae1e771935..b198f3eee7 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -9,6 +9,9 @@ 原因:%s 無法發送交易 此卡不支持%1$s網路上的代幣因為韌體限制 + 感謝您的反饋。我們會盡快回复 + 你的建議已送出 + 請嘗試完全按照動畫中顯示的方式點擊卡片或請求支持 有困難在掃描卡上嗎? 此卡不適用於此app 轉到設置以在 Tangem App 中啟用生物識別身份驗證 @@ -53,6 +56,7 @@ 創造 刪除 禁用 + 斷開連接 完成 允許 啟用 @@ -65,6 +69,7 @@ 主卡片 拒絕 重新命名 + 重試 保存設置 搜索 搜尋代幣 @@ -83,6 +88,7 @@ 交易 我了解 無法觸達 + 警告 已複製代幣地址 支持的網路 @@ -115,6 +121,7 @@ App Currency 發行人 簽署 + 如果您忘記密碼,您將無法使用您的資金。無法恢復代碼 更多 檢查您的網路連接或切換到其他網絡 服務條款 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 87ea9c3841..46299409f1 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -13,6 +13,9 @@ The selected does not support the %1$s network To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. Tokens in %1$s network are not supported by this card due to firmware limitation. + Thank you for your feedback. We will respond as soon as possible + Your suggestions were sent + Please try to tap the card exactly as shown in the animation or read our simple guide, or request support. If the problem persists, please request support. Are you having difficulty scanning your card? This card is not designed to work with this app Default Fee @@ -28,6 +31,8 @@ Dark Light System default + If system is selected, the app will auto-adjust based on your device\'s system settings + System Theme App settings To hide or show your balances, simply flip your device screen down, or switch it off in Settings @@ -39,7 +44,7 @@ Too many attempts You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings. Start backup process - From your fiat card or bank account + With your bank card or bank account %d card %d cards @@ -95,6 +100,7 @@ Delete Disabled + Disconnect Done Enable Enabled @@ -131,6 +137,7 @@ Reject Reload Rename + Retry Save Save changes Search @@ -159,6 +166,7 @@ There was an error. Please try again. Unreachable Unstake + Warning Yes Contract address copied! Available networks @@ -206,6 +214,7 @@ Flip-to-Hide Balances Issuer Signed + If you forget the code you will lose access to your funds. Code recovery is not possible. Send feedback Details Check your internet connection or switch to a different network @@ -267,7 +276,7 @@ No tokens found. Please try another request ID: %s Transaction ID copied - From another currency in your wallet + With another currency in your wallet The following information is optional. You can erase it if you don\'t want to share it. Tell us what functions you are missing, and we will try to help you. Please tell us what card do you have @@ -568,6 +577,7 @@ %1$s, %2$s Address Destination Tag + Are you sure you want to close the send screen? Enter address Address is the same as wallet address Invalid Tag. It won\'t be added to the transaction. @@ -632,7 +642,7 @@ ≈ %1$s (inc. fee: %2$s) %s will be sent Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while - %1$s is an asset in the Tron network. To calculate the fee and make a transaction you need to deposit some Tron(TRX) in your account. + %1$s is an asset in the Tron network. To calculate the fee and make a transaction you need to deposit some Tron (TRX) in your account. Invalid address %1$s (%2$s) Transaction sent @@ -714,6 +724,7 @@ Sending funds will be available once the pending transaction(s) in network %s is complete Selling %s is not available at the moment. Please check our updates. Staking %s is not available at the moment. Please check our updates. + Choose address Generate XPUB Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. From 33764123ba248cdbef8c353a4105f29aa9ebffb1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 31 Jul 2024 10:47:34 +0500 Subject: [PATCH 29/53] Updated on 2026-08-14 --- .../tangem/core/ui/utils/RequestPushPermission.kt | 9 ++++----- .../wallet/viewmodels/WalletViewModel.kt | 14 ++++++++------ .../intents/WalletPushPermissionClickIntents.kt | 14 ++++++++++---- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt index 575485aae9..cac63e19f7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt @@ -26,11 +26,10 @@ fun requestPushPermission( val permissionState = pushPermission?.let { permission -> val tempPermissionState = rememberPermissionState(permission = permission) rememberPermissionState(permission = permission) { - when { - it -> onAllow() - !tempPermissionState.status.shouldShowRationale && !isFirstTimeAsking -> onOpenSettings() - else -> onDeny() - } + val isGranted = tempPermissionState.status.isGranted + val shouldShowRationale = tempPermissionState.status.shouldShowRationale + + if (isGranted && !shouldShowRationale && !isFirstTimeAsking) onOpenSettings() } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 3a9681b918..ed7f027de5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -159,22 +159,24 @@ internal class WalletViewModel @Inject constructor( val isFirstTimeRequested = isFirstTimeAskingPermissionUseCase(PUSH_PERMISSION).getOrElse { true } val wasInitiallyAsk = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { true } - val onRequestLater: () -> Unit = if (wasInitiallyAsk) { - clickIntents::onDelayAskPushPermission - } else { - clickIntents::onNeverAskPushPermission + val onRequestLater: (Boolean) -> Unit = { isUserDismissed -> + if (wasInitiallyAsk) { + clickIntents.onDelayAskPushPermission(isUserDismissed) + } else { + clickIntents.onNeverAskPushPermission(isUserDismissed) + } } stateHolder.showBottomSheet( content = PushNotificationsBottomSheetConfig( isFirstTimeRequested = isFirstTimeRequested, wasInitiallyAsk = wasInitiallyAsk, onRequest = clickIntents::onRequestPushPermission, - onRequestLater = onRequestLater, + onRequestLater = { onRequestLater(false) }, onAllow = clickIntents::onAllowPushPermission, onDeny = clickIntents::onDenyPushPermission, openSettings = settingsManager::openSettings, ), - onDismiss = onRequestLater, + onDismiss = { onRequestLater(true) }, ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt index fe8848daf5..62fec7ce4f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt @@ -15,9 +15,9 @@ internal interface WalletPushPermissionClickIntents { fun onRequestPushPermission() - fun onDelayAskPushPermission() + fun onDelayAskPushPermission(isUserDismissed: Boolean) - fun onNeverAskPushPermission() + fun onNeverAskPushPermission(isUserDismissed: Boolean) fun onDenyPushPermission() @@ -32,7 +32,9 @@ internal class WalletPushPermissionClickIntentsImplementor @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, ) : BaseWalletClickIntents(), WalletPushPermissionClickIntents { + private var isUserDismissedDialog: Boolean = true override fun onRequestPushPermission() { + isUserDismissedDialog = false analyticsEventHandler.send( PushNotificationAnalyticEvents.ButtonAllow(AnalyticsParam.ScreensSources.Main), ) @@ -41,7 +43,9 @@ internal class WalletPushPermissionClickIntentsImplementor @Inject constructor( } } - override fun onDelayAskPushPermission() { + override fun onDelayAskPushPermission(isUserDismissed: Boolean) { + if (!isUserDismissedDialog) return + isUserDismissedDialog = isUserDismissed viewModelScope.launch { analyticsEventHandler.send( PushNotificationAnalyticEvents.ButtonLater(AnalyticsParam.ScreensSources.Main), @@ -50,7 +54,9 @@ internal class WalletPushPermissionClickIntentsImplementor @Inject constructor( } } - override fun onNeverAskPushPermission() { + override fun onNeverAskPushPermission(isUserDismissed: Boolean) { + if (!isUserDismissedDialog) return + isUserDismissedDialog = isUserDismissed viewModelScope.launch { analyticsEventHandler.send(PushNotificationAnalyticEvents.ButtonCancel) neverRequestPermissionUseCase(PUSH_PERMISSION) From e877fabd933f09d1991d44054bedc5396a3ebc45 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 31 Jul 2024 10:48:08 +0500 Subject: [PATCH 30/53] Updated on 2026-08-14 --- .../main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt index 778d4f08d8..e5d94f85b7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt @@ -37,7 +37,7 @@ object BigDecimalFormatter { maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8) minimumFractionDigits = 2 isGroupingUsed = true - roundingMode = RoundingMode.DOWN + roundingMode = RoundingMode.HALF_UP } return formatter.format(cryptoAmount).let { From 6c6ce3d05efe8394c10c49de9122f1f27b907803 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 31 Jul 2024 13:49:32 +0500 Subject: [PATCH 31/53] Updated on 2026-08-14 --- .../repository/DefaultCurrenciesRepository.kt | 15 ++------------- .../repository/paging/TxHistoryPagingSource.kt | 1 + gradle/dependencies.toml | 2 +- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index dcfd66e7a7..d1a7d70fcf 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -61,18 +61,6 @@ internal class DefaultCurrenciesRepository( private val isMultiCurrencyWalletCurrenciesFetching = MutableStateFlow( value = emptyMap(), ) - private val parallelTransactionsEnabledBlockchains = setOf( - Blockchain.Ethereum, - Blockchain.EthereumTestnet, - Blockchain.Polygon, - Blockchain.PolygonTestnet, - Blockchain.Arbitrum, - Blockchain.ArbitrumTestnet, - Blockchain.Binance, - Blockchain.BinanceTestnet, - Blockchain.Tron, - Blockchain.TronTestnet, - ) override suspend fun saveTokens( userWalletId: UserWalletId, @@ -391,7 +379,8 @@ internal class DefaultCurrenciesRepository( val outgoingTransactions = cryptoCurrencyStatus.value.pendingTransactions.filter { it.isOutgoing } outgoingTransactions.isNotEmpty() } - parallelTransactionsEnabledBlockchains.contains(blockchain) -> false + blockchain.isEvm() -> false + blockchain == Blockchain.Tron || blockchain == Blockchain.TronTestnet -> false else -> coinStatus?.value?.hasCurrentNetworkTransactions == true } } diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPagingSource.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPagingSource.kt index d534e238ec..87d2138b87 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPagingSource.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPagingSource.kt @@ -85,6 +85,7 @@ internal class TxHistoryPagingSource( currency = sourceParams.currency, ) .filterUnconfirmedTransaction() + .sortedByDescending { it.timestampInMillis } .filterIfTxAlreadyAdded(apiItems = items) return if (recentItems.isEmpty()) { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 74fab9ccbd..061ea006d3 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.13-707" +tangemBlockchainSdk = "release-app_5.13-714" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.13-376" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 8c8c0ada458c7697809ebc9227414562e1bfbd49 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 30 Jul 2024 11:37:27 +0300 Subject: [PATCH 32/53] Updated on 2026-08-14 --- .../tap/di/domain/WalletsDomainModule.kt | 11 +++++--- .../wallets/usecase/RenameWalletUseCase.kt | 25 ++++++++++++------- .../impl/DefaultRenameWalletComponent.kt | 10 ++++---- .../preview/PreviewRenameWalletComponent.kt | 2 +- .../walletsettings/entity/RenameWalletUM.kt | 2 +- .../walletsettings/ui/RenameWalletDialog.kt | 2 +- 6 files changed, 31 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 3dfc085c10..cc9b89e775 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -1,12 +1,12 @@ package com.tangem.tap.di.domain import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.transaction.WalletAddressServiceRepository import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.* @@ -95,8 +95,11 @@ internal object WalletsDomainModule { @Provides @Singleton - fun providesRenameWalletUseCase(userWalletsListManager: UserWalletsListManager): RenameWalletUseCase { - return RenameWalletUseCase(userWalletsListManager = userWalletsListManager) + fun providesRenameWalletUseCase( + userWalletsListManager: UserWalletsListManager, + dispatchers: CoroutineDispatcherProvider, + ): RenameWalletUseCase { + return RenameWalletUseCase(userWalletsListManager = userWalletsListManager, dispatchers = dispatchers) } @Provides diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt index 4206d4854f..ff568c6794 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt @@ -8,25 +8,32 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UpdateWalletError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext /** * Use case for rename user wallet * * @property userWalletsListManager user wallets list manager */ -class RenameWalletUseCase(private val userWalletsListManager: UserWalletsListManager) { +class RenameWalletUseCase( + private val userWalletsListManager: UserWalletsListManager, + private val dispatchers: CoroutineDispatcherProvider, +) { suspend operator fun invoke(userWalletId: UserWalletId, name: String): Either = - either { - val existingNames = userWalletsListManager.userWalletsSync + withContext(dispatchers.io) { + either { + val existingNames = userWalletsListManager.userWalletsSync - ensure(existingNames.none { it.name == name && it.walletId != userWalletId }) { - UpdateWalletError.NameAlreadyExists - } + ensure(existingNames.none { it.name == name && it.walletId != userWalletId }) { + UpdateWalletError.NameAlreadyExists + } - when (val result = userWalletsListManager.update(userWalletId) { it.copy(name = name) }) { - is CompletionResult.Failure -> raise(UpdateWalletError.DataError(result.error)) - is CompletionResult.Success -> result.data + when (val result = userWalletsListManager.update(userWalletId) { it.copy(name = name) }) { + is CompletionResult.Failure -> raise(UpdateWalletError.DataError(result.error)) + is CompletionResult.Success -> result.data + } } } } \ No newline at end of file diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/impl/DefaultRenameWalletComponent.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/impl/DefaultRenameWalletComponent.kt index 15ab597e72..902d26f1ef 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/impl/DefaultRenameWalletComponent.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/impl/DefaultRenameWalletComponent.kt @@ -34,8 +34,8 @@ internal class DefaultRenameWalletComponent @AssistedInject constructor( private val stateFlow: MutableStateFlow = MutableStateFlow( value = RenameWalletUM( walletNameValue = TextFieldValue(text = params.currentName), - isNameCorrect = false, updateValue = ::updateValue, + isConfirmEnabled = false, onConfirm = { renameWallet(params.userWalletId) }, ), ) @@ -58,12 +58,14 @@ internal class DefaultRenameWalletComponent @AssistedInject constructor( stateFlow.update { it.copy( walletNameValue = value, - isNameCorrect = value.text.isNotBlank() && value.text != currentWalletName, + isConfirmEnabled = value.text.isNotBlank() && value.text != currentWalletName, ) } } private fun renameWallet(userWalletId: UserWalletId) = componentScope.launch { + stateFlow.update { it.copy(isConfirmEnabled = false) } + val newName = stateFlow.value.walletNameValue val maybeError = renameWalletUseCase(userWalletId, newName.text).leftOrNull() @@ -71,9 +73,7 @@ internal class DefaultRenameWalletComponent @AssistedInject constructor( Timber.e("Unable to rename wallet: $maybeError") val message = when (maybeError) { - is UpdateWalletError.DataError -> resourceReference( - id = R.string.common_unknown_error, - ) + is UpdateWalletError.DataError -> resourceReference(id = R.string.common_unknown_error) is UpdateWalletError.NameAlreadyExists -> resourceReference( id = R.string.user_wallet_list_rename_popup_error_already_exists, formatArgs = wrappedList(newName), diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewRenameWalletComponent.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewRenameWalletComponent.kt index ff42b59689..fbcb3d6389 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewRenameWalletComponent.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewRenameWalletComponent.kt @@ -10,7 +10,7 @@ internal class PreviewRenameWalletComponent : RenameWalletComponent { private val previewState = RenameWalletUM( walletNameValue = TextFieldValue(text = "My Wallet"), - isNameCorrect = false, + isConfirmEnabled = false, updateValue = {}, onConfirm = {}, ) diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/entity/RenameWalletUM.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/entity/RenameWalletUM.kt index 4dd5b8c7a7..730cc1ee60 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/entity/RenameWalletUM.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/entity/RenameWalletUM.kt @@ -6,7 +6,7 @@ import androidx.compose.ui.text.input.TextFieldValue @Immutable internal data class RenameWalletUM( val walletNameValue: TextFieldValue, - val isNameCorrect: Boolean, val updateValue: (value: TextFieldValue) -> Unit, + val isConfirmEnabled: Boolean, val onConfirm: () -> Unit, ) \ No newline at end of file diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/RenameWalletDialog.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/RenameWalletDialog.kt index fa02de24c9..5ff04fe2e7 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/RenameWalletDialog.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/RenameWalletDialog.kt @@ -23,7 +23,7 @@ internal fun RenameWalletDialog(model: RenameWalletUM, onDismiss: () -> Unit) { fieldValue = value, confirmButton = DialogButton( title = stringResource(id = R.string.common_ok), - enabled = model.isNameCorrect, + enabled = model.isConfirmEnabled, onClick = model.onConfirm, ), dismissButton = DialogButton( From af822f9d7a16eb05baa83233a7ec64bbcf4dbc17 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 31 Jul 2024 12:57:35 +0300 Subject: [PATCH 33/53] Updated on 2026-08-14 --- .../analytics/DefaultAnalyticsContextProxy.kt | 31 +++++++++++ .../tap/di/analytics/AnalyticsModule.kt | 6 +++ .../details/ui/details/DetailsScreenState.kt | 2 +- app/src/main/res/drawable/ic_more_cards.xml | 5 -- core/analytics/build.gradle.kts | 1 + .../analytics/utils/AnalyticsContextProxy.kt | 17 +++++++ .../main/res/drawable/ic_more_cards_24.xml | 11 ++++ .../wallet-settings/impl/build.gradle.kts | 3 ++ .../walletsettings/analytics/Settings.kt | 13 +++++ .../preview/PreviewWalletSettingsComponent.kt | 2 + .../model/WalletSettingsModel.kt | 29 +++++++++++ .../walletsettings/utils/ItemsBuilder.kt | 51 ++++++++++++------- 12 files changed, 147 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/DefaultAnalyticsContextProxy.kt delete mode 100644 app/src/main/res/drawable/ic_more_cards.xml create mode 100644 core/analytics/src/main/java/com/tangem/core/analytics/utils/AnalyticsContextProxy.kt create mode 100644 core/ui/src/main/res/drawable/ic_more_cards_24.xml create mode 100644 features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/Settings.kt diff --git a/app/src/main/java/com/tangem/tap/common/analytics/DefaultAnalyticsContextProxy.kt b/app/src/main/java/com/tangem/tap/common/analytics/DefaultAnalyticsContextProxy.kt new file mode 100644 index 0000000000..d1aecc29d0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/DefaultAnalyticsContextProxy.kt @@ -0,0 +1,31 @@ +package com.tangem.tap.common.analytics + +import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.utils.AnalyticsContextProxy +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.common.extensions.addContext +import com.tangem.tap.common.extensions.eraseContext +import com.tangem.tap.common.extensions.removeContext +import com.tangem.tap.common.extensions.setContext + +/** +[REDACTED_AUTHOR] + */ +internal class DefaultAnalyticsContextProxy : AnalyticsContextProxy { + + override fun setContext(scanResponse: ScanResponse) { + Analytics.setContext(scanResponse) + } + + override fun eraseContext() { + Analytics.eraseContext() + } + + override fun addContext(scanResponse: ScanResponse) { + Analytics.addContext(scanResponse) + } + + override fun removeContext() { + Analytics.removeContext() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/analytics/AnalyticsModule.kt b/app/src/main/java/com/tangem/tap/di/analytics/AnalyticsModule.kt index 34dbbd2e11..c687d63c10 100644 --- a/app/src/main/java/com/tangem/tap/di/analytics/AnalyticsModule.kt +++ b/app/src/main/java/com/tangem/tap/di/analytics/AnalyticsModule.kt @@ -1,6 +1,8 @@ package com.tangem.tap.di.analytics +import com.tangem.core.analytics.utils.AnalyticsContextProxy import com.tangem.domain.analytics.ChangeCardAnalyticsContextUseCase +import com.tangem.tap.common.analytics.DefaultAnalyticsContextProxy import com.tangem.tap.common.analytics.DefaultChangeCardAnalyticsContextUseCase import dagger.Module import dagger.Provides @@ -17,4 +19,8 @@ internal object AnalyticsModule { fun provideChangeCardAnalyticsContextUseCase(): ChangeCardAnalyticsContextUseCase { return DefaultChangeCardAnalyticsContextUseCase() } + + @Provides + @Singleton + fun provideAnalyticsContextProxy(): AnalyticsContextProxy = DefaultAnalyticsContextProxy() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt index b35c17a5fd..acafefe8e7 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -60,7 +60,7 @@ internal sealed class SettingsItem( data class LinkMoreCards( override val onClick: () -> Unit, ) : SettingsItem( - iconResId = R.drawable.ic_more_cards, + iconResId = R.drawable.ic_more_cards_24, title = resourceReference(R.string.details_row_title_create_backup), ) diff --git a/app/src/main/res/drawable/ic_more_cards.xml b/app/src/main/res/drawable/ic_more_cards.xml deleted file mode 100644 index 0cc559021b..0000000000 --- a/app/src/main/res/drawable/ic_more_cards.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts index 9d05970191..c61c8e7af2 100644 --- a/core/analytics/build.gradle.kts +++ b/core/analytics/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { /** Domain */ implementation(projects.domain.analytics) + implementation(projects.domain.models) /** Other */ implementation(deps.kotlin.coroutines) diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/utils/AnalyticsContextProxy.kt b/core/analytics/src/main/java/com/tangem/core/analytics/utils/AnalyticsContextProxy.kt new file mode 100644 index 0000000000..cab4da6a7b --- /dev/null +++ b/core/analytics/src/main/java/com/tangem/core/analytics/utils/AnalyticsContextProxy.kt @@ -0,0 +1,17 @@ +package com.tangem.core.analytics.utils + +import com.tangem.domain.models.scan.ScanResponse + +/** +[REDACTED_AUTHOR] + */ +interface AnalyticsContextProxy { + + fun setContext(scanResponse: ScanResponse) + + fun eraseContext() + + fun addContext(scanResponse: ScanResponse) + + fun removeContext() +} \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_more_cards_24.xml b/core/ui/src/main/res/drawable/ic_more_cards_24.xml new file mode 100644 index 0000000000..1fd80fa554 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_more_cards_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/features/wallet-settings/impl/build.gradle.kts b/features/wallet-settings/impl/build.gradle.kts index 8c61fc867a..14885a740b 100644 --- a/features/wallet-settings/impl/build.gradle.kts +++ b/features/wallet-settings/impl/build.gradle.kts @@ -21,11 +21,13 @@ dependencies { implementation(projects.core.ui) implementation(projects.core.featuretoggles) implementation(projects.core.navigation) + implementation(projects.core.analytics) implementation(projects.core.analytics.models) implementation(projects.common.routing) /* Project - Domain */ implementation(projects.domain.legacy) + implementation(projects.domain.models) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) @@ -50,4 +52,5 @@ dependencies { /* Other */ implementation(deps.kotlin.immutable.collections) implementation(deps.timber) + implementation(deps.reKotlin) } \ No newline at end of file diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/Settings.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/Settings.kt new file mode 100644 index 0000000000..9a655d973a --- /dev/null +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/Settings.kt @@ -0,0 +1,13 @@ +package com.tangem.feature.walletsettings.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent + +internal sealed class Settings( + category: String = "Settings", + event: String, + params: Map = mapOf(), + error: Throwable? = null, +) : AnalyticsEvent(category, event, params, error) { + + class ButtonCreateBackup : Settings(event = "Button - Create Backup") +} \ No newline at end of file diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt index 7649ef9bd7..4dba365556 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt @@ -19,8 +19,10 @@ internal class PreviewWalletSettingsComponent : WalletSettingsComponent { userWalletId = UserWalletId("011"), userWalletName = "My Wallet", isReferralAvailable = true, + isLinkMoreCardsAvailable = true, renameWallet = {}, forgetWallet = {}, + onLinkMoreCardsClick = {}, ), ) diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index e066301748..a9e5b42932 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -5,6 +5,8 @@ import arrow.core.getOrElse import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.tangem.common.routing.AppRoute +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.utils.AnalyticsContextProxy import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -16,9 +18,14 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.ContentMessage import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.redux.LegacyAction +import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.DeleteWalletUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.feature.walletsettings.analytics.Settings import com.tangem.feature.walletsettings.component.WalletSettingsComponent import com.tangem.feature.walletsettings.entity.DialogConfig import com.tangem.feature.walletsettings.entity.WalletSettingsItemUM @@ -43,6 +50,9 @@ internal class WalletSettingsModel @Inject constructor( private val deleteWalletUseCase: DeleteWalletUseCase, private val itemsBuilder: ItemsBuilder, override val dispatchers: CoroutineDispatcherProvider, + private val analyticsEventHandler: AnalyticsEventHandler, + private val analyticsContextProxy: AnalyticsContextProxy, + private val reduxStateHolder: ReduxStateHolder, ) : Model() { val params: WalletSettingsComponent.Params = paramsContainer.require() @@ -75,6 +85,7 @@ internal class WalletSettingsModel @Inject constructor( userWalletId = userWallet.walletId, userWalletName = userWallet.name, isReferralAvailable = userWallet.cardTypesResolver.isTangemWallet(), + isLinkMoreCardsAvailable = userWallet.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup, renameWallet = { openRenameWalletDialog(userWallet, dialogNavigation) }, forgetWallet = { messageSender.send( @@ -98,6 +109,9 @@ internal class WalletSettingsModel @Inject constructor( }, ) }, + onLinkMoreCardsClick = { + onLinkMoreCardsClick(scanResponse = userWallet.scanResponse) + }, ) private fun openRenameWalletDialog(userWallet: UserWallet, dialogNavigation: SlotNavigation) { @@ -126,4 +140,19 @@ internal class WalletSettingsModel @Inject constructor( router.replaceAll(AppRoute.Home) } } + + private fun onLinkMoreCardsClick(scanResponse: ScanResponse) { + analyticsEventHandler.send(Settings.ButtonCreateBackup()) + + analyticsContextProxy.addContext(scanResponse) + + reduxStateHolder.dispatch( + LegacyAction.StartOnboardingProcess( + scanResponse = scanResponse, + canSkipBackup = false, + ), + ) + + router.push(AppRoute.OnboardingWallet()) + } } \ No newline at end of file diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt index 0e065183b2..941f1c53f2 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt @@ -19,15 +19,18 @@ internal class ItemsBuilder @Inject constructor( private val router: Router, ) { + @Suppress("LongParameterList") fun buildItems( userWalletId: UserWalletId, userWalletName: String, + isLinkMoreCardsAvailable: Boolean, isReferralAvailable: Boolean, forgetWallet: () -> Unit, renameWallet: () -> Unit, + onLinkMoreCardsClick: () -> Unit, ): PersistentList = persistentListOf( buildNameItem(userWalletName, renameWallet), - buildCardItem(userWalletId, isReferralAvailable), + buildCardItem(userWalletId, isLinkMoreCardsAvailable, isReferralAvailable, onLinkMoreCardsClick), buildForgetItem(forgetWallet), ) @@ -38,26 +41,38 @@ internal class ItemsBuilder @Inject constructor( onClick = renameWallet, ) - private fun buildCardItem(userWalletId: UserWalletId, isReferralAvailable: Boolean) = - WalletSettingsItemUM.WithItems( - id = "card", - description = resourceReference(R.string.settings_card_settings_footer), - blocks = buildList { + private fun buildCardItem( + userWalletId: UserWalletId, + isLinkMoreCardsAvailable: Boolean, + isReferralAvailable: Boolean, + onLinkMoreCardsClick: () -> Unit, + ) = WalletSettingsItemUM.WithItems( + id = "card", + description = resourceReference(R.string.settings_card_settings_footer), + blocks = buildList { + if (isLinkMoreCardsAvailable) { BlockUM( - text = resourceReference(R.string.card_settings_title), - iconRes = R.drawable.ic_card_settings_24, - onClick = { router.push(AppRoute.CardSettings(userWalletId)) }, + text = resourceReference(R.string.details_row_title_create_backup), + iconRes = R.drawable.ic_more_cards_24, + onClick = onLinkMoreCardsClick, ).let(::add) + } - if (isReferralAvailable) { - BlockUM( - text = resourceReference(R.string.details_referral_title), - iconRes = R.drawable.ic_add_friends_24, - onClick = { router.push(AppRoute.ReferralProgram(userWalletId)) }, - ).let(::add) - } - }.toImmutableList(), - ) + BlockUM( + text = resourceReference(R.string.card_settings_title), + iconRes = R.drawable.ic_card_settings_24, + onClick = { router.push(AppRoute.CardSettings(userWalletId)) }, + ).let(::add) + + if (isReferralAvailable) { + BlockUM( + text = resourceReference(R.string.details_referral_title), + iconRes = R.drawable.ic_add_friends_24, + onClick = { router.push(AppRoute.ReferralProgram(userWalletId)) }, + ).let(::add) + } + }.toImmutableList(), + ) private fun buildForgetItem(forgetWallet: () -> Unit) = WalletSettingsItemUM.WithItems( id = "forget", From cef5863d91da9e7ff0d9fd308275fc6374b4ec92 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 31 Jul 2024 14:50:38 +0300 Subject: [PATCH 34/53] Updated on 2026-08-14 --- .../tokendetails/viewmodels/ExchangeStatusFactory.kt | 5 +++-- .../tokendetails/viewmodels/TokenDetailsViewModel.kt | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt index 8299f06c97..fc2733a1ba 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt @@ -84,12 +84,13 @@ internal class ExchangeStatusFactory( } } - suspend fun removeTransactionOnBottomSheetClosed(): TokenDetailsState { + suspend fun removeTransactionOnBottomSheetClosed(isForceTerminal: Boolean = false): TokenDetailsState { val state = currentStateProvider() val bottomSheetConfig = state.bottomSheetConfig?.content as? ExchangeStatusBottomSheetConfig ?: return state val selectedTx = bottomSheetConfig.value - return if (selectedTx.activeStatus.isTerminal(selectedTx.isRefundTerminalStatus)) { + val shouldTerminate = selectedTx.activeStatus.isTerminal(selectedTx.isRefundTerminalStatus) || isForceTerminal + return if (shouldTerminate) { swapTransactionRepository.removeTransaction( userWalletId = userWalletId, fromCryptoCurrency = selectedTx.fromCryptoCurrency, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 37e0da4d40..557903c7ca 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -718,8 +718,8 @@ internal class TokenDetailsViewModel @Inject constructor( override fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency) { if (internalUiState.value.bottomSheetConfig?.content is ExchangeStatusBottomSheetConfig) { - viewModelScope.launch(dispatchers.main) { - internalUiState.value = exchangeStatusFactory.removeTransactionOnBottomSheetClosed() + viewModelScope.launch { + internalUiState.value = exchangeStatusFactory.removeTransactionOnBottomSheetClosed(true) } } internalUiState.value = stateFactory.getStateWithClosedBottomSheet() From ac29785733af4af7fd11083477ee6850082654ad Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 30 Jul 2024 23:56:55 +0400 Subject: [PATCH 35/53] Updated on 2026-08-14 --- .../domain/scanCard/LegacyScanProcessor.kt | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index 0bbdc3f5f4..ea7c4ac652 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -32,6 +32,7 @@ import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext internal object LegacyScanProcessor { @@ -115,24 +116,28 @@ internal object LegacyScanProcessor { } else { scope.launch { delay(DELAY_SDK_DIALOG_CLOSE) - disclaimerWillShow() - store.dispatchWithMain( - DisclaimerAction.Show( - from = DisclaimerSource.Home, - callback = DisclaimerCallback( - onAccept = { - scope.launch(Dispatchers.Main) { - nextHandler(scanResponse) - } - }, - onDismiss = { - scope.launch(Dispatchers.Main) { - onFailure(TangemSdkError.UserCancelled()) - } - }, + + withContext(Dispatchers.Main.immediate) { + disclaimerWillShow() + + store.dispatch( + DisclaimerAction.Show( + from = DisclaimerSource.Home, + callback = DisclaimerCallback( + onAccept = { + scope.launch(Dispatchers.Main.immediate) { + nextHandler(scanResponse) + } + }, + onDismiss = { + scope.launch(Dispatchers.Main.immediate) { + onFailure(TangemSdkError.UserCancelled()) + } + }, + ), ), - ), - ) + ) + } } } } From 01b9ceda966ac290fb16ac1051ff90fac8eb9774 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 30 Jul 2024 23:58:54 +0400 Subject: [PATCH 36/53] Updated on 2026-08-14 --- .../tangem/features/details/utils/UserWalletMappers.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt index eab5ed50e3..a9782fc1d9 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt @@ -21,7 +21,8 @@ internal fun List.toUiModels( isBalancesHidden: Boolean = false, ): ImmutableList = this.map { model -> val balance = balances[model.walletId] - model.mapToUiModel( + + model.toUiModel( balance = balance, appCurrency = appCurrency, isLoading = isLoading, @@ -30,7 +31,7 @@ internal fun List.toUiModels( ) }.toImmutableList() -private fun UserWallet.mapToUiModel( +private fun UserWallet.toUiModel( balance: TotalFiatBalance?, appCurrency: AppCurrency?, isLoading: Boolean, @@ -66,9 +67,9 @@ private fun UserWallet.getInfo( ) return when { + isBalanceHidden -> combinedReference(cardCountRef, dividerRef, stringReference(STARS)) isLocked -> combinedReference(cardCountRef, dividerRef, resourceReference(R.string.common_locked)) isLoading -> cardCountRef - isBalanceHidden -> combinedReference(cardCountRef, dividerRef, stringReference(STARS)) else -> getBalanceInfo(balance, appCurrency, cardCountRef, dividerRef) } } @@ -80,7 +81,7 @@ private fun getBalanceInfo( dividerRef: TextReference, ): TextReference { val amount = when (balance) { - is TotalFiatBalance.Loaded -> balance.amount.takeIf { balance.isAllAmountsSummarized } + is TotalFiatBalance.Loaded -> balance.amount is TotalFiatBalance.Failed, is TotalFiatBalance.Loading, null, From 3b5f51c639af99c02e18bed3d7567f481a80b205 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 31 Jul 2024 00:26:59 +0400 Subject: [PATCH 37/53] Updated on 2026-08-14 --- .../ui/cardsettings/CardSettingsViewModel.kt | 54 ++++++++++++------- .../twins/redux/TwinCardsMiddleware.kt | 24 ++++----- .../products/twins/redux/TwinCardsState.kt | 12 +++-- .../twins/ui/OnboardingTwinsFragment.kt | 8 +-- 4 files changed, 59 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index d8b9f44ca9..47425804ff 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -24,6 +24,8 @@ import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.features.details.ui.common.utils.* +import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.store import com.tangem.wallet.R import dagger.hilt.android.lifecycle.HiltViewModel @@ -129,27 +131,8 @@ internal class CardSettingsViewModel @Inject constructor( changeAccessCode() } is CardInfo.ResetToFactorySettings -> { - val card = requireNotNull(scannedScanResponse.value) { - "Impossible to reset card if ScanResponse is null" - }.card - Analytics.send(Settings.CardSettings.ButtonFactoryReset()) - store.dispatchNavigationAction { - push( - route = AppRoute.ResetToFactory( - userWalletId = userWalletId, - cardId = card.cardId, - isActiveBackupStatus = card.backupStatus?.isActive == true, - backupCardsCount = when (val status = card.backupStatus) { - is CardDTO.BackupStatus.Active -> status.cardCount - is CardDTO.BackupStatus.CardLinked, - CardDTO.BackupStatus.NoBackup, - null, - -> 0 - }, - ), - ) - } + resetWalletToFactorySettings() } is CardInfo.SecurityMode -> { Analytics.send(Settings.CardSettings.ButtonChangeSecurityMode()) @@ -166,6 +149,37 @@ internal class CardSettingsViewModel @Inject constructor( } } + private fun resetWalletToFactorySettings() { + val scanResponse = requireNotNull(scannedScanResponse.value) { + "Impossible to reset card if ScanResponse is null" + } + + if (scanResponse.cardTypesResolver.isTangemTwins()) { + store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet(scanResponse))) + + store.dispatchNavigationAction { push(AppRoute.OnboardingTwins) } + } else { + val card = scanResponse.card + + store.dispatchNavigationAction { + push( + route = AppRoute.ResetToFactory( + userWalletId = userWalletId, + cardId = card.cardId, + isActiveBackupStatus = card.backupStatus?.isActive == true, + backupCardsCount = when (val status = card.backupStatus) { + is CardDTO.BackupStatus.Active -> status.cardCount + is CardDTO.BackupStatus.CardLinked, + CardDTO.BackupStatus.NoBackup, + null, + -> 0 + }, + ), + ) + } + } + } + private fun changeAccessCode() = viewModelScope.launch { val scanResponse = requireNotNull(scannedScanResponse.value) { "Scan response is null" } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 2bc6f07ce1..a53d4f51a7 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -46,7 +46,7 @@ object TwinCardsMiddleware { val handler = twinsWalletMiddleware } -private val twinsWalletMiddleware: Middleware = { dispatch, state -> +private val twinsWalletMiddleware: Middleware = { dispatch, _ -> { next -> { action -> handle(action, dispatch) @@ -65,16 +65,16 @@ private fun handle(action: Action, dispatch: DispatchFunction) { val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) fun getScanResponse(): ScanResponse { - return when (twinCardsState.mode) { - CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse - CreateTwinWalletMode.RecreateWallet -> globalState.scanResponse + return when (val mode = twinCardsState.mode) { + is CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse + is CreateTwinWalletMode.RecreateWallet -> mode.scanResponse } ?: throw NullPointerException("ScanResponse can't be NULL") } fun updateScanResponse(response: ScanResponse) { when (twinCardsState.mode) { - CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response - CreateTwinWalletMode.RecreateWallet -> store.dispatchOnMain(GlobalAction.SaveScanResponse(response)) + is CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response + is CreateTwinWalletMode.RecreateWallet -> store.dispatchOnMain(GlobalAction.SaveScanResponse(response)) } } @@ -113,7 +113,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } when (twinCardsState.mode) { - CreateTwinWalletMode.CreateWallet -> { + is CreateTwinWalletMode.CreateWallet -> { mainScope.launch { val wasTwinsOnboardingShown = store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase) .invokeSync() @@ -132,7 +132,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { store.dispatch(dispatchAction) } } - CreateTwinWalletMode.RecreateWallet -> { + is CreateTwinWalletMode.RecreateWallet -> { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Warning)) } } @@ -226,10 +226,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) { delay(DELAY_SDK_DIALOG_CLOSE) withMainContext { when (twinCardsState.mode) { - CreateTwinWalletMode.CreateWallet -> { + is CreateTwinWalletMode.CreateWallet -> { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.TopUpWallet)) } - CreateTwinWalletMode.RecreateWallet -> { + is CreateTwinWalletMode.RecreateWallet -> { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done)) } } @@ -319,11 +319,11 @@ private fun handle(action: Action, dispatch: DispatchFunction) { TwinCardsAction.Done -> { val scanResponse = getScanResponse() when (twinCardsState.mode) { - CreateTwinWalletMode.CreateWallet -> { + is CreateTwinWalletMode.CreateWallet -> { store.dispatchOnMain(GlobalAction.Onboarding.Stop) OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse) } - CreateTwinWalletMode.RecreateWallet -> { + is CreateTwinWalletMode.RecreateWallet -> { scope.launch { val walletsRepository = store.inject(DaggerGraphState::walletsRepository) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt index a8867a1a2c..83e641bc8a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt @@ -32,7 +32,7 @@ data class TwinCardsState( val steps: List get() = when (mode) { - CreateTwinWalletMode.CreateWallet -> listOf( + is CreateTwinWalletMode.CreateWallet -> listOf( TwinCardsStep.None, TwinCardsStep.CreateFirstWallet, TwinCardsStep.CreateSecondWallet, @@ -40,7 +40,7 @@ data class TwinCardsState( TwinCardsStep.TopUpWallet, TwinCardsStep.Done, ) - CreateTwinWalletMode.RecreateWallet -> listOf( + is CreateTwinWalletMode.RecreateWallet -> listOf( TwinCardsStep.None, TwinCardsStep.CreateFirstWallet, TwinCardsStep.CreateSecondWallet, @@ -56,7 +56,13 @@ data class TwinCardsState( get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet } -enum class CreateTwinWalletMode { CreateWallet, RecreateWallet } +sealed class CreateTwinWalletMode { + data object CreateWallet : CreateTwinWalletMode() + + data class RecreateWallet( + val scanResponse: ScanResponse, + ) : CreateTwinWalletMode() +} sealed class TwinCardsStep { object None : TwinCardsStep() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt index 69db8e0120..eda118c3dc 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt @@ -56,10 +56,10 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment( override fun configureTransitions() { when (store.state.twinCardsState.mode) { - CreateTwinWalletMode.CreateWallet -> { + is CreateTwinWalletMode.CreateWallet -> { super.configureTransitions() } - CreateTwinWalletMode.RecreateWallet -> { + is CreateTwinWalletMode.RecreateWallet -> { configureDefaultTransactions() } } @@ -391,8 +391,8 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment( tvBody.setText(R.string.onboarding_done_body) val layout = when (state.mode) { - CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins - CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done + is CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins + is CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done } updateConstraints(state.currentStep, layout) } From 2044fd0da2585e99eccda47353d418415b071a5f Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 31 Jul 2024 15:15:37 +0300 Subject: [PATCH 38/53] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index e5f1b0cf1f..5b292b2371 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,7 +87,7 @@ markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.12-701" +tangemBlockchainSdk = "release-app_5.12-715" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.12-373" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 58cab30bd392ecc74dc12b1a753f74743e92163d Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Aug 2024 17:52:39 +0500 Subject: [PATCH 39/53] Updated on 2026-08-14 --- .../usecase/SendTransactionUseCase.kt | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt index faccbea7e4..96b5db01fa 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt @@ -14,6 +14,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.common.TapWorkarounds.isStart2Coin +import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.DemoTransactionSender import com.tangem.domain.tokens.model.Network @@ -37,7 +38,10 @@ class SendTransactionUseCase( userWallet: UserWallet, network: Network, ): Either { - val signer = cardSdkConfigRepository.getCommonSigner(cardId = null) + val card = userWallet.scanResponse.card + val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins + + val signer = cardSdkConfigRepository.getCommonSigner(cardId = card.cardId.takeIf { isCardNotBackedUp }) val linkedTerminal = cardSdkConfigRepository.isLinkedTerminal() if (userWallet.scanResponse.card.isStart2Coin) { @@ -105,22 +109,7 @@ class SendTransactionUseCase( } val error = result.error as? BlockchainSdkError ?: return SendTransactionError.UnknownError() return when (error) { - is BlockchainSdkError.WrappedTangemError -> { - if (error.code == USER_CANCELLED_ERROR_CODE) { - SendTransactionError.UserCancelledError - } else { - val tangemError = error.tangemError - if (tangemError is TangemSdkError) { - val resource = tangemError.localizedDescriptionRes() - val resId = resource.resId ?: R.string.common_unknown_error - val resArgs = resource.args.map { it.value } - val textReference = resourceReference(resId, wrappedList(resArgs)) - SendTransactionError.TangemSdkError(tangemError.code, textReference) - } else { - SendTransactionError.BlockchainSdkError(error.code, tangemError.customMessage) - } - } - } + is BlockchainSdkError.WrappedTangemError -> parseWrappedError(error) is BlockchainSdkError.CreateAccountUnderfunded -> { val minAmount = error.minReserve val minValue = minAmount.value?.toFormattedString(minAmount.decimals).orEmpty() @@ -134,4 +123,26 @@ class SendTransactionUseCase( } } } + + private fun parseWrappedError(error: BlockchainSdkError.WrappedTangemError): SendTransactionError { + return if (error.code == USER_CANCELLED_ERROR_CODE) { + SendTransactionError.UserCancelledError + } else { + when (val tangemError = error.tangemError) { + is TangemSdkError -> { + val resource = tangemError.localizedDescriptionRes() + val resId = resource.resId ?: R.string.common_unknown_error + val resArgs = resource.args.map { it.value } + val textReference = resourceReference(resId, wrappedList(resArgs)) + SendTransactionError.TangemSdkError(tangemError.code, textReference) + } + is BlockchainSdkError.WrappedTangemError -> { + parseWrappedError(tangemError) // todo remove when sdk errors are revised + } + else -> { + SendTransactionError.BlockchainSdkError(error.code, tangemError.customMessage) + } + } + } + } } \ No newline at end of file From 186f8f77c49c4990cb75e0de911272b4a2a6f987 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Aug 2024 16:44:47 +0300 Subject: [PATCH 40/53] Updated on 2026-08-14 --- features/referral/domain/build.gradle.kts | 1 + .../referral/domain/ReferralInteractorImpl.kt | 13 ++++++++++++- .../feature/referral/domain/errors/ReferralError.kt | 8 ++++++++ .../referral/viewmodels/ReferralViewModel.kt | 4 ++-- .../crypto/models/errors/UserCancelledException.kt | 3 --- 5 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 features/referral/domain/src/main/java/com/tangem/feature/referral/domain/errors/ReferralError.kt delete mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/models/errors/UserCancelledException.kt diff --git a/features/referral/domain/build.gradle.kts b/features/referral/domain/build.gradle.kts index 0ae67f2d5f..4206513db7 100644 --- a/features/referral/domain/build.gradle.kts +++ b/features/referral/domain/build.gradle.kts @@ -32,6 +32,7 @@ dependencies { implementation(deps.arrow.core) implementation(deps.jodatime) implementation(deps.timber) + implementation(deps.tangem.card.core) /** DI */ implementation(deps.hilt.android) diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt index 36782f4e1d..04e3bb3028 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt @@ -1,10 +1,12 @@ package com.tangem.feature.referral.domain import arrow.core.getOrElse +import com.tangem.common.core.TangemSdkError import com.tangem.domain.card.DerivePublicKeysUseCase import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.feature.referral.domain.errors.ReferralError import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.feature.referral.domain.models.TokenData import com.tangem.lib.crypto.UserWalletManager @@ -42,7 +44,7 @@ internal class ReferralInteractorImpl( val cryptoCurrency = repository.getCryptoCurrency(userWalletId = userWallet.walletId, tokenData = tokenData) derivePublicKeysUseCase(userWallet.walletId, listOfNotNull(cryptoCurrency)).getOrElse { Timber.e("Failed to derive public keys: $it") - throw it + throw it.mapToDomainError() } addCryptoCurrenciesUseCase( @@ -67,4 +69,13 @@ internal class ReferralInteractorImpl( tokensForReferral.clear() tokensForReferral.addAll(tokens) } + + private fun Throwable.mapToDomainError(): ReferralError { + if (this !is TangemSdkError) return ReferralError.DataError(this) + return if (this is TangemSdkError.UserCancelled) { + ReferralError.UserCancelledException + } else { + ReferralError.SdkError + } + } } \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/errors/ReferralError.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/errors/ReferralError.kt new file mode 100644 index 0000000000..8d78d31fdd --- /dev/null +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/errors/ReferralError.kt @@ -0,0 +1,8 @@ +package com.tangem.feature.referral.domain.errors + +sealed class ReferralError : Exception() { + data object UserCancelledException : ReferralError() + data object SdkError : ReferralError() + + data class DataError(val throwable: Throwable) : ReferralError() +} \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt index 6565947793..236d0c93db 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt @@ -13,6 +13,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.referral.analytics.ReferralEvents import com.tangem.feature.referral.domain.ReferralInteractor +import com.tangem.feature.referral.domain.errors.ReferralError import com.tangem.feature.referral.domain.models.DiscountType import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.feature.referral.domain.models.ReferralInfo @@ -21,7 +22,6 @@ import com.tangem.feature.referral.models.ReferralStateHolder import com.tangem.feature.referral.models.ReferralStateHolder.ErrorSnackbar import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState import com.tangem.feature.referral.router.ReferralRouter -import com.tangem.lib.crypto.models.errors.UserCancelledException import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runCatching import dagger.hilt.android.lifecycle.HiltViewModel @@ -95,7 +95,7 @@ internal class ReferralViewModel @Inject constructor( runCatching(dispatchers.io) { referralInteractor.startReferral(userWalletId) } .onSuccess(::showContent) .onFailure { throwable -> - if (throwable is UserCancelledException) { + if (throwable is ReferralError.UserCancelledException) { lastReferralData?.let { referralData -> showContent(referralData) } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/errors/UserCancelledException.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/errors/UserCancelledException.kt deleted file mode 100644 index bdaf360494..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/errors/UserCancelledException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.tangem.lib.crypto.models.errors - -class UserCancelledException : Exception() \ No newline at end of file From e8301388dbeba0290d394c7349a834df9423a7aa Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Aug 2024 13:33:39 +0300 Subject: [PATCH 41/53] Updated on 2026-08-14 --- .../ui/cardsettings/CardSettingsFragment.kt | 11 +---- .../ui/cardsettings/CardSettingsScreen.kt | 17 ++++---- .../cardsettings/CardSettingsScreenState.kt | 1 + .../ui/cardsettings/CardSettingsViewModel.kt | 33 ++++++++------ .../AccessCodeRecoveryViewModel.kt | 43 ++++++++----------- .../domain/CardSettingsInteractor.kt | 35 +++++++++++++++ .../ui/resetcard/ResetCardViewModel.kt | 4 ++ .../com/tangem/common/routing/AppRoute.kt | 8 +--- 8 files changed, 89 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/domain/CardSettingsInteractor.kt diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt index 6054581056..328815f377 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt @@ -5,11 +5,8 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.common.routing.AppRouter import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.store import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -25,12 +22,6 @@ internal class CardSettingsFragment : ComposeFragment() { override fun ScreenContent(modifier: Modifier) { val state by viewModel.screenState.collectAsStateWithLifecycle() - CardSettingsScreen( - modifier = modifier, - state = state, - onBackClick = { - store.dispatchNavigationAction(AppRouter::pop) - }, - ) + CardSettingsScreen(modifier = modifier, state = state) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt index b0e2785b32..57f1103703 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -1,10 +1,13 @@ package com.tangem.tap.features.details.ui.cardsettings import android.content.res.Configuration -import androidx.compose.foundation.* +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -13,18 +16,14 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.features.details.ui.common.DetailsMainButton import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold import com.tangem.wallet.R @Composable -internal fun CardSettingsScreen( - state: CardSettingsScreenState, - onBackClick: () -> Unit, - modifier: Modifier = Modifier, -) { +internal fun CardSettingsScreen(state: CardSettingsScreenState, modifier: Modifier = Modifier) { val needReadCard = state.cardDetails == null SettingsScreensScaffold( @@ -37,7 +36,7 @@ internal fun CardSettingsScreen( } }, titleRes = R.string.card_settings_title, - onBackClick = onBackClick, + onBackClick = state.onBackClick, ) } @@ -180,7 +179,7 @@ private fun CardSettings(state: CardSettingsScreenState) { // region Preview @Composable private fun CardSettingsScreenStateSample() { - CardSettingsScreen(state = CardSettingsScreenState(onScanCardClick = {}, onElementClick = {}), {}) + CardSettingsScreen(state = CardSettingsScreenState(onBackClick = {}, onScanCardClick = {}, onElementClick = {})) } @Preview(showBackground = true, widthDp = 360) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt index 46425706b4..bbde2c8579 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt @@ -12,6 +12,7 @@ internal data class CardSettingsScreenState( val cardDetails: List? = null, val onScanCardClick: () -> Unit, val onElementClick: (CardInfo) -> Unit, + val onBackClick: () -> Unit, ) internal sealed class CardInfo( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index 47425804ff..e3fe0469f9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope import com.tangem.common.CompletionResult import com.tangem.common.doOnSuccess import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter import com.tangem.common.routing.bundle.unbundle import com.tangem.core.analytics.Analytics import com.tangem.domain.card.ScanCardProcessor @@ -23,14 +24,14 @@ import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.* import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.store import com.tangem.wallet.R import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.update +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -39,6 +40,7 @@ import javax.inject.Inject internal class CardSettingsViewModel @Inject constructor( private val scanCardProcessor: ScanCardProcessor, private val tangemSdkManager: TangemSdkManager, + private val cardSettingsInteractor: CardSettingsInteractor, savedStateHandle: SavedStateHandle, ) : ViewModel() { @@ -46,24 +48,28 @@ internal class CardSettingsViewModel @Inject constructor( ?.unbundle(UserWalletId.serializer()) ?: error("User wallet ID is required for CardSettingsViewModel") - private val scannedScanResponse = MutableStateFlow(value = null) - val screenState: MutableStateFlow = MutableStateFlow(getInitialState()) + init { + cardSettingsInteractor.scannedScanResponse + .filterNotNull() + .onEach(::updateCardDetails) + .launchIn(viewModelScope) + } + private fun getInitialState() = CardSettingsScreenState( cardDetails = null, onElementClick = ::handleClickingItem, onScanCardClick = ::scanCard, + onBackClick = ::onBackClick, ) private fun scanCard() = viewModelScope.launch { scanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true) .doOnSuccess { scanResponse -> - scannedScanResponse.value = scanResponse - val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() if (userWalletId == scannedUserWalletId || scannedUserWalletId == null) { - updateCardDetails(scanResponse) + cardSettingsInteractor.initialize(scanResponse) } else { store.dispatchDialogShow( AppDialog.SimpleOkDialogRes( @@ -141,16 +147,14 @@ internal class CardSettingsViewModel @Inject constructor( } } is CardInfo.AccessCodeRecovery -> { - store.dispatchNavigationAction { - push(route = AppRoute.AccessCodeRecovery(userWalletId)) - } + store.dispatchNavigationAction { push(AppRoute.AccessCodeRecovery) } } else -> {} } } private fun resetWalletToFactorySettings() { - val scanResponse = requireNotNull(scannedScanResponse.value) { + val scanResponse = requireNotNull(cardSettingsInteractor.scannedScanResponse.value) { "Impossible to reset card if ScanResponse is null" } @@ -181,7 +185,7 @@ internal class CardSettingsViewModel @Inject constructor( } private fun changeAccessCode() = viewModelScope.launch { - val scanResponse = requireNotNull(scannedScanResponse.value) { "Scan response is null" } + val scanResponse = requireNotNull(cardSettingsInteractor.scannedScanResponse.value) { "Scan response is null" } when (val result = tangemSdkManager.setAccessCode(scanResponse.card.cardId)) { is CompletionResult.Success -> Analytics.send(Settings.CardSettings.UserCodeChanged()) @@ -196,4 +200,9 @@ internal class CardSettingsViewModel @Inject constructor( val isNotAllowed = hasPermanentWallet || cardTypesResolver.isStart2Coin() return !isNotAllowed } + + private fun onBackClick() { + cardSettingsInteractor.clear() + store.dispatchNavigationAction(AppRouter::pop) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt index 8404af2beb..d65fb74a54 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt @@ -1,23 +1,16 @@ package com.tangem.tap.features.details.ui.cardsettings.coderecovery -import android.os.Bundle -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import arrow.core.getOrElse import com.tangem.common.doOnSuccess -import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter -import com.tangem.common.routing.bundle.unbundle import com.tangem.core.analytics.Analytics import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.isAccessCodeRecoveryEnabled import com.tangem.tap.store import dagger.hilt.android.lifecycle.HiltViewModel @@ -28,25 +21,21 @@ import javax.inject.Inject @HiltViewModel internal class AccessCodeRecoveryViewModel @Inject constructor( - private val getUserWalletUseCase: GetUserWalletUseCase, private val tangemSdkManager: TangemSdkManager, - savedStateHandle: SavedStateHandle, + private val cardSettingsInteractor: CardSettingsInteractor, ) : ViewModel() { - private val userWalletId = savedStateHandle.get(AppRoute.AccessCodeRecovery.USER_WALLET_ID_KEY) - ?.unbundle(UserWalletId.serializer()) - ?: error("UserWalletId is required for AccessCodeRecoveryViewModel") + private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value + ?: error("Scan response is null") val screenState = MutableStateFlow( value = getInitialState(), ) private fun getInitialState(): AccessCodeRecoveryScreenState { - val userWallet = getUserWallet() - val isEnabled = isAccessCodeRecoveryEnabled( - typeResolver = userWallet.scanResponse.cardTypesResolver, - card = userWallet.scanResponse.card, + typeResolver = scannedScanResponse.cardTypesResolver, + card = scannedScanResponse.card, ) return AccessCodeRecoveryScreenState( @@ -59,11 +48,10 @@ internal class AccessCodeRecoveryViewModel @Inject constructor( } private fun saveChanges() = viewModelScope.launch { - val userWallet = getUserWallet() val isEnabled = screenState.value.enabledSelection tangemSdkManager - .setAccessCodeRecoveryEnabled(userWallet.cardId, isEnabled) + .setAccessCodeRecoveryEnabled(scannedScanResponse.card.cardId, isEnabled) .doOnSuccess { Analytics.send( Settings.CardSettings.AccessCodeRecoveryChanged( @@ -71,6 +59,16 @@ internal class AccessCodeRecoveryViewModel @Inject constructor( ), ) + cardSettingsInteractor.update { scanResponse -> + scanResponse.copy( + card = scanResponse.card.copy( + userSettings = scanResponse.card.userSettings?.copy( + isUserCodeRecoveryAllowed = isEnabled, + ), + ), + ) + } + store.dispatchNavigationAction(AppRouter::pop) } } @@ -78,14 +76,9 @@ internal class AccessCodeRecoveryViewModel @Inject constructor( private fun selectOption(isEnabled: Boolean) { screenState.update { it.copy( + enabledSelection = isEnabled, isSaveChangesEnabled = isEnabled != it.enabledOnCard, ) } } - - private fun getUserWallet(): UserWallet { - return getUserWalletUseCase(userWalletId).getOrElse { - error("Unable to get user wallet $userWalletId: $it") - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/domain/CardSettingsInteractor.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/domain/CardSettingsInteractor.kt new file mode 100644 index 0000000000..73bbf10991 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/domain/CardSettingsInteractor.kt @@ -0,0 +1,35 @@ +package com.tangem.tap.features.details.ui.cardsettings.domain + +import com.tangem.domain.models.scan.ScanResponse +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Interactor for sharing logic and data between all card settings screens + * +[REDACTED_AUTHOR] + */ +@Singleton +internal class CardSettingsInteractor @Inject constructor() { + + private val _scannedScanResponse = MutableStateFlow(value = null) + val scannedScanResponse: StateFlow = _scannedScanResponse + + fun initialize(scanResponse: ScanResponse) { + _scannedScanResponse.value = scanResponse + } + + fun update(transform: (ScanResponse) -> ScanResponse) { + _scannedScanResponse.update { + requireNotNull(it) + transform(it) + } + } + + fun clear() { + _scannedScanResponse.value = null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt index 41d0535147..fefbdf122e 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -23,6 +23,7 @@ import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.features.details.redux.ResetCardDialog +import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription import com.tangem.tap.store import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE @@ -44,6 +45,7 @@ internal class ResetCardViewModel @Inject constructor( private val deleteWalletUseCase: DeleteWalletUseCase, private val userWalletsListManager: UserWalletsListManager, private val analyticsEventHandler: AnalyticsEventHandler, + private val cardSettingsInteractor: CardSettingsInteractor, savedStateHandle: SavedStateHandle, ) : ViewModel() { @@ -255,6 +257,8 @@ internal class ResetCardViewModel @Inject constructor( } private fun finishFullReset() { + cardSettingsInteractor.clear() + val newSelectedWallet = userWalletsListManager.selectedUserWalletSync if (newSelectedWallet != null) { diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 8bb8395a7d..20d08dcf6a 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -177,15 +177,9 @@ sealed class AppRoute(val path: String) : Route { } @Serializable - data class AccessCodeRecovery( - val userWalletId: UserWalletId, - ) : AppRoute(path = "/access_code_recovery/${userWalletId.stringValue}"), RouteBundleParams { + data object AccessCodeRecovery : AppRoute(path = "/access_code_recovery"), RouteBundleParams { override fun getBundle(): Bundle = bundle(serializer()) - - companion object { - const val USER_WALLET_ID_KEY = "userWalletId" - } } @Serializable From 07d5ede58cdc82df4e700ecbf28e5a2a62ade340 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Aug 2024 18:08:35 +0300 Subject: [PATCH 42/53] Updated on 2026-08-14 --- .../redux/OnboardingWalletMiddleware.kt | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index c86ed79d1d..0e32a392c4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -35,6 +35,7 @@ import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.wallet.R +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware @@ -44,6 +45,8 @@ object OnboardingWalletMiddleware { val handler = onboardingWalletMiddleware } +private const val HIDE_PROGRESS_DELAY = 400L + private val onboardingWalletMiddleware: Middleware = { dispatch, state -> { next -> { action -> @@ -161,17 +164,13 @@ private fun handleWalletAction(action: Action) { store.dispatch(GlobalAction.Onboarding.Stop) if (scanResponse == null) { - store.dispatchNavigationAction(AppRouter::pop) - store.dispatch(HomeAction.ReadCard(scope = action.scope)) + action.scope.launch { + readCard { newScanResponse -> + handleFinishOnboardind(newScanResponse) + } + } } else { - val backupState = store.state.onboardingWalletState.backupState - val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState) - OnboardingHelper.trySaveWalletAndNavigateToWalletScreen( - scanResponse = updatedScanResponse, - accessCode = backupState.accessCode, - backupCardsIds = backupState.backupCardIds, - hasBackupError = backupState.hasBackupError, - ) + handleFinishOnboardind(scanResponse) } } is OnboardingWalletAction.ResumeBackup -> { @@ -196,6 +195,43 @@ private fun handleWalletAction(action: Action) { } } +private fun handleFinishOnboardind(scanResponse: ScanResponse) { + val backupState = store.state.onboardingWalletState.backupState + val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState) + OnboardingHelper.trySaveWalletAndNavigateToWalletScreen( + scanResponse = updatedScanResponse, + accessCode = backupState.accessCode, + backupCardsIds = backupState.backupCardIds, + hasBackupError = backupState.hasBackupError, + ) +} + +private suspend fun readCard(onSuccess: (ScanResponse) -> Unit) { + val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes() + + store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( + isBiometricsRequestPolicy = shouldSaveAccessCodes, + ) + + store.inject(DaggerGraphState::scanCardProcessor).scan( + analyticsSource = com.tangem.core.analytics.models.AnalyticsParam.ScreensSources.Intro, + onProgressStateChange = { showProgress -> + if (showProgress) { + store.dispatch(HomeAction.ScanInProgress(scanInProgress = true)) + } else { + delay(HIDE_PROGRESS_DELAY) + store.dispatch(HomeAction.ScanInProgress(scanInProgress = false)) + } + }, + onFailure = { + Timber.e(it, "Unable to scan card") + delay(HIDE_PROGRESS_DELAY) + store.dispatch(HomeAction.ScanInProgress(scanInProgress = false)) + }, + onSuccess = onSuccess, + ) +} + private suspend fun loadArtworkForUnfinishedBackup( cardId: String, cardPublicKey: ByteArray, From e1b50efbe0e64fdb1970d566eea464028ae8e5e2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Aug 2024 15:40:30 +0500 Subject: [PATCH 43/53] Updated on 2026-08-14 --- .../tap/di/domain/SettingsDomainModule.kt | 24 ----------- .../core/ui/utils/RequestPushPermission.kt | 13 +----- .../settings/DefaultPermissionRepository.kt | 43 +------------------ .../settings/DelayPermissionRequestUseCase.kt | 13 ------ .../IsFirstTimeAskingPermissionUseCase.kt | 11 ----- .../SetFirstTimeAskingPermissionUseCase.kt | 11 ----- .../repositories/PermissionRepository.kt | 17 -------- .../PushNotificationAnalyticEvents.kt | 8 +--- .../impl/PushNotificationsFragment.kt | 3 +- .../ui/PushNotificationsScreen.kt | 9 ++-- .../viewmodel/PushNotificationViewModel.kt | 25 ++--------- .../PushNotificationsClickIntents.kt | 4 +- .../PushNotificationsBottomSheetConfig.kt | 5 +-- .../PushNotificationsBottomSheet.kt | 15 ++----- .../wallet/viewmodels/WalletViewModel.kt | 20 +-------- .../WalletPushPermissionClickIntents.kt | 22 +--------- 16 files changed, 21 insertions(+), 222 deletions(-) delete mode 100644 domain/settings/src/main/java/com/tangem/domain/settings/DelayPermissionRequestUseCase.kt delete mode 100644 domain/settings/src/main/java/com/tangem/domain/settings/IsFirstTimeAskingPermissionUseCase.kt delete mode 100644 domain/settings/src/main/java/com/tangem/domain/settings/SetFirstTimeAskingPermissionUseCase.kt 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 7ff162a0d1..c05129c777 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 @@ -184,30 +184,6 @@ internal object SettingsDomainModule { return NeverToInitiallyAskPermissionUseCase(repository = permissionRepository) } - @Provides - @Singleton - fun provideIsFirstTimeAskingPermissionUseCase( - permissionRepository: PermissionRepository, - ): IsFirstTimeAskingPermissionUseCase { - return IsFirstTimeAskingPermissionUseCase(repository = permissionRepository) - } - - @Provides - @Singleton - fun provideSetFirstTimeAskingPushPermissionUseCase( - permissionRepository: PermissionRepository, - ): SetFirstTimeAskingPermissionUseCase { - return SetFirstTimeAskingPermissionUseCase(repository = permissionRepository) - } - - @Provides - @Singleton - fun provideDelayPermissionRequestUseCase( - permissionRepository: PermissionRepository, - ): DelayPermissionRequestUseCase { - return DelayPermissionRequestUseCase(repository = permissionRepository) - } - @Provides @Singleton fun provideShouldAskPermissionUseCase(permissionRepository: PermissionRepository): ShouldAskPermissionUseCase { diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt index cac63e19f7..1b21fff1ef 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.MutableState import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState -import com.google.accompanist.permissions.shouldShowRationale /** * Returns push permission requester. @@ -16,21 +15,13 @@ import com.google.accompanist.permissions.shouldShowRationale @OptIn(ExperimentalPermissionsApi::class) @Composable fun requestPushPermission( - isFirstTimeAsking: Boolean, pushPermission: String?, isClicked: MutableState, onAllow: () -> Unit, onDeny: () -> Unit, - onOpenSettings: () -> Unit, ): () -> Unit { val permissionState = pushPermission?.let { permission -> - val tempPermissionState = rememberPermissionState(permission = permission) - rememberPermissionState(permission = permission) { - val isGranted = tempPermissionState.status.isGranted - val shouldShowRationale = tempPermissionState.status.shouldShowRationale - - if (isGranted && !shouldShowRationale && !isFirstTimeAsking) onOpenSettings() - } + rememberPermissionState(permission = permission) } // Check if user granted permission and close bottom sheet @@ -44,7 +35,7 @@ fun requestPushPermission( } return if (permissionState == null) { - onOpenSettings + {} } else { permissionState::launchPermissionRequest } diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultPermissionRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultPermissionRepository.kt index 6497ea59b4..e2528964fe 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultPermissionRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultPermissionRepository.kt @@ -1,11 +1,6 @@ package com.tangem.data.settings -import android.os.SystemClock import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.preferences.PreferencesKeys -import com.tangem.datasource.local.preferences.PreferencesKeys.getIsFirstTimeAskingPermission -import com.tangem.datasource.local.preferences.PreferencesKeys.getPermissionDaysCount -import com.tangem.datasource.local.preferences.PreferencesKeys.getPermissionLaunchCount import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowInitialPermissionScreen import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowPermission import com.tangem.datasource.local.preferences.utils.getSyncOrDefault @@ -30,47 +25,11 @@ internal class DefaultPermissionRepository( ) } - override suspend fun isFirstTimeAskingPermission(permission: String): Boolean = - appPreferencesStore.getSyncOrDefault( - key = getIsFirstTimeAskingPermission(permission), - default = true, - ) - - override suspend fun setFirstTimeAskingPermission(permission: String, value: Boolean) { - appPreferencesStore.store( - key = getIsFirstTimeAskingPermission(permission), - value = value, - ) - } - override suspend fun shouldAskPermission(permission: String): Boolean { - val shouldAskPermission = appPreferencesStore.getSyncOrDefault(getShouldShowPermission(permission), true) - val delayedLaunches = appPreferencesStore.getSyncOrDefault(getPermissionLaunchCount(permission), 0) - val delayedDays = appPreferencesStore.getSyncOrDefault(getPermissionDaysCount(permission), 0) - val currentLaunchCounter = appPreferencesStore.getSyncOrDefault(PreferencesKeys.APP_LAUNCH_COUNT_KEY, 0) - - val nowMillis = SystemClock.elapsedRealtime() - val isDaysDelayed = delayedDays < nowMillis - val isLaunchesDelayed = delayedLaunches < currentLaunchCounter - return shouldAskPermission && isDaysDelayed && isLaunchesDelayed + return appPreferencesStore.getSyncOrDefault(getShouldShowPermission(permission), true) } override suspend fun neverAskPermission(permission: String) { appPreferencesStore.store(key = getShouldShowPermission(permission), value = false) } - - override suspend fun delayPermissionAsking(permission: String) { - appPreferencesStore.editData { - val appLaunchCounter = it.getOrDefault(PreferencesKeys.APP_LAUNCH_COUNT_KEY, 0) - val nowMillis = SystemClock.elapsedRealtime() - - it[getPermissionLaunchCount(permission)] = appLaunchCounter + DELAY_LAUNCH_COUNT - it[getPermissionDaysCount(permission)] = nowMillis + DELAY_DAYS_COUNT - } - } - - private companion object { - const val DELAY_LAUNCH_COUNT = 5 - const val DELAY_DAYS_COUNT = 3L * 24 * 3600 * 1000 // 3 days in millis - } } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/DelayPermissionRequestUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/DelayPermissionRequestUseCase.kt deleted file mode 100644 index bf300cb779..0000000000 --- a/domain/settings/src/main/java/com/tangem/domain/settings/DelayPermissionRequestUseCase.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.domain.settings - -import arrow.core.Either -import com.tangem.domain.settings.repositories.PermissionRepository - -class DelayPermissionRequestUseCase( - private val repository: PermissionRepository, -) { - - suspend operator fun invoke(permission: String): Either = Either.catch { - repository.delayPermissionAsking(permission) - } -} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/IsFirstTimeAskingPermissionUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/IsFirstTimeAskingPermissionUseCase.kt deleted file mode 100644 index 000bfd7ed1..0000000000 --- a/domain/settings/src/main/java/com/tangem/domain/settings/IsFirstTimeAskingPermissionUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.domain.settings - -import arrow.core.Either -import com.tangem.domain.settings.repositories.PermissionRepository - -class IsFirstTimeAskingPermissionUseCase(private val repository: PermissionRepository) { - - suspend operator fun invoke(permission: String): Either = Either.catch { - repository.isFirstTimeAskingPermission(permission) - } -} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/SetFirstTimeAskingPermissionUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/SetFirstTimeAskingPermissionUseCase.kt deleted file mode 100644 index fa5d057e77..0000000000 --- a/domain/settings/src/main/java/com/tangem/domain/settings/SetFirstTimeAskingPermissionUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.domain.settings - -import arrow.core.Either -import com.tangem.domain.settings.repositories.PermissionRepository - -class SetFirstTimeAskingPermissionUseCase(private val repository: PermissionRepository) { - - suspend operator fun invoke(permission: String): Either = Either.catch { - repository.setFirstTimeAskingPermission(permission, false) - } -} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PermissionRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PermissionRepository.kt index 721bfe90be..21b476b233 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PermissionRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PermissionRepository.kt @@ -13,18 +13,6 @@ interface PermissionRepository { */ suspend fun neverInitiallyShowPermissionScreen(permission: String) - /** - * Indicates which time [permission] was asked via platform dialog. - * NOTE: Use this method to indicate either reroute to settings or display platform dialog. - */ - suspend fun isFirstTimeAskingPermission(permission: String): Boolean - - /** - * Sets value indicating that [permission] was asked via platform dialog. - * NOTE: Use this method to indicate either reroute to settings or display platform dialog. - */ - suspend fun setFirstTimeAskingPermission(permission: String, value: Boolean) - /** * Is clear to ask [permission]. * User could already granted or permanently denied permission @@ -36,9 +24,4 @@ interface PermissionRepository { * Permanently deny [permission] and never request again */ suspend fun neverAskPermission(permission: String) - - /** - * Delay next [permission] request for some time or active sessions - */ - suspend fun delayPermissionAsking(permission: String) } \ No newline at end of file diff --git a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/analytics/PushNotificationAnalyticEvents.kt b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/analytics/PushNotificationAnalyticEvents.kt index c35b196ec8..ae6f90b7a1 100644 --- a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/analytics/PushNotificationAnalyticEvents.kt +++ b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/analytics/PushNotificationAnalyticEvents.kt @@ -17,19 +17,15 @@ sealed class PushNotificationAnalyticEvents( ), ) - data class ButtonLater( + data class ButtonCancel( val source: AnalyticsParam.ScreensSources, ) : PushNotificationAnalyticEvents( - event = "Button - Later", + event = "Button - Cancel", params = mapOf( AnalyticsParam.SOURCE to source.value, ), ) - data object ButtonCancel : PushNotificationAnalyticEvents( - event = "Button - Cancel", - ) - data class PermissionStatus( val isAllowed: Boolean, ) : PushNotificationAnalyticEvents( diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/PushNotificationsFragment.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/PushNotificationsFragment.kt index 1d6fb08e71..c7110e411f 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/PushNotificationsFragment.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/PushNotificationsFragment.kt @@ -26,10 +26,9 @@ internal class PushNotificationsFragment : ComposeFragment() { NavigationBar3ButtonsScrim() PushNotificationsScreen( onRequest = viewModel::onRequest, - onRequestLater = viewModel::onRequestLater, + onNeverRequest = viewModel::onNeverRequest, onAllowPermission = viewModel::onAllowPermission, onDenyPermission = viewModel::onDenyPermission, - onOpenSettings = viewModel::openSettings, ) } diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt index c039d9109d..a73e852c64 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt @@ -17,18 +17,15 @@ import kotlinx.collections.immutable.persistentListOf @Composable internal fun PushNotificationsScreen( onRequest: () -> Unit, - onRequestLater: () -> Unit, + onNeverRequest: () -> Unit, onAllowPermission: () -> Unit, onDenyPermission: () -> Unit, - onOpenSettings: () -> Unit, ) { val isClicked = remember { mutableStateOf(false) } val requestPushPermission = requestPushPermission( - isFirstTimeAsking = true, isClicked = isClicked, onAllow = onAllowPermission, onDeny = onDenyPermission, - onOpenSettings = onOpenSettings, pushPermission = getPushPermissionOrNull(), ) @@ -54,8 +51,8 @@ internal fun PushNotificationsScreen( }, ), secondaryButton = ShowcaseButtonModel( - buttonText = resourceReference(R.string.common_later), - onClick = onRequestLater, + buttonText = resourceReference(R.string.common_cancel), + onClick = onNeverRequest, ), modifier = Modifier.systemBarsPadding(), ) diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationViewModel.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationViewModel.kt index b87d3f4f88..4a0f3ed9f8 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationViewModel.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationViewModel.kt @@ -4,11 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.navigation.settings.SettingsManager -import com.tangem.domain.settings.DelayPermissionRequestUseCase import com.tangem.domain.settings.NeverRequestPermissionUseCase -import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase -import com.tangem.domain.settings.SetFirstTimeAskingPermissionUseCase import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.pushnotifications.impl.navigation.DefaultPushNotificationsRouter @@ -19,12 +15,8 @@ import javax.inject.Inject @Suppress("LongParameterList") @HiltViewModel internal class PushNotificationViewModel @Inject constructor( - private val setFirstTimeAskingPermissionUseCase: SetFirstTimeAskingPermissionUseCase, - private val delayPermissionRequestUseCase: DelayPermissionRequestUseCase, private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase, - private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase, private val router: DefaultPushNotificationsRouter, - private val settingsManager: SettingsManager, private val analyticHandler: AnalyticsEventHandler, ) : ViewModel(), PushNotificationsClickIntents { @@ -32,19 +24,14 @@ internal class PushNotificationViewModel @Inject constructor( analyticHandler.send( PushNotificationAnalyticEvents.ButtonAllow(AnalyticsParam.ScreensSources.Stories), ) - viewModelScope.launch { - setFirstTimeAskingPermissionUseCase(PUSH_PERMISSION) - } } - override fun onRequestLater() { + override fun onNeverRequest() { analyticHandler.send( - PushNotificationAnalyticEvents.ButtonLater(AnalyticsParam.ScreensSources.Stories), + PushNotificationAnalyticEvents.ButtonCancel(AnalyticsParam.ScreensSources.Stories), ) viewModelScope.launch { - delayPermissionRequestUseCase(PUSH_PERMISSION) - setFirstTimeAskingPermissionUseCase(PUSH_PERMISSION) - neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) + neverRequestPermissionUseCase(PUSH_PERMISSION) router.openHome() } } @@ -55,7 +42,6 @@ internal class PushNotificationViewModel @Inject constructor( ) viewModelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) - neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) router.openHome() } } @@ -65,11 +51,8 @@ internal class PushNotificationViewModel @Inject constructor( PushNotificationAnalyticEvents.PermissionStatus(isAllowed = false), ) viewModelScope.launch { - delayPermissionRequestUseCase(PUSH_PERMISSION) - neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) + neverRequestPermissionUseCase(PUSH_PERMISSION) router.openHome() } } - - override fun openSettings() = settingsManager.openSettings() } \ No newline at end of file diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationsClickIntents.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationsClickIntents.kt index 2d4fe34217..63100190b7 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationsClickIntents.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationsClickIntents.kt @@ -3,11 +3,9 @@ package com.tangem.features.pushnotifications.impl.presentation.viewmodel internal interface PushNotificationsClickIntents { fun onRequest() - fun onRequestLater() + fun onNeverRequest() fun onAllowPermission() fun onDenyPermission() - - fun openSettings() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/PushNotificationsBottomSheetConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/PushNotificationsBottomSheetConfig.kt index c659d29d7c..6fd16546b2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/PushNotificationsBottomSheetConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/PushNotificationsBottomSheetConfig.kt @@ -3,11 +3,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.model import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent data class PushNotificationsBottomSheetConfig( - val isFirstTimeRequested: Boolean, - val wasInitiallyAsk: Boolean, val onRequest: () -> Unit, - val onRequestLater: () -> Unit, + val onNeverRequest: () -> Unit, val onAllow: () -> Unit, val onDeny: () -> Unit, - val openSettings: () -> Unit, ) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt index dcc56be741..aeaad6ef3f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt @@ -39,7 +39,6 @@ private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetC val isClicked = remember { mutableStateOf(false) } val requestPushPermission = requestPushPermission( pushPermission = getPushPermissionOrNull(), - isFirstTimeAsking = content.isFirstTimeRequested, isClicked = isClicked, onAllow = { content.onAllow() @@ -49,7 +48,6 @@ private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetC content.onDeny() onDismiss() }, - onOpenSettings = content.openSettings, ) Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) { @@ -78,13 +76,9 @@ private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetC ), ) { SecondaryButton( - text = if (content.wasInitiallyAsk) { - stringResource(R.string.common_later) - } else { - stringResource(R.string.common_cancel) - }, + text = stringResource(R.string.common_cancel), onClick = { - content.onRequestLater() + content.onNeverRequest() onDismiss() }, modifier = Modifier.weight(1f), @@ -110,13 +104,10 @@ private fun PushNotificationsSheetContent_Preview() { TangemThemePreview { PushNotificationsSheetContent( PushNotificationsBottomSheetConfig( - isFirstTimeRequested = false, - wasInitiallyAsk = false, onRequest = {}, - onRequestLater = {}, + onNeverRequest = {}, onAllow = {}, onDeny = {}, - openSettings = {}, ), onDismiss = {}, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index ed7f027de5..06bb5b53a9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.navigation.settings.SettingsManager import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.settings.* import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase @@ -63,11 +62,8 @@ internal class WalletViewModel @Inject constructor( private val walletDeepLinksHandler: WalletDeepLinksHandler, private val walletNameMigrationUseCase: WalletNameMigrationUseCase, private val refreshMultiCurrencyWalletQuotesUseCase: RefreshMultiCurrencyWalletQuotesUseCase, - private val shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase, - private val isFirstTimeAskingPermissionUseCase: IsFirstTimeAskingPermissionUseCase, private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles, - private val settingsManager: SettingsManager, analyticsEventsHandler: AnalyticsEventHandler, ) : ViewModel() { @@ -157,26 +153,14 @@ internal class WalletViewModel @Inject constructor( delay(timeMillis = 1_800) - val isFirstTimeRequested = isFirstTimeAskingPermissionUseCase(PUSH_PERMISSION).getOrElse { true } - val wasInitiallyAsk = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { true } - val onRequestLater: (Boolean) -> Unit = { isUserDismissed -> - if (wasInitiallyAsk) { - clickIntents.onDelayAskPushPermission(isUserDismissed) - } else { - clickIntents.onNeverAskPushPermission(isUserDismissed) - } - } stateHolder.showBottomSheet( content = PushNotificationsBottomSheetConfig( - isFirstTimeRequested = isFirstTimeRequested, - wasInitiallyAsk = wasInitiallyAsk, onRequest = clickIntents::onRequestPushPermission, - onRequestLater = { onRequestLater(false) }, + onNeverRequest = { clickIntents.onNeverAskPushPermission(false) }, onAllow = clickIntents::onAllowPushPermission, onDeny = clickIntents::onDenyPushPermission, - openSettings = settingsManager::openSettings, ), - onDismiss = { onRequestLater(true) }, + onDismiss = { clickIntents.onNeverAskPushPermission(true) }, ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt index 62fec7ce4f..13a301f372 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt @@ -2,9 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.domain.settings.DelayPermissionRequestUseCase import com.tangem.domain.settings.NeverRequestPermissionUseCase -import com.tangem.domain.settings.SetFirstTimeAskingPermissionUseCase import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import dagger.hilt.android.scopes.ViewModelScoped @@ -15,8 +13,6 @@ internal interface WalletPushPermissionClickIntents { fun onRequestPushPermission() - fun onDelayAskPushPermission(isUserDismissed: Boolean) - fun onNeverAskPushPermission(isUserDismissed: Boolean) fun onDenyPushPermission() @@ -26,9 +22,7 @@ internal interface WalletPushPermissionClickIntents { @ViewModelScoped internal class WalletPushPermissionClickIntentsImplementor @Inject constructor( - private val setFirstTimeAskingPermissionUseCase: SetFirstTimeAskingPermissionUseCase, private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase, - private val delayPermissionRequestUseCase: DelayPermissionRequestUseCase, private val analyticsEventHandler: AnalyticsEventHandler, ) : BaseWalletClickIntents(), WalletPushPermissionClickIntents { @@ -38,27 +32,13 @@ internal class WalletPushPermissionClickIntentsImplementor @Inject constructor( analyticsEventHandler.send( PushNotificationAnalyticEvents.ButtonAllow(AnalyticsParam.ScreensSources.Main), ) - viewModelScope.launch { - setFirstTimeAskingPermissionUseCase(PUSH_PERMISSION) - } - } - - override fun onDelayAskPushPermission(isUserDismissed: Boolean) { - if (!isUserDismissedDialog) return - isUserDismissedDialog = isUserDismissed - viewModelScope.launch { - analyticsEventHandler.send( - PushNotificationAnalyticEvents.ButtonLater(AnalyticsParam.ScreensSources.Main), - ) - delayPermissionRequestUseCase(PUSH_PERMISSION) - } } override fun onNeverAskPushPermission(isUserDismissed: Boolean) { if (!isUserDismissedDialog) return isUserDismissedDialog = isUserDismissed viewModelScope.launch { - analyticsEventHandler.send(PushNotificationAnalyticEvents.ButtonCancel) + PushNotificationAnalyticEvents.ButtonCancel(AnalyticsParam.ScreensSources.Main) neverRequestPermissionUseCase(PUSH_PERMISSION) } } From 6c9eb4860f4426131bed75e1b19fbf9eb45e48fe Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 2 Aug 2024 15:53:00 +0300 Subject: [PATCH 44/53] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 35af912b96..b5726a45bf 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 35af912b96ce0f871906061bd483d9cf3af7451d +Subproject commit b5726a45bf51b7763c5967afae4d3d687676240b From 5f96138f4f8621aa16a1518453266b1765223d15 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 5 Aug 2024 16:53:06 +0300 Subject: [PATCH 45/53] Updated on 2026-08-14 --- .../java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt b/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt index 549f514b01..7fe3ef0081 100644 --- a/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt @@ -1,8 +1,6 @@ package com.tangem.tap.common.url import android.content.Context -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK -import android.content.Intent.FLAG_ACTIVITY_NO_HISTORY import android.net.Uri import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent @@ -36,9 +34,6 @@ internal class CustomTabsUrlOpener : UrlOpener { ) .build() - // Open CustomTabsActivity as new task without saving into the stack - customTabsIntent.intent.setFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_NO_HISTORY) - customTabsIntent.launchUrl(context, Uri.parse(url)) } } \ No newline at end of file From 3df780105d66a29a058abfeaaab0458d424a138b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Aug 2024 10:32:38 +0300 Subject: [PATCH 46/53] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt b/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt index c56c238e57..6f1c8151d6 100644 --- a/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt +++ b/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt @@ -1,8 +1,6 @@ package com.tangem.tap.common import android.content.Context -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK -import android.content.Intent.FLAG_ACTIVITY_NO_HISTORY import android.net.Uri import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent @@ -26,9 +24,6 @@ class CustomTabsManager { ) .build() - // Open CustomTabsActivity as new task without saving into the stack - customTabsIntent.intent.setFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_NO_HISTORY) - customTabsIntent.launchUrl(context, Uri.parse(url)) } } \ No newline at end of file From b350717cd0a1caa20dc46b4f367c824dcb22b6ec Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Aug 2024 13:28:40 +0300 Subject: [PATCH 47/53] Updated on 2026-08-14 --- .../tokens/impl/presentation/ui/BriefNetworksList.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt index e122327ff8..4091e53c06 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt @@ -4,7 +4,10 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.Text @@ -123,14 +126,14 @@ internal fun HasMoreItem(moreCount: Int) { ) { Text( modifier = Modifier - .padding(TangemTheme.dimens.spacing4) .align(Alignment.Center) .drawWithContent { if (readyToDraw) drawContent() }, text = "+$count", style = textStyle, + color = TangemTheme.colors.text.tertiary, overflow = TextOverflow.Clip, onTextLayout = { textLayoutResult -> - if (textLayoutResult.didOverflowHeight) { + if (textLayoutResult.hasVisualOverflow) { textStyle = textStyle.copy(fontSize = textStyle.fontSize * 0.9) } else { readyToDraw = true From 3d0a434423a44d4919d2638e473434cc3d06e1a1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Aug 2024 16:37:09 +0500 Subject: [PATCH 48/53] Updated on 2026-08-14 --- .../impl/presentation/viewmodel/PushNotificationViewModel.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationViewModel.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationViewModel.kt index 4a0f3ed9f8..5b62923bbb 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationViewModel.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/viewmodel/PushNotificationViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.settings.NeverRequestPermissionUseCase +import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.pushnotifications.impl.navigation.DefaultPushNotificationsRouter @@ -16,6 +17,7 @@ import javax.inject.Inject @HiltViewModel internal class PushNotificationViewModel @Inject constructor( private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase, + private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase, private val router: DefaultPushNotificationsRouter, private val analyticHandler: AnalyticsEventHandler, ) : ViewModel(), PushNotificationsClickIntents { @@ -32,6 +34,7 @@ internal class PushNotificationViewModel @Inject constructor( ) viewModelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) + neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) router.openHome() } } @@ -42,6 +45,7 @@ internal class PushNotificationViewModel @Inject constructor( ) viewModelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) + neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) router.openHome() } } @@ -52,6 +56,7 @@ internal class PushNotificationViewModel @Inject constructor( ) viewModelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) + neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) router.openHome() } } From 83772083833522de12c50d497966b66f93b80299 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Aug 2024 15:24:14 +0400 Subject: [PATCH 49/53] Updated on 2026-08-14 --- .../tap/features/disclaimer/Disclaimer.kt | 38 +---------------- .../tap/features/disclaimer/DisclaimerType.kt | 42 +++---------------- .../local/preferences/PreferencesKeys.kt | 2 - .../tangem/data/card/DefaultCardRepository.kt | 25 ----------- .../domain/card/repository/CardRepository.kt | 5 --- 5 files changed, 7 insertions(+), 105 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt index f92bc4be2f..9cda07b754 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt @@ -6,14 +6,13 @@ import android.net.Uri [REDACTED_AUTHOR] */ interface Disclaimer { - fun type(): DisclaimerType fun getUri(): Uri suspend fun accept() suspend fun isAccepted(): Boolean } abstract class BaseDisclaimer( - protected val dataProvider: DisclaimerDataProvider, + private val dataProvider: DisclaimerDataProvider, ) : Disclaimer { val baseUrl = "https://tangem.com" @@ -26,46 +25,11 @@ abstract class BaseDisclaimer( } class DummyDisclaimer : Disclaimer { - override fun type(): DisclaimerType = DisclaimerType.Tangem override fun getUri(): Uri = Uri.parse("https://tangem.com/tangem_tos.html") override suspend fun accept() {} override suspend fun isAccepted(): Boolean = false } class TangemDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) { - override fun type(): DisclaimerType = DisclaimerType.Tangem override fun getUri(): Uri = Uri.parse("$baseUrl/tangem_tos.html") -} - -class Start2CoinDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) { - override fun type(): DisclaimerType = DisclaimerType.Start2Coin - override fun getUri(): Uri = Uri.parse("$baseUrl/" + filename(dataProvider.getLanguage(), getRegion())) - - @Suppress("ComplexMethod") - private fun filename(languageCode: String, regionCode: String?): String { - return when { - languageCode == "fr" && regionCode == "ch" -> "start2coin-fr-ch-tangem.html" - languageCode == "de" && regionCode == "ch" -> "start2coin-de-ch-tangem.html" - languageCode == "en" && regionCode == "ch" -> "start2coin-en-ch-tangem.html" - languageCode == "it" && regionCode == "ch" -> "start2coin-it-ch-tangem.html" - languageCode == "fr" && regionCode == "fr" -> "start2coin-fr-fr-tangem.html" - languageCode == "de" && regionCode == "at" -> "start2coin-de-at-tangem.html" - regionCode == "fr" -> "start2coin-fr-fr-tangem.html" - regionCode == "ch" -> "start2coin-en-ch-tangem.html" - regionCode == "at" -> "start2coin-de-at-tangem.html" - else -> "start2coin-fr-fr-tangem.html" - } - } - - private fun getRegion(): String? { - val cardId = dataProvider.getCardId() - if (cardId.isEmpty()) return null - - return when (cardId[1]) { - '0' -> "fr" - '1' -> "ch" - '2' -> "at" - else -> null - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt index 796452dcab..90f73cb4ad 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt @@ -1,58 +1,28 @@ package com.tangem.tap.features.disclaimer -import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.models.scan.CardDTO import com.tangem.tap.common.extensions.inject import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import java.util.Locale -/** -[REDACTED_AUTHOR] - */ -enum class DisclaimerType { - Tangem, - Start2Coin, - ; - - companion object { - fun get(cardDTO: CardDTO): DisclaimerType { - return when { - cardDTO.isStart2Coin -> Start2Coin - else -> Tangem - } - } - } +fun CardDTO.createDisclaimer(): Disclaimer { + val dataProvider = provideDisclaimerDataProvider(cardId) + return TangemDisclaimer(dataProvider) } -fun DisclaimerType.createDisclaimer(cardDTO: CardDTO): Disclaimer { - val dataProvider = provideDisclaimerDataProvider(cardDTO.cardId, this) - return when (this) { - DisclaimerType.Tangem -> TangemDisclaimer(dataProvider) - DisclaimerType.Start2Coin -> Start2CoinDisclaimer(dataProvider) - } -} - -fun CardDTO.createDisclaimer(): Disclaimer = DisclaimerType.get(this).createDisclaimer(this) - -private fun provideDisclaimerDataProvider(cardId: String, disclaimerType: DisclaimerType): DisclaimerDataProvider { +private fun provideDisclaimerDataProvider(cardId: String): DisclaimerDataProvider { val cardRepository = store.inject(DaggerGraphState::cardRepository) return object : DisclaimerDataProvider { override fun getLanguage(): String = Locale.getDefault().language override fun getCardId(): String = cardId override suspend fun accept() { - when (disclaimerType) { - DisclaimerType.Tangem -> cardRepository.acceptTangemTOS() - DisclaimerType.Start2Coin -> cardRepository.acceptStart2CoinTOS(cardId) - } + cardRepository.acceptTangemTOS() } override suspend fun isAccepted(): Boolean { - return when (disclaimerType) { - DisclaimerType.Tangem -> cardRepository.isTangemTOSAccepted() - DisclaimerType.Start2Coin -> cardRepository.isStart2CoinTOSAccepted(cardId) - } + return cardRepository.isTangemTOSAccepted() } } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index c517d9fdec..bb2c5fa1b0 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -95,8 +95,6 @@ object PreferencesKeys { booleanPreferencesKey(name = "isTokenSwapPromoOkxShown") } - fun getStart2CoinTOSAcceptedKey(region: String?) = booleanPreferencesKey(name = "start2Coin_tos_accepted_$region") - // region Permission fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission") diff --git a/data/card/src/main/java/com/tangem/data/card/DefaultCardRepository.kt b/data/card/src/main/java/com/tangem/data/card/DefaultCardRepository.kt index d0250fd7c3..5241c8562b 100644 --- a/data/card/src/main/java/com/tangem/data/card/DefaultCardRepository.kt +++ b/data/card/src/main/java/com/tangem/data/card/DefaultCardRepository.kt @@ -80,24 +80,10 @@ internal class DefaultCardRepository( return appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.IS_TANGEM_TOS_ACCEPTED_KEY, default = false) } - override suspend fun isStart2CoinTOSAccepted(cardId: String): Boolean { - return appPreferencesStore.getSyncOrDefault( - key = PreferencesKeys.getStart2CoinTOSAcceptedKey(region = getRegion(cardId)), - default = false, - ) - } - override suspend fun acceptTangemTOS() { return appPreferencesStore.store(key = PreferencesKeys.IS_TANGEM_TOS_ACCEPTED_KEY, true) } - override suspend fun acceptStart2CoinTOS(cardId: String) { - appPreferencesStore.store( - key = PreferencesKeys.getStart2CoinTOSAcceptedKey(region = getRegion(cardId)), - value = true, - ) - } - private suspend fun AppPreferencesStore.editUsedCards(cardId: String, update: (UsedCardInfo) -> UsedCardInfo) { editData { mutablePreferences -> val usedCards = mutablePreferences.getUsedCards() @@ -127,16 +113,5 @@ internal class DefaultCardRepository( .firstOrNull { it.cardId == cardId } } - private fun getRegion(cardId: String): String? { - if (cardId.isEmpty()) return null - - return when (cardId[1]) { - '0' -> "fr" - '1' -> "ch" - '2' -> "at" - else -> null - } - } - private fun createDefaultUsedCardInfo(cardId: String) = UsedCardInfo(cardId = cardId) } \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardRepository.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardRepository.kt index 68e476443e..5e01929fa1 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardRepository.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardRepository.kt @@ -26,10 +26,5 @@ interface CardRepository { @Throws suspend fun isTangemTOSAccepted(): Boolean - @Throws - suspend fun isStart2CoinTOSAccepted(cardId: String): Boolean - suspend fun acceptTangemTOS() - - suspend fun acceptStart2CoinTOS(cardId: String) } \ No newline at end of file From 3013aad882052541d297d85085da4b2252511d80 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Aug 2024 16:14:34 +0400 Subject: [PATCH 50/53] Updated on 2026-08-14 --- .../com/tangem/common/routing/AppRoute.kt | 9 +------ .../impl/DefaultDisclaimerComponent.kt | 10 +------- .../disclaimer/impl/model/DisclaimerModel.kt | 24 +++++++++++++++---- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 20d08dcf6a..6cd73dfcb0 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -35,14 +35,7 @@ sealed class AppRoute(val path: String) : Route { @Serializable data class Disclaimer( val isTosAccepted: Boolean, - ) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}"), RouteBundleParams { - - override fun getBundle(): Bundle = bundle(serializer()) - - companion object { - const val IS_TOS_ACCEPTED_KEY = "isTosAccepted" - } - } + ) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}") @Serializable data object OnboardingNote : AppRoute(path = "/onboarding/note") diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/component/impl/DefaultDisclaimerComponent.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/component/impl/DefaultDisclaimerComponent.kt index f281084c55..5fbd86f488 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/component/impl/DefaultDisclaimerComponent.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/component/impl/DefaultDisclaimerComponent.kt @@ -7,7 +7,6 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.navigation.finisher.AppFinisher import com.tangem.features.disclaimer.api.components.DisclaimerComponent import com.tangem.features.disclaimer.impl.model.DisclaimerModel import com.tangem.features.disclaimer.impl.ui.DisclaimerScreen @@ -18,7 +17,6 @@ import dagger.assisted.AssistedInject internal class DefaultDisclaimerComponent @AssistedInject constructor( @Assisted context: AppComponentContext, @Assisted private val params: DisclaimerComponent.Params, - private val appFinisher: AppFinisher, ) : DisclaimerComponent, AppComponentContext by context { private val model: DisclaimerModel = getOrCreateModel(params) @@ -27,13 +25,7 @@ internal class DefaultDisclaimerComponent @AssistedInject constructor( override fun Content(modifier: Modifier) { val state by model.state.collectAsStateWithLifecycle() - BackHandler { - if (params.isTosAccepted) { - state.popBack() - } else { - appFinisher.finish() - } - } + BackHandler(onBack = state.popBack) DisclaimerScreen(state = state) } diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt index f1be4240e0..e2dffb37f3 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt @@ -5,6 +5,7 @@ import com.tangem.core.decompose.di.ComponentScoped 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.navigation.finisher.AppFinisher import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.settings.NeverRequestPermissionUseCase import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase @@ -17,12 +18,14 @@ import kotlinx.coroutines.launch import javax.inject.Inject @ComponentScoped +@Suppress("LongParameterList") internal class DisclaimerModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, private val cardRepository: CardRepository, private val router: Router, - override val dispatchers: CoroutineDispatcherProvider, private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase, private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase, + private val appFinisher: AppFinisher, paramsContainer: ParamsContainer, ) : Model() { @@ -33,23 +36,34 @@ internal class DisclaimerModel @Inject constructor( onAccept = ::onAccept, url = DISCLAIMER_URL, isTosAccepted = params.isTosAccepted, - popBack = router::pop, + popBack = ::popBack, ), ) - private fun onAccept(shouldAskPushPermission: Boolean) { - modelScope.launch { + private fun onAccept(shouldAskPushPermission: Boolean) = modelScope.launch { + if (params.isTosAccepted) { + router.pop() + } else { cardRepository.acceptTangemTOS() + if (shouldAskPushPermission) { router.push(AppRoute.PushNotification) } else { neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) neverRequestPermissionUseCase(PUSH_PERMISSION) - router.push(AppRoute.Home) + router.replaceAll(AppRoute.Home) } } } + private fun popBack() { + if (params.isTosAccepted) { + router.pop() + } else { + appFinisher.finish() + } + } + private companion object { const val DISCLAIMER_URL = "https://tangem.com/tangem_tos.html" } From 25abb8e2fc0ae491edc24176dd41e5f62b5c85aa Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Aug 2024 13:43:21 +0300 Subject: [PATCH 51/53] Updated on 2026-08-14 --- .../details/ui/cardsettings/CardSettingsViewModel.kt | 2 ++ .../products/twins/redux/TwinCardsMiddleware.kt | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index e3fe0469f9..7f972b948d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -161,6 +161,8 @@ internal class CardSettingsViewModel @Inject constructor( if (scanResponse.cardTypesResolver.isTangemTwins()) { store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet(scanResponse))) + cardSettingsInteractor.clear() + store.dispatchNavigationAction { push(AppRoute.OnboardingTwins) } } else { val card = scanResponse.card diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index a53d4f51a7..19d77c509e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -65,9 +65,9 @@ private fun handle(action: Action, dispatch: DispatchFunction) { val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) fun getScanResponse(): ScanResponse { - return when (val mode = twinCardsState.mode) { + return when (twinCardsState.mode) { is CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse - is CreateTwinWalletMode.RecreateWallet -> mode.scanResponse + is CreateTwinWalletMode.RecreateWallet -> globalState.scanResponse } ?: throw NullPointerException("ScanResponse can't be NULL") } @@ -105,6 +105,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) { mainScope.launch { if (twinCardsState.currentStep is TwinCardsStep.WelcomeOnly) return@launch + if (twinCardsState.mode is CreateTwinWalletMode.RecreateWallet) { + store.dispatch(GlobalAction.SaveScanResponse(twinCardsState.mode.scanResponse)) + } + val scanResponse = getScanResponse() onboardingManager?.apply { if (!isActivationStarted(scanResponse.card.cardId)) { @@ -368,8 +372,7 @@ private fun getPopBackScreen(): KClass { val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) return if (userWalletsListManager.hasUserWallets) { - val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync } - .fold(onSuccess = { true }, onFailure = { false }) + val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync!! }.getOrElse { false } if (isLocked) { AppRoute.Welcome::class From dcc3286a085df353772998ae2f707b68c0fc403d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 7 Aug 2024 10:38:08 +0300 Subject: [PATCH 52/53] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index b5726a45bf..095b8f4ea0 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit b5726a45bf51b7763c5967afae4d3d687676240b +Subproject commit 095b8f4ea0fa02e7ccea93cf0f437346345297ef From 5e3fcd146a2ef0ab290bf0043224e5e34835bf28 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 7 Aug 2024 12:02:38 +0300 Subject: [PATCH 53/53] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 51 +++++++++--------- core/res/src/main/res/values-fr/strings.xml | 24 +++------ core/res/src/main/res/values-ja/strings.xml | 40 +++++++------- core/res/src/main/res/values-ru/strings.xml | 54 +++++++++++++------ .../src/main/res/values-uk-rUA/strings.xml | 21 ++------ core/res/src/main/res/values/strings.xml | 30 ++++++----- .../previewdata/InitialStakingStatePreview.kt | 9 +--- .../SetInitialDataStateTransformer.kt | 8 +-- .../ShowInfoBottomSheetStateTransformer.kt | 4 +- 9 files changed, 120 insertions(+), 121 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 67cef4ccd7..0123a1dfdd 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1,7 +1,7 @@ Netzwerk wählen - Benutzerdefiniertes Token hinzufügen + Benutzerdef. Token hinzuf. Token verwalten Sende nur %1$s ( %2$s ) vom %3$s -Netzwerk an diese Adresse. Die Verwendung anderer Token und Netzwerke kann zum Verlust von Geldern führen. So scannt man @@ -13,9 +13,6 @@ Das gewählte unterstützt nicht das %1$s Netzwerk Um die kryptografische Verschlüsselung der %1$s Blockchain zu aktivieren, musst Du die Wallet auf die Werkseinstellungen zurücksetzen. Bitte heb vorher dein Guthaben ab, um sicherzustellen, dass du nichts verlierst, und führe dann den Reset-Vorgang durch. Nach dem Zurücksetzen ist der Zugriff auf die aktuelle Wallet nicht mehr möglich. Tokens im %1$s -Netzwerk werden von dieser Karte aufgrund einer Firmware-Einschränkung nicht unterstützt. - Vielen Dank für dein Feedback. Wir werden so schnell wie möglich antworten - Deine Vorschläge wurden übermittelt - Bitte versuche, die Karte genau wie in der Animation gezeigt anzutippen, oder lese unsere einfache Anleitung, oder forder Support an. Wenn das Problem weiterhin besteht, wende dich bitte an den Support. Hast du Probleme beim Scannen deiner Karte? Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet Standardgebühr @@ -31,8 +28,6 @@ Dunkel Hell Systemstandard - Wenn das System ausgewählt ist, passt sich die App automatisch an die Systemeinstellungen deines Geräts an - System Thema App Einstellungen Um deine Kontostände ein- oder auszublenden, flippe einfach das Display deines Geräts nach unten, oder schalte es in den Einstellungen aus. @@ -51,7 +46,7 @@ Deaktiviere diese Option, wenn du nicht möchtest, dass diese Karte zum Zurücksetzen von Zugangscodes auf anderen Karten in dieser Wallets verwendet wird. Bitte beachte, dass du dann auch den Zugangscode auf dieser Karte nicht zurücksetzen kannst. Ermöglicht die Verwendung dieser Karte zum Zurücksetzen des Zugangscodes auf anderen Karten in dieser Brieftasche - Wiederherstellung des Zugangscodes + Zugangscodes wiederherstellen Zurücksetzen Möchtest du das wirklich tun? Zugangscode ändern @@ -89,18 +84,17 @@ Abbrechen Stakingbelohnungen beanstpruchen Schließen - Weitermachen + Weiter Kopieren Adresse kopieren Erstellen - Benutzerdefiniert + Benutzerdef. - Tag - Tage + %d tag + %d tage Entfernen Deaktiviert - Trennen Erledigt Aktivieren Aktiviert @@ -113,7 +107,7 @@ Schnell Markt Langsam - Geschwindigkeit und Gebühr + Gebühren Adressen abrufen Zum Anbieter gehen Zum Token @@ -137,7 +131,6 @@ Ablehnen Neu laden Umbenennen - Wiederholen Speichern Änderungen speichern Suchen @@ -166,7 +159,6 @@ Es ist ein Fehler aufgetreten. Bitte versuche es erneut. Nicht erreichbar staking beenden - Warnung Ja Vertragsadresse kopiert! Verfügbare Netzwerke @@ -214,7 +206,6 @@ Flipp um Guthaben auszublenden Aussteller Signiert - Wenn du den Code vergisst, verlierst du den Zugriff auf dein Geld. Eine Codewiederherstellung ist nicht möglich. Gib uns eine Rückmeldung Details Überprüfe deine Internetverbindung oder wechseln zu einem anderen Netzwerk @@ -466,7 +457,7 @@ leer %d Wörter - Um deine Wallets zu importieren, gib bitte deineSeed-Phrase in das folgende Feld ein + Um deine Wallets zu importieren, gib bitte deine Seed-Phrase in das folgende Feld ein Seed-Phrase generieren Wallet importieren Eine Seed-Phrase ist eine Reihe von Wörtern, mit denen du deine Wallet wiederherstellen kannst. Im Gegensatz zu den von der Karte generierten Schlüsseln sind Seed-Phrasen ungeschützt und können kopiert und gestohlen werden. Die Verwendung dieser Option erfolgt auf eigene Gefahr. @@ -508,7 +499,7 @@ Gruppe erstellen Nach Guthaben Token organisieren - Gruppierung aufheben + Gruppierung aufh. Wählen aus der Galerie aus Einstellungen Du hast keinen Zugriff auf deine Kamera gewährt @@ -581,7 +572,6 @@ %1$s, %2$s Adresse Ziel-Tag - Möchtest du den Sendebildschirm wirklich schließen? Adresse eingeben Die Adresse stimmt mit der Adresse Ihrer Brieftasche überein Ungültiges Tag. Es wird der Transaktion nicht hinzugefügt. @@ -657,9 +647,10 @@ Aktiv Um deine Kryptos zu unstaken, klick hier. Die Anzahl der zu stakenden Krypros muss mindesten %s betragen + nicht gestakte beanspruche + Jährliche prozentuale Rendite + Die jährliche prozentuale Rendite, die du durch die Teilnahme am Staking erzielen kannst. Effektiver Jahreszins - Jährliche prozentuale Rendite - Die jährliche prozentuale Rendite, die du durch die Teilnahme am Staking erzielen kannst. Verfügbar Durchschnittliche Belohnungsquote %s geschätzter Profit @@ -667,7 +658,6 @@ Metriken Mindestanforderungen Keine Belohnungen zu beanspruchen. - Gestaked Belohnungen beanspruchen Eine Möglichkeit, Staking-Belohnungen zu erhalten. Es kann automatisch oder manuell beansprucht werden. Belohnungszeitplan @@ -679,15 +669,27 @@ Aufwärmphase Die zugewiesene Zeit für die Aktivierung der Teilnahme am Staking. Stake %s + Migrieren Natives Staking Mit Staking kannst du %1s verdienen. Deine Staking-Belohnungen kommen alle ~%2s Tage. Verdiene Staking-Belohnungen + Die Belohnungen werden sofort nach dem unstaken gestoppt. Der unstakingprozess dauert %s. + Erneut binden + Erneut staken + Belohnungen erneut staken + Widerrufen + Neuwahl Belohnungen + Stake gesperrt Mehr staken - unstaken + gelocktes unlocken + Unstaken Prüfe, was nicht eingesetzt wurde, um dein Vermögen zu beanspruchen Staking beenden Validator/ Prüfer + Abstimmung + Abstimmung gesperrt + Zurückziehen Bewahre deine Krypto-Assets sicher auf, während die privaten Schlüssel auf deiner Karte bleiben Revolutionäre Hardware-Wallet Bis zu 3 physische Karten pro Wallet @@ -728,7 +730,6 @@ Die Geldsendung wird verfügbar, sobald die ausstehende(n) Transaktion(en) im Netzwerk %s abgeschlossen ist/sind. Der Verkauf von %s ist im Moment nicht verfügbar. Bitte prüfe später ob es Updates gibt. Staking %s ist aktuell nicht verfügbar. Prüfe bitte ob es ein neues Update gibt. - Adresse wählen Generiere XPUB Ausblenden Du bist dabei, dieses Token vom Hauptbildschirm auszublenden. Du kannst es jederzeit über die Seite „Token verwalten“ wieder hinzufügen. @@ -821,7 +822,7 @@ Aktivierungsfehler Laut den Entwicklern des BNB-Netzes wird die Unterstützung für den BEP-2-Standard im Juni 2024 enden. Um den Verlust von Vermögenswerten mit diesem Standard zu vermeiden, konvertiere bitte in den BEP-20 Standard. Nutze gerne unseren Swap-Service, um sie auf das BNB Smart Chain Netzwerk zu übertragen. BNB Beacon Chain wird abgeschaltet - Könnte besser sein + verbesserungswürdig Gefällt mir OK, habe ich verstanden! Echt toll! diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 30baffe42f..071924cd18 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -13,9 +13,6 @@ Le sélectionné ne prend pas en charge le réseau %1$s Pour activer le cryptage de la blockchain %1$s, vous devrez réinitialiser le portefeuille aux paramètres d\'usine. Veuillez retirer vos fonds avant de le faire pour vous assurer de ne pas les perdre, puis terminez le processus de réinitialisation. L\'accès au portefeuille actuel ne sera pas possible après la réinitialisation. Les jetons du réseau %1$s ne sont pas pris en charge par cette carte en raison d\'une limitation du micrologiciel. - Merci pour votre retour. Nous vous répondrons dès que possible - Vos suggestions ont été envoyées - Veuillez essayer d\'appuyer sur la carte exactement comme indiqué dans l\'animation ou lire notre guide simple, ou demander de l\'aide. Si le problème persiste, veuillez demander de l\'aide. Avez-vous des difficultés à scanner votre carte ? Cette carte n\'est pas conçue pour fonctionner avec Tangem Frais par défaut @@ -31,8 +28,6 @@ Sombre Clair Par défaut du système - Si le système est sélectionné, l\'application s\'ajustera automatiquement en fonction des paramètres système de votre appareil - Système Thème Paramètres de l\'application Pour masquer ou afficher vos soldes, il suffit de retourner l\'écran de votre appareil vers le bas ou de le désactiver dans les paramètres @@ -98,7 +93,6 @@ Supprimer Désactivé - Se déconnecter Exécuté Activer Activé @@ -134,7 +128,6 @@ Rejeter Recharger Renommer - Réessayer Enregistrer Sauvegarder les modifications Rechercher @@ -162,7 +155,6 @@ Je comprends Il y avait une erreur. Veuillez réessayer. Inaccessible - Alerte Oui Adresse du contrat copiée ! Réseaux disponibles @@ -210,7 +202,6 @@ Retourner pour masquer les soldes Emetteur Signé - Si vous oubliez le code, vous perdrez l\'accès à vos fonds. La récupération du code n\'est pas possible. Envoyer un commentaire Référénces Vérifiez votre connexion Internet ou passez à un réseau différent @@ -385,7 +376,7 @@ Voulez-vous quitter le processus d\'activation ? Initialiser Un autre portefeuille a déjà été créé sur la carte que vous essayez d\'ajouter. Si vous avez des fonds dans ce portefeuille, veuillez les retirer, puis réinitialiser cette carte et l\'ajouter comme sauvegarde. - Création d\'une sauvegarde + Sauvegarde en cours En savoir plus sur les seed phrases Empty @@ -398,7 +389,7 @@ Pour importer votre portefeuille, entrez votre seed phrase dans le champ ci-dessous Générer une seed phrase - Importer un portefeuille + Importez Une seed phrase est une série de mots qui vous permet de récupérer votre portefeuille. Contrairement aux clés générées par la carte, les seed phrases ne sont pas protégées et peuvent être copiées et volées. Utilisez cette option à vos propres risques. Utiliser une seed phrase Seed phrase invalide. Veuillez vérifier l\'ordre des mots. @@ -509,7 +500,6 @@ %1$s, %2$s Adresse Destination Tag - Êtes-vous sûr de vouloir fermer l\'écran d\'envoi ? Entrez l\'adresse L\'adresse est la même que celle de votre portefeuille Tag invalide. Il ne sera pas ajouté à la transaction. @@ -574,6 +564,7 @@ ≈ %1$s (incl. les commissions : %2$s) Sera envoyé %s La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps + %1$s est un actif du réseau Tron. Pour calculer les frais et effectuer une transaction, déposez du Tron (TRX) sur votre compte. Adresse incorrecte %1$s (%2$s) Transaction envoyée @@ -583,9 +574,8 @@ Nom Actif Afin d\'unstaker vos actifs, cliquez ici. + Le pourcentage de rendement annuel que vous pouvez gagner en participant au staking. APR - APY - Le pourcentage de rendement annuel que vous pouvez gagner en participant au staking. Disponible Taux de récompense moyen %s profit estimatif @@ -593,7 +583,6 @@ Métriques Minimum requis Aucune récompense à réclamer - En jeu Réclamation de récompense Un moyen de recevoir des récompenses de staking. Il peut être réclamé automatiquement ou manuellement. Calendrier de récompenses @@ -649,7 +638,6 @@ L\'envoi de fonds sera disponible une fois la ou les transactions en attente dans le réseau %s terminées. La vente de %s n\'est pas disponible pour le moment. Veuillez consulter nos mises à jour. Le staking %s n’est pas disponible pour le moment. Veuillez consulter nos mises à jour. - Choisissez une adresse Générer XPUB Masquer Vous êtes sur le point de masquer ce jeton de l\'écran principal. Vous pouvez le rajouter à tout moment via la page de gestion des jetons. @@ -657,7 +645,7 @@ Masquer le jeton Le Staking vous permet d\'en gagner %1$s et d\'obtenir des récompenses tous les %2$s jours Gagnez jusqu\'à %s récompense de mise par an - %1$s jeton dans %%image%% le réseau %2$s + %1$s jeton dans %%image%% %2$s le réseau Jeton dans le %%image%% %1$s réseau Le jeton %1$s (%2$s) est la principale devise du réseau %3$s et ne peut pas être masqué tant que vous avez d\'autres jetons de ce réseau dans la liste Impossible de masquer %s @@ -741,7 +729,7 @@ Erreur d\'activation Selon les développeurs du réseau BNB, le support de la norme BEP-2\nprendra fin en juin 2024. Pour éviter de perdre des actifs avec cette norme, veuillez les convertir à la norme BEP-20. Utilisez notre service de d\'échange pour les transférer sur le réseau BNB Smart Chain. BNB Beacon Chain va s\'arrêter de fonctionner - Pourrait être mieux + Pas terrible J\'aime Ok, je l\'ai! Vraiment cool ! diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index c0913ede01..d9c7168c7a 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -13,9 +13,6 @@ 選択したものは%1$sネットワークをサポートしていません。 %1$sブロックチェーンの暗号化を有効にするには、ウォレットを工場出荷時の設定にリセットする必要があります。リセットする前に出金して、資金が失われないようにしてから、リセット処理を完了してください。リセット後は、現在のウォレットにアクセスできなくなります。 %1$s ネットワークのトークンは、ファームウェアの制限により、このカードではサポートされていません。 - ご意見ありがとうございます。できるだけ早くご返信いたします。 - あなたの提案が送信されました - アニメーションに表示されているとおりにカードをタップするか、簡単なガイドをお読みください。それでも問題が解決しない場合は、サポートをご依頼ください。 カードのスキャンに問題がありますか? このカードはこのアプリでは使用できません。 デフォルト手数料 @@ -31,8 +28,6 @@ ダーク ライト システムのデフォルト - システムを選択した場合、アプリはデバイスのシステム設定に基づいて自動調整されます。 - システム テーマ アプリ設定 残高を表示または非表示にするには、デバイスの画面を下向きにするか、設定でオフにしてください。 @@ -94,11 +89,10 @@ 作成 設定 - + %d 日 削除 無効 - 切断 完了 有効にする 有効 @@ -135,7 +129,6 @@ 拒否 リロード 名前を変更 - リトライ 保存 変更内容を保存 検索 @@ -151,7 +144,7 @@ ステーキング ステーキング 始める - 提出する + 送信 成功 サポート スワップ @@ -164,7 +157,6 @@ エラーが発生しました。もう一度お試しください。 アクセスできません ステーキング解除 - 警告 はい コントラクトアドレスをコピーしました! 利用可能なネットワーク @@ -212,7 +204,6 @@ フリップして残高を非表示にする 発行者 署名済み - コードを忘れた場合、資金にアクセスできなくなります。コードの回復は不可能です。 フィードバックを送信 詳細 インターネット接続を確認するか、別のネットワークに切り替えてください。 @@ -347,7 +338,7 @@ 利用可能なネットワーク 私のポートフォリオ マーケット - 選択したネットワークのアドレスを生成するには、Tangemカードをタップする必要があります + 選択したネットワークのアドレスを生成するには、Tangemカードをスキャンする必要があります。 データを読み込めません… クイックアクション 結果 @@ -458,7 +449,7 @@ ウォレットをインポートするには、下のフィールドにシードフレーズを入力してください。 シードフレーズを生成する - ウォレットをインポートする + ウォレットをインポート シードフレーズは、ウォレットを復元できる一連の単語です。カードによって生成される秘密鍵とは異なり、シードフレーズは保護されていないため、コピーされて盗まれる可能性があります。このオプションは自己責任で使用してください。 シードフレーズを使用する 無効なシードフレーズです。語順を確認してください。 @@ -569,7 +560,6 @@ %1$s 、 %2$s アドレス 宛先タグ - 送金画面を閉じてもよろしいですか? アドレスを入力 アドレスはウォレットアドレスと同じです 無効なタグです。取引には追加されません。 @@ -645,17 +635,18 @@ アクティブ 資産のステーキングを解除するには、ここをクリックしてください。 ステーキング金額は %s 以上である必要があります + ステーキング解除分を請求する + APY + ステーキングに参加することで得られる年間収益率。 APR - APY - ステーキングに参加することで得られる年間収益率。 利用可能 平均報酬率 + ステーキングとは? %s 推定利益 市場評価 指標 最低要件 請求できる報酬はありません - ステーキング中 請求中の報酬 ステーキング報酬を受け取る方法。自動または手動で請求できます。 報酬スケジュール @@ -666,14 +657,28 @@ ステーキングから資金の引き出しを要求した後、トークンが利用可能になるまでの待機期間。 ウォームアップ期間 ステーキングへの参加を有効にするために割り当てられた時間。 + %sをステーキングする + 移行 ネイティブステーキング ステーキングにより%1sを獲得できます。ステーキング報酬は ~ %2s日ごとに届きます。 ステーキング報酬を獲得 + ステーキング解除後、報酬の獲得はすぐに停止します。ステーキング解除プロセスには%sかかります。 + 再結束 + 再度ステーキングする + 報酬をステーキングする + 取り消す + 再投票 報酬 + ステーキングはロックされています もっとステーキングする + ステーキング解除はロックされています スタックされていない 資産を請求するために、unstakedを確認してください + ステーキング解除 バリデーター + 投票する + 投票はロックされています + 引き出す カード内に秘密鍵を保管しながら暗号資産を安全に保管します 革新的なハードウェアウォレット 1つのウォレットに最大3枚のカード @@ -714,7 +719,6 @@ ネットワーク%s内の保留中の取引が完了すると、送金が可能になります。 現在、 %sの売却はご利用いただけません。アップデート情報をご確認ください。 %s のステーキングは現在ご利用いただけません。最新情報をご確認ください。 - アドレスを選択 XPUBを生成する 非表示 このトークンをメイン画面から非表示にします。トークンの管理ページからいつでも再度追加できます。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 741704103a..f9f2f33750 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -13,9 +13,6 @@ Выбранный кошелёк не поддерживает сеть %1$s Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. - Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки. - Ваши предложения отправлены - Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, либо прочтите руководство по сканированию. Если проблема осталась, то запросите поддержку. У вас возникли трудности со сканированием карты? Эта карта не предназначена для работы с этим приложением Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. @@ -30,8 +27,6 @@ Тёмная Светлая Как в системе - При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства - Системная Тема Настройки приложения Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" @@ -86,6 +81,7 @@ Перейти на %1$s Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. Отмена + Вывести награду Закрыть Продолжить Копировать @@ -100,7 +96,6 @@ Удалить Отключено - Отключить Готово Включить Включено @@ -137,7 +132,6 @@ Отклонить Перезагрузить Переименовать - Повторить Сохранить Сохранить изменения Искать @@ -150,6 +144,7 @@ Поделиться Подписать Подписать и отправить + Застейкать Стейкинг Начать Отправить @@ -164,7 +159,7 @@ Я понял Произошла ошибка. Пожалуйста, попробуйте снова. Недоступно - Предупреждение + Завершить стейкинг Да Адрес контракта скопирован! Доступные сети @@ -212,7 +207,6 @@ Скрывать балансы жестом переворота Эмитент Подписано - Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно. Отправить отзыв Подробности Проверьте подключение с интернетом или переключитесь на другую сеть @@ -289,6 +283,7 @@ Укажите лимит доступа к выбранному токену Количество %s Функция подтверждения необходима для предоставления другому адресу разрешения на использование определенного количества ваших токенов.По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту StakeKit разрешение использовать ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете осуществить стейкинг токена. + Чтобы продолжить, вам необходимо разрешить смарт контракту StakeKit использовать ваш %s Чтобы продолжить, вам нужно разрешить смарт-контракту %1s использовать ваш %2s Дать разрешение Безлимитно @@ -514,7 +509,6 @@ %1$s, %2$s Адрес Код назначения - Вы действительно хотите закрыть экран отправки транзакции? Введите адрес Адрес совпадает с адресом кошелька Недопустимый Tag. Он не будет добавлен в транзакцию. @@ -586,20 +580,49 @@ Забыть кошелек Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова. Имя + Активно + Для завершения стейкинга нажмите сюда Сумма для стейкинга должна быть не менее %s - APY - Годовой процентный доход, который вы можете получить от участия в стейкинге. + Забрать средства + APY + Годовой процентный доход, который вы можете получить от участия в стейкинге. + APR Доступно - %s + Средння ставка вознаграждения + %s оценка доходности + Позиция в рынке + Метрики + Минимальное количество + Нет вознаграждений к получению Способ возраграждения - Способ получения вознаграждений за стейкинг. Он может быть автоматическим или ручным. + Способ получения вознаграждений за стейкинг. Он может быть автоматическим, при котором вознаграждение само зачисляется вам на адрес или в ручную, когда вознаграждение нужно вывести, создав транзакцию на её получение. Период возрагражения Это период, определяющий, когда участники стейкинга получат свои вознаграждения. + Вознаграждение для получения: %s Стейкинг %s Период вывода Период, который необходимо подождать после запроса на вывод средств из стейкинга, прежде чем токены станут доступны. Период прогрева - Время, необходимое для начала процесса стейкинга. + Время, необходимое для начала процесса стейкинга и активации процесса начисления наград + Застейкать %s + Переместить + Нативный стейкинг + Стейкинг дает возможность вам получать %1s. Награда будет зачисляться каждый %2s + Получите награду за стейкинг + Награда перестанет начисляться сразу после завершения стейкинга. Процесс завершения длится %s. + Повторный стейкинг + Застейкать вознаграждения + Отозвать + Переголосовать + Вознаграждения + Застейкать еще + Разблокировать + Выведено из стейкинга + Проверьте процесс завершения стейкинга, чтобы вывести свои средства. + Завершение стейкинга + Валидатор + Проголосовать + Вывод Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. Революционный аппаратный кошелек До трех карт с одним кошельком @@ -639,7 +662,6 @@ Отправка средств станет доступной после завершения транзакции(-ий) в сети %s В данный момент продажа %s недоступна. Следите за нашими обновлениями. В данный момент стейкинг монеты %s недоступен. Следите за нашими обновлениями. - Выберите адрес Сгенерировать XPUB Скрыть Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. 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 1c150d0e88..14a8373849 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -13,9 +13,6 @@ Обраний гаманець не підтримує мережу %1$s Щоб активувати криптографічне шифрування блокчейну %1$s, вам потрібно скинути налаштування гаманця до заводських. Зніміть свої кошти перед цим, щоб переконатися, що ви їх не втратите, а потім завершіть процес скидання. Вхід до поточного гаманця буде неможливий після скидання. Токени в мережі %1$s не підтримуються цією карткою через обмеження прошивки. - Дякуємо за ваш відгук. Ми відповімо якнайшвидше. - Ваші пропозиції надіслано - Будь ласка, спробуйте прикласти картку точно так, як показано на анімації, або прочитайте наш простий посібник. Якщо проблема залишилася, зверніться до служби підтримки. У вас виникли труднощі зі скануванням картки? Ця картка не призначена для роботи з цим додатком Комісія за замовчуванням @@ -31,8 +28,6 @@ Темна Світла Системна - При виборі \"Системна\" застосунок буде використовувати тему відповідно до налаштувань вашого пристрою - Системна Тема Налаштування застосунку Щоб приховати або показати свій баланс, просто переверніть екран пристрою вниз або вимкніть його в налаштуваннях @@ -104,7 +99,6 @@ Видалити Вимкнуто - Від\'єднати Готово Увімкнути Увімкнено @@ -141,7 +135,6 @@ Відхилити Перезавантажити Перейменувати - Повторити Зберегти Зберегти зміни Шукати @@ -170,7 +163,6 @@ Виникла помилка. Будь ласка, спробуйте ще раз. Недоступно Скасувати стейкінг - Увага Так Адреса контракту скопійована! Доступні мережі @@ -218,7 +210,6 @@ Приховувати баланси жестом перевороту Емітент Підписано - Якщо ви забудете код, ви втратите доступ до своїх коштів. Відновлення коду неможливе. Надіслати відгук Деталі Перевірте підключення до інтернету або змініть мережу @@ -356,7 +347,7 @@ Доступні мережі Моє портфоліо Маркет - Щоб згенерувати адреси для обраних мереж, потрібно прикласти картку Tangem + Щоб згенерувати адреси для обраних мереж, потрібно відсканувати свою картку Tangem. Не вдалося завантажити дані... Швидкі дії Результат @@ -476,7 +467,7 @@ Щоб імпортувати гаманець, введіть seed-фразу в поле нижче Згенерувати seed-фразу - Імпортувати гаманець + Імпорт гаманця Seed-фраза — це набір слів, який дозволяє відновити ваш гаманець. На відміну від ключів, що генеруються карткою, seed-фраза не захищена і може бути скопійована та викрадена. Використовуйте цю опцію на свій власний ризик. Використовувати seed-фразу Невірна seed-фраза. Будь ласка, перевірте порядок слів. @@ -593,7 +584,6 @@ %1$s, %2$s Адреса Тег призначення - Ви дійсно хочете закрити екран надсилання транзакції? Введіть адресу Адреса збігається з адресою гаманця Недопустимий Tag. Він не буде доданий у транзакцію. @@ -669,9 +659,8 @@ Активний Щоб вивести активи зі стейкінгу, натисніть тут. Сума для стейкінгу має бути не менше %s + Річний відсоток, який ви можете отримати, беручи участь у стейкінгу. APR - APY - Річний відсоток, який ви можете отримати, беручи участь у стейкінгу. Доступно Середня ставка винагороди ~ прибуток за %s @@ -679,7 +668,6 @@ Метрики Мінімальні вимоги Немає винагород, щоб отримати - В стейкінгу Отримати винагороду Спосіб отримання винагороди за стейкінг. Його можна отримати автоматично або вручну. Розклад винагород @@ -739,7 +727,6 @@ Надсилання коштів стане доступним після завершення транзакції(-ій) в мережі %s Наразі продаж %s недоступний. Слідкуйте за нашими оновленнями. Стейкінг %s зараз недоступний. Будь ласка, слідкуйте за нашими оновленнями. - Оберіть адресу Згенерувати XPUB Приховати Ви збираєтеся приховати цей токен з головного екрану. Ви можете додати його назад будь-коли на сторінці керування токенами. @@ -832,7 +819,7 @@ Помилка активації За рішенням розробників мережі BNB стандарт BEP-2 перестане підтримуватись у червні 2024 року. Щоб не втратити свої активи, їх необхідно конвертувати у стандарт BEP-20. Використовуйте функцію обміну, щоб перевести їх у мережу BNB Smart Chain. Відключення мережі BNB Beacon Chain - Могло б бути краще + Можна краще Вподобати Зрозуміло! Дуже круто! diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 46299409f1..b277ef22a8 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -13,9 +13,6 @@ The selected does not support the %1$s network To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. Tokens in %1$s network are not supported by this card due to firmware limitation. - Thank you for your feedback. We will respond as soon as possible - Your suggestions were sent - Please try to tap the card exactly as shown in the animation or read our simple guide, or request support. If the problem persists, please request support. Are you having difficulty scanning your card? This card is not designed to work with this app Default Fee @@ -31,8 +28,6 @@ Dark Light System default - If system is selected, the app will auto-adjust based on your device\'s system settings - System Theme App settings To hide or show your balances, simply flip your device screen down, or switch it off in Settings @@ -100,7 +95,6 @@ Delete Disabled - Disconnect Done Enable Enabled @@ -137,7 +131,6 @@ Reject Reload Rename - Retry Save Save changes Search @@ -166,7 +159,6 @@ There was an error. Please try again. Unreachable Unstake - Warning Yes Contract address copied! Available networks @@ -214,7 +206,6 @@ Flip-to-Hide Balances Issuer Signed - If you forget the code you will lose access to your funds. Code recovery is not possible. Send feedback Details Check your internet connection or switch to a different network @@ -577,7 +568,6 @@ %1$s, %2$s Address Destination Tag - Are you sure you want to close the send screen? Enter address Address is the same as wallet address Invalid Tag. It won\'t be added to the transaction. @@ -653,17 +643,18 @@ Active To unstake your assets, click here. The amount to stake must be at least %s + Claim unstaked + Annual percentage rate + The annual percentage return you can earn from participating in staking. APR - APY - The annual percentage return you can earn from participating in staking. Available Average Reward Rate + What is Staking? %s est. profit Market rating Metrics Minimum Requirement No rewards to claim - On stake Reward claiming A way to receive staking rewards. It can be claimed automatically or manually. Reward schedule @@ -675,15 +666,27 @@ Warmup period The allocated time for activating participation in staking. Stake %s + Migrate Native staking Staking allow you to earn %1s. Your staking rewards arrive every ~%2s days. Earn staking rewards + Rewards stop accruing immediately after you unstake. The unstaking process takes %s. + Rebond + Restake + Restake rewards + Revoke + Revote Rewards + Stake locked Stake more + Unlock locked Unstaked Check unstaked to claim your assets Unstaking Validator + Vote + Vote locked + Withdraw Store your crypto assets secure while keeping private keys contained in your card Revolutionary Hardware Wallet Up to 3 physical cards to one wallet @@ -724,7 +727,6 @@ Sending funds will be available once the pending transaction(s) in network %s is complete Selling %s is not available at the moment. Please check our updates. Staking %s is not available at the moment. Please check our updates. - Choose address Generate XPUB Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt index 2255ba2015..cca20b1416 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt @@ -19,15 +19,10 @@ internal object InitialStakingStatePreview { endText = TextReference.Str("15 SOL"), ), RoundedListWithDividersItemData( - id = R.string.staking_details_apy, - startText = TextReference.Res(R.string.staking_details_apy), + id = R.string.staking_details_annual_percentage_rate, + startText = TextReference.Res(R.string.staking_details_annual_percentage_rate), endText = TextReference.Str("2.54-5.12%"), ), - RoundedListWithDividersItemData( - id = R.string.staking_details_on_stake, - startText = TextReference.Res(R.string.staking_details_on_stake), - endText = TextReference.Str("0 SOL"), - ), RoundedListWithDividersItemData( id = R.string.staking_details_unbonding_period, startText = TextReference.Res(R.string.staking_details_unbonding_period), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index 7e45dc8557..5dba67759a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -100,14 +100,14 @@ internal class SetInitialDataStateTransformer( ), ), RoundedListWithDividersItemData( - id = R.string.staking_details_apy, - startText = TextReference.Res(R.string.staking_details_apy), + id = R.string.staking_details_annual_percentage_rate, + startText = TextReference.Res(R.string.staking_details_annual_percentage_rate), endText = getAprRange(), iconClick = { clickIntents.onInfoClick(InfoType.APY) }, ), RoundedListWithDividersItemData( - id = R.string.staking_details_on_stake, - startText = TextReference.Res(R.string.staking_details_on_stake), + id = 0, // todo remove in merge + startText = TextReference.Res(0), // todo remove in merge endText = TextReference.Str( value = BigDecimalFormatter.formatCryptoAmount( cryptoAmount = (yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance().orZero(), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ShowInfoBottomSheetStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ShowInfoBottomSheetStateTransformer.kt index ff1701bc9b..05db6fb7ba 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ShowInfoBottomSheetStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ShowInfoBottomSheetStateTransformer.kt @@ -19,8 +19,8 @@ internal class ShowInfoBottomSheetStateTransformer( isShow = true, content = when (infoType) { InfoType.APY -> StakingInfoBottomSheetConfig( - title = resourceReference(R.string.staking_details_apy), - text = resourceReference(R.string.staking_details_apy_info), + title = resourceReference(R.string.staking_details_annual_percentage_rate), + text = resourceReference(R.string.staking_details_annual_percentage_rate_info), ) InfoType.UNBOUNDING_PERIOD -> StakingInfoBottomSheetConfig( title = resourceReference(R.string.staking_details_unbonding_period),