From 799a38655e8df72e482ae08faae5ab373fd504a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 10 Apr 2026 11:10:57 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../src/main/res/values-pt-rBR/strings.xml | 23 ++ core/res/src/main/res/values/strings.xml | 19 + .../DefaultConsolidationRepository.kt | 22 +- .../model/DynamicAddressesDelegate.kt | 300 +++++++++++++-- .../tokendetails/model/TokenDetailsModel.kt | 24 +- .../DynamicAddressesBottomSheet.kt | 18 +- .../DynamicAddressesBottomSheetConfig.kt | 28 +- .../DynamicAddressesBottomSheetContent.kt | 364 +++++++++++++++++- gradle/tangem_dependencies.toml | 2 +- 9 files changed, 733 insertions(+), 67 deletions(-) diff --git a/core/res/src/main/res/values-pt-rBR/strings.xml b/core/res/src/main/res/values-pt-rBR/strings.xml index e931a23bdc..e011607a12 100644 --- a/core/res/src/main/res/values-pt-rBR/strings.xml +++ b/core/res/src/main/res/values-pt-rBR/strings.xml @@ -80,6 +80,10 @@ Adicionar tokens Escolha o token que deseja receber. Escolha o token que deseja trocar. + Adicione ao seu portfólio + Adicionar tokens + Classificação e agrupamento + Organizar tokens Escolha a rede Adicionar token personalizado Gerenciar tokens @@ -584,6 +588,8 @@ Escolha qual token usar para pagar a taxa de rede. %s Escolha o token Mercado e Notícias + Mostrar tudo + Mostrar menos Em alta agora As informações a seguir são opcionais. Você pode apagá-las se não quiser compartilhá-las. Diga-nos quais funções estão faltando e tentaremos ajudá-lo. @@ -719,6 +725,7 @@ Limite de mana A rede Koinos exige Mana para o pagamento das taxas de rede. Você tem %1$s/%2$s Mana Nível de mana + Adicionar e gerenciar Para começar a rastrear seus criptoativos e transações, adicione tokens. Gerenciar tokens Leia o código QR para enviar fundos ou conectar-se a um aplicativo @@ -1534,6 +1541,7 @@ Negociação em grande escala Teremos todo o prazer em receber seu feedback. O Tangem Pay agora está em versão beta. + Não foi possível renomear o cartão. Cartão bloqueado Pagamento com cartão Depósito @@ -1567,7 +1575,9 @@ Seu cartão foi desbloqueado. Retirada Não é possível usar em dispositivos com root. + Saldo disponível Ocultar KYC da tela principal + Cartão Tangem Pay 1 Adicionar fundos Opções de recarga Adicionar ao Google Wallet @@ -1600,10 +1610,13 @@ Compartilhe seu endereço ou mostre o código QR. Problemas técnicos detectados. Tente novamente mais tarde ou entre em contato com o suporte. Receber indisponível agora + Somente letras e números são permitidos. + Caracteres inválidos Revelar Mostrar detalhes Troque qualquer ativo da sua carteira por um cartão. Detalhes do cartão + Por favor, tente novamente mais tarde. Descongelar cartão Volte ao aplicativo se você se esquecer. Seu código PIN @@ -1611,12 +1624,16 @@ Retirada indisponível agora Você não pode iniciar uma troca ou um novo saque até que o atual seja concluído. Retirada em andamento + Configurações do cartão Alterar código PIN Volte ao aplicativo se você se esquecer. + Cartão digital Entendo que perderei completamente o acesso ao meu cartão Tangem Pay e a todos os fundos nele contidos, sem possibilidade de recuperação. Falha na emissão do cartão Ocorreu um erro técnico. Tente novamente clicando no botão abaixo. Ocorreu um erro técnico. Por favor, entre em contato com o suporte. + O recurso estará disponível em breve. + Você poderá emitir cartões adicionais para sua conta de pagamento. Obtenha seu cartão virtual Visa Tangem grátis. Obtenha o Tangem Pay Acesse o Suporte @@ -1648,6 +1665,7 @@ Uma conta de pagamento separada será criada sem divulgar seus endereços e bens. Privacidade incomparável Obtenha seu cartão Tangem Pay gratuito em minutos. + Suporte de pagamento Conta de pagamento A conta de pagamento não está sincronizada. PIN inválido: evite sequências ou repetições. @@ -1660,6 +1678,7 @@ Use USDC para pagamentos do dia a dia. O serviço Tangem Pay está temporariamente inacessível. Tangem Pay + USDC na rede Polygon Clique no botão abaixo para restaurar o acesso. O saldo do seu Polygon em USDC na blockchain é diferente do saldo do seu cartão e é atualizado em até 2 dias úteis após uma compra. Os fundos de compras reembolsadas não serão devolvidos ao seu saldo na blockchain nem estarão disponíveis para saque, mas permanecerão no saldo do seu cartão para compras futuras. Observe @@ -1690,6 +1709,10 @@ Serviço de staking %1$s token em %%imagem%% %2$s rede Token em %%imagem%% %1$s rede + %s rede + %1$s em %2$s rede + %1$s em %%imagem%% %2$s + %1$s em %2$s %%imagem%% O %1$s (%2$sO token ) é a principal moeda da plataforma. %3$s rede e não pode ser ocultada enquanto você tiver outros tokens dessa rede na lista Não foi possível ocultar %s N/A diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index dd706dc203..1ec9514666 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -80,6 +80,10 @@ Add tokens Choose the token you want to receive Choose the token you want to swap + Add to your portfolio + Add tokens + Sorting and grouping + Organize tokens Choose network Add custom token Manage tokens @@ -477,6 +481,11 @@ Send funds using only Dynamic addresses Dynamic Addresses is enabled. Custom \"change\" and \"index\" are unavailable. + You are turning off Dynamic Addresses. Funds will now be received on your fixed address. + A network fee applies to consolidate funds into the fixed address. + Disable Dynamic Addresses + Disable dynamic addresses + Dynamic addresses disabled Dynamic addresses enabled Use a new address for each transaction to reduce traceability and improve on-chain privacy. Enhanced Privacy @@ -584,6 +593,8 @@ Choose which token to use to pay\nthe network fee. %s Choose token Market & News + Show All + Show less Tangem AI Trending Now The following information is optional. You can erase it if you don\'t want to share it. @@ -720,6 +731,7 @@ Mana limit The Koinos network requires Mana for network fees. You have %1$s/%2$s Mana Mana level + Add & Manage To begin tracking your crypto assets and transactions, add tokens Manage tokens Scan QR code to send funds or connect to an app @@ -1569,7 +1581,9 @@ Your card is unfrozen. Withdrawal Unable to use on rooted device + Available balance Hide KYC from main screen + Tangem Pay Card 1 Add funds Top-up options Add to Google Wallet @@ -1619,10 +1633,13 @@ Card settings Change PIN-code Come back to the app if you forget it. + Digital card I understand that I will completely lose access to my Tangem Pay Card and all funds on it without the possibility of recovery Failed to issue card A technical error has occurred, please try again by clicking the button below. A technical error has occurred, please contact support. + Feature will be availible soon + You will be available to issue additional cards for your payment account Get your free Tangem Visa virtual card Get Tangem Pay Go to Support @@ -1654,6 +1671,7 @@ A separate payment account will be created without disclosing your addresses and assets Unrivaled privacy Get your free Tangem Pay Card in minutes + Pay Support Payment account Payment account is not synced Invalid PIN: avoid sequences or repeats @@ -1666,6 +1684,7 @@ Use USDC for everyday payments Tangem Pay is temporarily unreachable Tangem Pay + USDC on Polygon network Click the button below to restore access Your on-chain USDC Polygon balance differs from your card balance and updates within 2 business days after a purchase. Funds from refunded purchases won’t be returned your on-chain balance or be available for withdrawal, but will stay on your card balance for purchases. Please note diff --git a/data/dynamic-addresses/src/main/java/com/tangem/data/dynamicaddresses/DefaultConsolidationRepository.kt b/data/dynamic-addresses/src/main/java/com/tangem/data/dynamicaddresses/DefaultConsolidationRepository.kt index 0860a7b9ce..c1b88d69d2 100644 --- a/data/dynamic-addresses/src/main/java/com/tangem/data/dynamicaddresses/DefaultConsolidationRepository.kt +++ b/data/dynamic-addresses/src/main/java/com/tangem/data/dynamicaddresses/DefaultConsolidationRepository.kt @@ -1,10 +1,12 @@ package com.tangem.data.dynamicaddresses import arrow.core.Either +import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.DynamicAddressesManager import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.transaction.Fee +import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.domain.dynamicaddresses.repository.ConsolidationRepository import com.tangem.domain.models.network.Network @@ -33,13 +35,25 @@ internal class DefaultConsolidationRepository( when (val result = dynamicAddressesManager.createConsolidationTransaction(fee)) { is Result.Success -> result.data - is Result.Failure -> error("Failed to create consolidation tx: ${result.error}") + is Result.Failure -> throw result.error } } } - @Suppress("UnusedParameter") - private fun getNormalFee(walletManager: WalletManager): Fee { - TODO("Fee calculation for consolidation from multiple addresses is not yet implemented") + private suspend fun getNormalFee(walletManager: WalletManager): Fee { + val coinAmount = walletManager.wallet.amounts[AmountType.Coin] + ?: error("Coin amount not found") + val feeResult = walletManager.getFee( + amount = coinAmount, + destination = walletManager.wallet.address, + ) + + return when (feeResult) { + is Result.Success -> when (val txFee = feeResult.data) { + is TransactionFee.Single -> txFee.normal + is TransactionFee.Choosable -> txFee.normal + } + is Result.Failure -> throw feeResult.error + } } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/DynamicAddressesDelegate.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/DynamicAddressesDelegate.kt index e9fe02cce1..a6a548aa4e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/DynamicAddressesDelegate.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/DynamicAddressesDelegate.kt @@ -1,42 +1,65 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.model import com.tangem.common.core.TangemSdkError +import com.tangem.core.decompose.di.GlobalUiMessageSender import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.res.R import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.common.ui.amountScreen.utils.getFiatString +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.dynamicaddresses.CreateConsolidationTransactionUseCase +import com.tangem.domain.dynamicaddresses.DisableDynamicAddressesUseCase import com.tangem.domain.dynamicaddresses.EnableDynamicAddressesError import com.tangem.domain.dynamicaddresses.EnableDynamicAddressesUseCase import com.tangem.domain.dynamicaddresses.IsXpubDerivedUseCase +import com.tangem.domain.dynamicaddresses.model.DynamicAddressesStatus import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository +import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.Network -import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.utils.Provider +import com.tangem.domain.transaction.error.SendTransactionError +import com.tangem.domain.transaction.usecase.GetFeeUseCase +import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.wallets.usecase.GetExtendedPublicKeyForCurrencyUseCase import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.dynamicaddresses.DynamicAddressesBottomSheetConfig import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runSuspendCatching import com.tangem.utils.logging.TangemLogger +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @Suppress("LongParameterList") -internal class DynamicAddressesDelegate( +internal class DynamicAddressesDelegate @AssistedInject constructor( private val enableDynamicAddressesUseCase: EnableDynamicAddressesUseCase, + private val disableDynamicAddressesUseCase: DisableDynamicAddressesUseCase, + private val createConsolidationTransactionUseCase: CreateConsolidationTransactionUseCase, + private val getFeeUseCase: GetFeeUseCase, + private val sendTransactionUseCase: SendTransactionUseCase, private val isXpubDerivedUseCase: IsXpubDerivedUseCase, private val dynamicAddressesRepository: DynamicAddressesRepository, private val getExtendedPublicKeyUseCase: GetExtendedPublicKeyForCurrencyUseCase, - private val uiMessageSender: UiMessageSender, - private val userWalletId: UserWalletId, - private val network: Network, - private val coroutineScope: CoroutineScope, + @GlobalUiMessageSender private val uiMessageSender: UiMessageSender, private val dispatchers: CoroutineDispatcherProvider, - private val showBottomSheet: () -> Unit, - private val dismissBottomSheet: () -> Unit, - private val onDynamicAddressesEnabled: () -> Unit, + @Assisted private val userWallet: UserWallet, + @Assisted private val cryptoCurrencyStatusProvider: Provider, + @Assisted private val appCurrencyProvider: Provider, + @Assisted private val coroutineScope: CoroutineScope, + @Assisted("showBottomSheet") private val showBottomSheet: () -> Unit, + @Assisted("dismissBottomSheet") private val dismissBottomSheet: () -> Unit, + @Assisted("onDynamicAddressesStateChanged") private val onDynamicAddressesStateChanged: () -> Unit, ) { + private val userWalletId get() = userWallet.walletId + private val _bottomSheetConfig = MutableStateFlow( DynamicAddressesBottomSheetConfig.Enable( isCardScanRequired = false, @@ -45,27 +68,45 @@ internal class DynamicAddressesDelegate( ) val bottomSheetConfig: StateFlow = _bottomSheetConfig.asStateFlow() - fun onDynamicAddressesClick() { - coroutineScope.launch(dispatchers.main) { - val hasConflicts = dynamicAddressesRepository.hasConflictingCustomTokens(userWalletId, network) - if (hasConflicts) { - _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.Unavailable( - onGotItClick = dismissBottomSheet, - ) - showBottomSheet() - return@launch - } + // region Entry point - val isCardScanRequired = !isXpubAlreadyDerived() - _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.Enable( - isCardScanRequired = isCardScanRequired, - onEnableClick = ::onEnableClick, - ) - showBottomSheet() + fun onDynamicAddressesClick() { + val network = cryptoCurrencyStatusProvider()?.currency?.network ?: return + coroutineScope.launch(dispatchers.main) { + val status = dynamicAddressesRepository.getStatus(userWalletId, network).first() + when (status) { + DynamicAddressesStatus.ENABLED, + DynamicAddressesStatus.ENABLED_REQUIRES_SETUP, + -> onDisableFlow(network) + DynamicAddressesStatus.DISABLED -> onEnableFlow(network) + } } } + // endregion + + // region Enable flow + + private suspend fun onEnableFlow(network: Network) { + val hasConflicts = dynamicAddressesRepository.hasConflictingCustomTokens(userWalletId, network) + if (hasConflicts) { + _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.ConflictingCustomTokens( + onDismissClick = dismissBottomSheet, + ) + showBottomSheet() + return + } + + val isCardScanRequired = !isXpubAlreadyDerived(network) + _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.Enable( + isCardScanRequired = isCardScanRequired, + onEnableClick = ::onEnableClick, + ) + showBottomSheet() + } + private fun onEnableClick() { + val network = cryptoCurrencyStatusProvider()?.currency?.network ?: return coroutineScope.launch(dispatchers.main) { _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.Enable( isCardScanRequired = false, @@ -80,7 +121,7 @@ internal class DynamicAddressesDelegate( } else { TangemLogger.e("Failed to get XPUB: ${error.message}") _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.ServiceUnavailable( - onGotItClick = dismissBottomSheet, + onDismissClick = dismissBottomSheet, ) } return@launch @@ -92,21 +133,21 @@ internal class DynamicAddressesDelegate( ifLeft = { error -> when (error) { is EnableDynamicAddressesError.ConflictingCustomTokens -> { - _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.Unavailable( - onGotItClick = dismissBottomSheet, + _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.ConflictingCustomTokens( + onDismissClick = dismissBottomSheet, ) } is EnableDynamicAddressesError.ServiceError -> { TangemLogger.e("Failed to enable DA: ${error.cause.message}") _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.ServiceUnavailable( - onGotItClick = dismissBottomSheet, + onDismissClick = dismissBottomSheet, ) } } }, ifRight = { dismissBottomSheet() - onDynamicAddressesEnabled() + onDynamicAddressesStateChanged() uiMessageSender.send( SnackbarMessage(message = resourceReference(R.string.dynamic_addresses_enabled_toast_title)), ) @@ -115,11 +156,208 @@ internal class DynamicAddressesDelegate( } } - private suspend fun isXpubAlreadyDerived(): Boolean { + // endregion + + // region Disable flow + + private fun onDisableFlow(network: Network) { + coroutineScope.launch(dispatchers.main) { + disableDynamicAddressesUseCase(userWalletId, network).fold( + ifLeft = { error -> + TangemLogger.e("Failed to check disable: ${error.message}") + _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.ServiceUnavailable( + onDismissClick = dismissBottomSheet, + ) + showBottomSheet() + }, + ifRight = { isConsolidationRequired -> + if (!isConsolidationRequired) { + showSimpleDisableSheet() + } else { + showDisableSheetAndLoadFee() + } + }, + ) + } + } + + private fun showSimpleDisableSheet() { + _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.DisableWithoutConsolidation( + onDisableClick = ::onSimpleDisableClick, + onReadMoreClick = ::onReadMoreClick, + ) + showBottomSheet() + } + + private fun onSimpleDisableClick() { + val network = cryptoCurrencyStatusProvider()?.currency?.network ?: return + coroutineScope.launch(dispatchers.main) { + runSuspendCatching { dynamicAddressesRepository.disable(userWalletId, network) } + .onSuccess { + dismissBottomSheet() + onDynamicAddressesStateChanged() + uiMessageSender.send( + SnackbarMessage(message = resourceReference(R.string.dynamic_addresses_disabled_popup_title)), + ) + } + .onFailure { e -> + TangemLogger.e("Failed to disable DA: ${e.message}") + _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.ServiceUnavailable( + onDismissClick = dismissBottomSheet, + ) + } + } + } + + private fun showDisableSheetAndLoadFee() { + _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.DisableWithConsolidation( + feeState = DynamicAddressesBottomSheetConfig.DisableFeeState.Loading, + onDisableClick = ::onDisableClick, + onRefreshFee = ::loadDisableFee, + onReadMoreClick = ::onReadMoreClick, + ) + showBottomSheet() + loadDisableFee() + } + + private fun loadDisableFee() { + coroutineScope.launch(dispatchers.main) { + _bottomSheetConfig.value = disableWithConsolidationConfig().copy( + feeState = DynamicAddressesBottomSheetConfig.DisableFeeState.Loading, + ) + + val status = cryptoCurrencyStatusProvider() + val currency = status?.currency + val balance = status?.value?.amount + val address = status?.value?.networkAddress?.defaultAddress?.value + + if (currency == null || balance == null || address == null) { + _bottomSheetConfig.value = disableWithConsolidationConfig().copy( + feeState = DynamicAddressesBottomSheetConfig.DisableFeeState.Error, + ) + return@launch + } + + getFeeUseCase( + amount = balance, + destination = address, + userWallet = userWallet, + cryptoCurrency = currency, + ).fold( + ifLeft = { + _bottomSheetConfig.value = disableWithConsolidationConfig().copy( + feeState = DynamicAddressesBottomSheetConfig.DisableFeeState.Error, + ) + }, + ifRight = { txFee -> + val fee = txFee.normal + val fiatFormatted = getFiatString( + value = fee.amount.value, + rate = status.value.fiatRate, + appCurrency = appCurrencyProvider(), + approximate = true, + ) + _bottomSheetConfig.value = disableWithConsolidationConfig().copy( + feeState = DynamicAddressesBottomSheetConfig.DisableFeeState.Content( + feeSymbol = currency.symbol, + fiatFormatted = fiatFormatted, + ), + ) + }, + ) + } + } + + private fun disableWithConsolidationConfig(): DynamicAddressesBottomSheetConfig.DisableWithConsolidation { + return _bottomSheetConfig.value as? DynamicAddressesBottomSheetConfig.DisableWithConsolidation + ?: DynamicAddressesBottomSheetConfig.DisableWithConsolidation( + onDisableClick = ::onDisableClick, + onRefreshFee = ::loadDisableFee, + onReadMoreClick = ::onReadMoreClick, + ) + } + + // TODO: Replace with actual DA documentation URL + private fun onReadMoreClick() { + // Stub: open documentation about Dynamic Addresses consolidation + } + + private fun onDisableClick() { + val network = cryptoCurrencyStatusProvider()?.currency?.network ?: return + coroutineScope.launch(dispatchers.main) { + _bottomSheetConfig.value = disableWithConsolidationConfig().copy( + isSending = true, + ) + + val txData = createConsolidationTransactionUseCase(userWalletId, network).fold( + ifLeft = { error -> + TangemLogger.e("Failed to create consolidation tx: ${error.message}") + _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.ServiceUnavailable( + onDismissClick = dismissBottomSheet, + ) + return@launch + }, + ifRight = { it }, + ) + + sendTransactionUseCase( + txData = txData, + userWallet = userWallet, + network = network, + ).fold( + ifLeft = { error -> + if (error is SendTransactionError.UserCancelledError) { + dismissBottomSheet() + } else { + TangemLogger.e("Failed to send consolidation tx: $error") + _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.ServiceUnavailable( + onDismissClick = dismissBottomSheet, + ) + } + }, + ifRight = { + try { + dynamicAddressesRepository.disable(userWalletId, network) + } catch (e: Exception) { + TangemLogger.e("Failed to disable DA after consolidation: ${e.message}") + } + dismissBottomSheet() + onDynamicAddressesStateChanged() + uiMessageSender.send( + SnackbarMessage( + message = resourceReference(R.string.dynamic_addresses_disabled_popup_title), + ), + ) + }, + ) + } + } + + // endregion + + // region Common + + private suspend fun isXpubAlreadyDerived(network: Network): Boolean { return isXpubDerivedUseCase(userWalletId, network) } private fun isUserCancellation(error: Throwable): Boolean { return error is TangemSdkError.UserCancelled || error.cause is TangemSdkError.UserCancelled } + + // endregion + + @AssistedFactory + interface Factory { + @Suppress("LongParameterList") + fun create( + userWallet: UserWallet, + cryptoCurrencyStatusProvider: Provider, + appCurrencyProvider: Provider, + coroutineScope: CoroutineScope, + @Assisted("showBottomSheet") showBottomSheet: () -> Unit, + @Assisted("dismissBottomSheet") dismissBottomSheet: () -> Unit, + @Assisted("onDynamicAddressesStateChanged") onDynamicAddressesStateChanged: () -> Unit, + ): DynamicAddressesDelegate + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index f5def6e3a1..bb946cf2a8 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -11,10 +11,7 @@ import com.tangem.blockchain.common.address.AddressType import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles import com.tangem.domain.dynamicaddresses.DynamicAddressesSupportedBlockchains -import com.tangem.domain.dynamicaddresses.EnableDynamicAddressesUseCase -import com.tangem.domain.dynamicaddresses.IsXpubDerivedUseCase import com.tangem.domain.dynamicaddresses.IsXpubSupportedUseCase -import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.common.ui.bottomsheet.receive.AddressModel @@ -170,11 +167,9 @@ internal class TokenDetailsModel @Inject constructor( private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase, private val yieldSupplyGetRewardsBalanceUseCase: YieldSupplyGetRewardsBalanceUseCase, private val signCloreMessageUseCase: SignCloreMessageUseCase, - private val enableDynamicAddressesUseCase: EnableDynamicAddressesUseCase, private val isXpubSupportedUseCase: IsXpubSupportedUseCase, - private val isXpubDerivedUseCase: IsXpubDerivedUseCase, - private val dynamicAddressesRepository: DynamicAddressesRepository, private val dynamicAddressesFeatureToggles: DynamicAddressesFeatureToggles, + private val dynamicAddressesDelegateFactory: DynamicAddressesDelegate.Factory, private val dialogFactory: TokenDetailsDialogFactory, private val userWalletsListRepository: UserWalletsListRepository, private val singleAccountListSupplier: SingleAccountListSupplier, @@ -246,21 +241,16 @@ internal class TokenDetailsModel @Inject constructor( // region Dynamic Addresses val dynamicAddressesDelegate by lazy(mode = LazyThreadSafetyMode.NONE) { - DynamicAddressesDelegate( - enableDynamicAddressesUseCase = enableDynamicAddressesUseCase, - isXpubDerivedUseCase = isXpubDerivedUseCase, - dynamicAddressesRepository = dynamicAddressesRepository, - getExtendedPublicKeyUseCase = getExtendedPublicKeyForCurrencyUseCase, - uiMessageSender = uiMessageSender, - userWalletId = userWalletId, - network = cryptoCurrency.network, + dynamicAddressesDelegateFactory.create( + userWallet = userWallet, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + appCurrencyProvider = Provider { selectedAppCurrencyFlow.value }, coroutineScope = modelScope, - dispatchers = dispatchers, showBottomSheet = { bottomSheetNavigation.activate(TokenDetailsBottomSheetConfig.DynamicAddresses) }, dismissBottomSheet = bottomSheetNavigation::dismiss, - onDynamicAddressesEnabled = ::onDynamicAddressesEnabled, + onDynamicAddressesStateChanged = ::onDynamicAddressesStateChanged, ) } // endregion Dynamic Addresses @@ -707,7 +697,7 @@ internal class TokenDetailsModel @Inject constructor( override fun onDynamicAddressesClick() = dynamicAddressesDelegate.onDynamicAddressesClick() - private fun onDynamicAddressesEnabled() { + private fun onDynamicAddressesStateChanged() { updateTopBarMenu() modelScope.launch(dispatchers.main) { cryptoCurrencyBalanceFetcher.invokeAndAwait(userWalletId = userWalletId, currency = cryptoCurrency) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/dynamicaddresses/DynamicAddressesBottomSheet.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/dynamicaddresses/DynamicAddressesBottomSheet.kt index a12557f768..556fd79f30 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/dynamicaddresses/DynamicAddressesBottomSheet.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/dynamicaddresses/DynamicAddressesBottomSheet.kt @@ -4,12 +4,14 @@ import androidx.compose.runtime.Composable import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle +import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.R as CoreR @Composable internal fun DynamicAddressesBottomSheet(config: TangemBottomSheetConfig) { TangemModalBottomSheet( config = config, + containerColor = TangemTheme.colors.background.tertiary, title = { TangemModalBottomSheetTitle( endIconRes = CoreR.drawable.ic_close_24, @@ -19,10 +21,18 @@ internal fun DynamicAddressesBottomSheet(config: TangemBottomSheetConfig) { ) { content -> when (content) { is DynamicAddressesBottomSheetConfig.Enable -> DynamicAddressesEnableContent(content = content) - is DynamicAddressesBottomSheetConfig.Unavailable -> DynamicAddressesUnavailableContent(content = content) - is DynamicAddressesBottomSheetConfig.ServiceUnavailable -> DynamicAddressesServiceUnavailableContent( - content = content, - ) + is DynamicAddressesBottomSheetConfig.DisableWithoutConsolidation -> { + DynamicAddressesDisableWithoutConsolidationContent(content = content) + } + is DynamicAddressesBottomSheetConfig.DisableWithConsolidation -> { + DynamicAddressesDisableWithConsolidationContent(content = content) + } + is DynamicAddressesBottomSheetConfig.ConflictingCustomTokens -> { + DynamicAddressesConflictingCustomTokensContent(content = content) + } + is DynamicAddressesBottomSheetConfig.ServiceUnavailable -> { + DynamicAddressesServiceUnavailableContent(content = content) + } } } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/dynamicaddresses/DynamicAddressesBottomSheetConfig.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/dynamicaddresses/DynamicAddressesBottomSheetConfig.kt index a228fb29b2..7270a74d1e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/dynamicaddresses/DynamicAddressesBottomSheetConfig.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/dynamicaddresses/DynamicAddressesBottomSheetConfig.kt @@ -12,11 +12,33 @@ internal sealed class DynamicAddressesBottomSheetConfig : TangemBottomSheetConfi val onEnableClick: () -> Unit, ) : DynamicAddressesBottomSheetConfig() - data class Unavailable( - val onGotItClick: () -> Unit, + data class DisableWithoutConsolidation( + val onDisableClick: () -> Unit, + val onReadMoreClick: () -> Unit, + ) : DynamicAddressesBottomSheetConfig() + + data class DisableWithConsolidation( + val feeState: DisableFeeState = DisableFeeState.Loading, + val isSending: Boolean = false, + val onDisableClick: () -> Unit, + val onRefreshFee: () -> Unit, + val onReadMoreClick: () -> Unit, + ) : DynamicAddressesBottomSheetConfig() + + sealed interface DisableFeeState { + data object Loading : DisableFeeState + data class Content( + val feeSymbol: String, + val fiatFormatted: String, + ) : DisableFeeState + data object Error : DisableFeeState + } + + data class ConflictingCustomTokens( + val onDismissClick: () -> Unit, ) : DynamicAddressesBottomSheetConfig() data class ServiceUnavailable( - val onGotItClick: () -> Unit, + val onDismissClick: () -> Unit, ) : DynamicAddressesBottomSheetConfig() } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/dynamicaddresses/DynamicAddressesBottomSheetContent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/dynamicaddresses/DynamicAddressesBottomSheetContent.kt index fb1eaa7605..1b40922d1d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/dynamicaddresses/DynamicAddressesBottomSheetContent.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/dynamicaddresses/DynamicAddressesBottomSheetContent.kt @@ -1,5 +1,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.dynamicaddresses +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 @@ -14,12 +16,28 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withLink +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.components.TextShimmer +import com.tangem.core.ui.components.audits.AuditLabel +import com.tangem.core.ui.components.audits.AuditLabelUM +import com.tangem.core.ui.components.notifications.Notification +import com.tangem.core.ui.components.notifications.NotificationConfig +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.res.R import com.tangem.core.ui.R as CoreR +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.dynamicaddresses.DynamicAddressesBottomSheetConfig.DisableFeeState @Composable internal fun DynamicAddressesEnableContent(content: DynamicAddressesBottomSheetConfig.Enable) { @@ -72,13 +90,13 @@ internal fun DynamicAddressesEnableContent(content: DynamicAddressesBottomSheetC Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing24)) - PrimaryButton( + PrimaryButtonIconEnd( text = stringResourceSafe(id = R.string.dynamic_addresses_enter_main_button_title), + iconResId = if (content.isCardScanRequired) CoreR.drawable.ic_tangem_24 else null, onClick = content.onEnableClick, modifier = Modifier.fillMaxWidth(), showProgress = content.isLoading, enabled = !content.isLoading, - // TODO add card icon when isCardScanRequired ) Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing16)) @@ -86,12 +104,205 @@ internal fun DynamicAddressesEnableContent(content: DynamicAddressesBottomSheetC } @Composable -internal fun DynamicAddressesUnavailableContent(content: DynamicAddressesBottomSheetConfig.Unavailable) { +internal fun DynamicAddressesDisableWithoutConsolidationContent( + content: DynamicAddressesBottomSheetConfig.DisableWithoutConsolidation, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing16), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + DisableHeader() + + Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing24)) + + PrimaryButtonIconEnd( + text = stringResourceSafe(id = R.string.dynamic_addresses_disable_main_button_title), + iconResId = null, + onClick = content.onDisableClick, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing16)) + } +} + +@Composable +internal fun DynamicAddressesDisableWithConsolidationContent( + content: DynamicAddressesBottomSheetConfig.DisableWithConsolidation, +) { + val isConfirmEnabled = content.feeState is DisableFeeState.Content && !content.isSending + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing16), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + DisableHeader() + + Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing16)) + + DisableFeeBlock( + feeState = content.feeState, + onReadMoreClick = content.onReadMoreClick, + ) + + if (content.feeState is DisableFeeState.Error) { + Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing12)) + + Notification( + config = NotificationConfig( + title = resourceReference(R.string.send_fee_unreachable_error_title), + subtitle = resourceReference(R.string.send_fee_unreachable_error_text), + iconResId = CoreR.drawable.ic_alert_24, + iconTint = NotificationConfig.IconTint.Warning, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.warning_button_refresh), + onClick = content.onRefreshFee, + ), + ), + containerColor = TangemTheme.colors.background.action, + ) + } + + Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing24)) + + PrimaryButtonIconEnd( + text = stringResourceSafe(id = R.string.dynamic_addresses_disable_main_button_title), + iconResId = CoreR.drawable.ic_tangem_24, + onClick = content.onDisableClick, + modifier = Modifier.fillMaxWidth(), + showProgress = content.isSending, + enabled = isConfirmEnabled, + ) + + Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing16)) + } +} + +@Composable +private fun DisableHeader() { + Icon( + painter = painterResource(id = CoreR.drawable.ic_dynamic_addresses_bottomsheet_enable_unavailable), + contentDescription = null, + modifier = Modifier.size(TangemTheme.dimens.size44), + tint = TangemTheme.colors.icon.attention, + ) + + Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing12)) + + Text( + text = stringResourceSafe(id = R.string.dynamic_addresses_disable_title), + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + + Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing8)) + + Text( + text = stringResourceSafe(id = R.string.dynamic_addresses_disable_description), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) +} + +@Composable +private fun DisableFeeBlock(feeState: DisableFeeState, onReadMoreClick: () -> Unit) { + Column(modifier = Modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .fillMaxWidth() + .background( + color = TangemTheme.colors.background.action, + shape = TangemTheme.shapes.roundedCornersXMedium, + ) + .padding(TangemTheme.dimens.spacing12), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResourceSafe(id = R.string.common_network_fee_title), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + when (feeState) { + is DisableFeeState.Loading -> TextShimmer( + style = TangemTheme.typography.body2, + modifier = Modifier.size( + width = TangemTheme.dimens.size80, + height = TangemTheme.dimens.spacing16, + ), + ) + is DisableFeeState.Content -> Row( + verticalAlignment = Alignment.CenterVertically, + ) { + AuditLabel( + state = AuditLabelUM( + text = stringReference(feeState.feeSymbol), + type = AuditLabelUM.Type.General, + ), + ) + Text( + text = feeState.fiatFormatted, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.padding(start = TangemTheme.dimens.spacing4), + ) + } + is DisableFeeState.Error -> Text( + text = "\u2014", + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + } + } + + Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing8)) + DisableFeeDescription(onReadMoreClick = onReadMoreClick) + } +} + +@Composable +private fun DisableFeeDescription(onReadMoreClick: () -> Unit) { + val readMoreText = stringResourceSafe(id = R.string.common_read_more) + val fullText = stringResourceSafe(id = R.string.dynamic_addresses_disable_fee_description) + + val annotatedString = buildAnnotatedString { + withStyle(SpanStyle(color = TangemTheme.colors.text.tertiary)) { + append(fullText) + append(" ") + } + withLink( + link = LinkAnnotation.Clickable( + tag = "read_more", + linkInteractionListener = { onReadMoreClick() }, + ), + ) { + withStyle(SpanStyle(color = TangemTheme.colors.text.accent)) { + append(readMoreText) + } + } + } + + Text( + text = annotatedString, + style = TangemTheme.typography.caption2, + ) +} + +@Composable +internal fun DynamicAddressesConflictingCustomTokensContent( + content: DynamicAddressesBottomSheetConfig.ConflictingCustomTokens, +) { ErrorContent( titleRes = R.string.dynamic_addresses_error_has_custom_token_title, descriptionRes = R.string.dynamic_addresses_error_has_custom_token_description, buttonTextRes = R.string.common_got_it, - onButtonClick = content.onGotItClick, + onButtonClick = content.onDismissClick, ) } @@ -101,7 +312,7 @@ internal fun DynamicAddressesServiceUnavailableContent(content: DynamicAddresses titleRes = R.string.dynamic_addresses_error_service_unavailable_title, descriptionRes = R.string.dynamic_addresses_error_service_unavailable_description, buttonTextRes = R.string.common_got_it, - onButtonClick = content.onGotItClick, + onButtonClick = content.onDismissClick, ) } @@ -117,7 +328,7 @@ private fun ErrorContent(titleRes: Int, descriptionRes: Int, buttonTextRes: Int, painter = painterResource(id = CoreR.drawable.ic_dynamic_addresses_bottomsheet_enable_unavailable), contentDescription = null, modifier = Modifier.size(TangemTheme.dimens.size44), - tint = TangemTheme.colors.icon.warning, + tint = TangemTheme.colors.icon.attention, ) Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing12)) @@ -176,4 +387,143 @@ private fun FeatureItem(iconRes: Int, title: String, description: String) { ) } } -} \ No newline at end of file +} + +// region Previews + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_Enable() { + TangemThemePreview { + DynamicAddressesEnableContent( + content = DynamicAddressesBottomSheetConfig.Enable( + isCardScanRequired = false, + onEnableClick = {}, + ), + ) + } +} + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_EnableWithCardScan() { + TangemThemePreview { + DynamicAddressesEnableContent( + content = DynamicAddressesBottomSheetConfig.Enable( + isCardScanRequired = true, + onEnableClick = {}, + ), + ) + } +} + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_DisableWithoutConsolidation() { + TangemThemePreview { + DynamicAddressesDisableWithoutConsolidationContent( + content = DynamicAddressesBottomSheetConfig.DisableWithoutConsolidation( + onDisableClick = {}, + onReadMoreClick = {}, + ), + ) + } +} + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_DisableFeeLoading() { + TangemThemePreview { + DynamicAddressesDisableWithConsolidationContent( + content = DynamicAddressesBottomSheetConfig.DisableWithConsolidation( + feeState = DisableFeeState.Loading, + onDisableClick = {}, + onRefreshFee = {}, + onReadMoreClick = {}, + ), + ) + } +} + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_DisableFeeLoaded() { + TangemThemePreview { + DynamicAddressesDisableWithConsolidationContent( + content = DynamicAddressesBottomSheetConfig.DisableWithConsolidation( + feeState = DisableFeeState.Content( + feeSymbol = "BTC", + fiatFormatted = "~$0.12", + ), + onDisableClick = {}, + onRefreshFee = {}, + onReadMoreClick = {}, + ), + ) + } +} + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_DisableFeeError() { + TangemThemePreview { + DynamicAddressesDisableWithConsolidationContent( + content = DynamicAddressesBottomSheetConfig.DisableWithConsolidation( + feeState = DisableFeeState.Error, + onDisableClick = {}, + onRefreshFee = {}, + onReadMoreClick = {}, + ), + ) + } +} + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_DisableSending() { + TangemThemePreview { + DynamicAddressesDisableWithConsolidationContent( + content = DynamicAddressesBottomSheetConfig.DisableWithConsolidation( + feeState = DisableFeeState.Content( + feeSymbol = "BTC", + fiatFormatted = "~$0.12", + ), + isSending = true, + onDisableClick = {}, + onRefreshFee = {}, + onReadMoreClick = {}, + ), + ) + } +} + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_ConflictingCustomTokens() { + TangemThemePreview { + DynamicAddressesConflictingCustomTokensContent( + content = DynamicAddressesBottomSheetConfig.ConflictingCustomTokens(onDismissClick = {}), + ) + } +} + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_ServiceUnavailable() { + TangemThemePreview { + DynamicAddressesServiceUnavailableContent( + content = DynamicAddressesBottomSheetConfig.ServiceUnavailable(onDismissClick = {}), + ) + } +} + +// endregion \ No newline at end of file diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 165d042410..d9b87d6b95 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1482" +tangemBlockchainSdk = "develop-1486" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-602" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^