Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-10 11:10:57 +03:00
parent 8966e28e29
commit 799a38655e
9 changed files with 733 additions and 67 deletions

View file

@ -80,6 +80,10 @@
<string name="action_buttons_swap_not_enough_tokens_alert_title">Adicionar tokens</string>
<string name="action_buttons_you_want_to_receive">Escolha o token que deseja receber.</string>
<string name="action_buttons_you_want_to_swap">Escolha o token que deseja trocar.</string>
<string name="add_and_manage_sheet_manage_subtitle">Adicione ao seu portfólio</string>
<string name="add_and_manage_sheet_manage_title">Adicionar tokens</string>
<string name="add_and_manage_sheet_organize_subtitle">Classificação e agrupamento</string>
<string name="add_and_manage_sheet_organize_title">Organizar tokens</string>
<string name="add_custom_token_choose_network">Escolha a rede</string>
<string name="add_custom_token_title">Adicionar token personalizado</string>
<string name="add_tokens_title">Gerenciar tokens</string>
@ -584,6 +588,8 @@
<string name="fee_selector_choose_token_description">Escolha qual token usar para pagar a taxa de rede. %s</string>
<string name="fee_selector_choose_token_title">Escolha o token</string>
<string name="feed_market_and_news">Mercado e Notícias</string>
<string name="feed_search_show_all_user_assets">Mostrar tudo</string>
<string name="feed_search_show_less_user_assets">Mostrar menos</string>
<string name="feed_trending_now">Em alta agora</string>
<string name="feedback_data_collection_message">As informações a seguir são opcionais. Você pode apagá-las se não quiser compartilhá-las.</string>
<string name="feedback_preface_rate_negative">Diga-nos quais funções estão faltando e tentaremos ajudá-lo.</string>
@ -719,6 +725,7 @@
<string name="koinos_mana_exceeds_koin_balance_title">Limite de mana</string>
<string name="koinos_mana_level_description">A rede Koinos exige Mana para o pagamento das taxas de rede. Você tem %1$s/%2$s Mana</string>
<string name="koinos_mana_level_title">Nível de mana</string>
<string name="main_add_and_manage_tokens">Adicionar e gerenciar</string>
<string name="main_empty_tokens_list_message">Para começar a rastrear seus criptoativos e transações, adicione tokens.</string>
<string name="main_manage_tokens">Gerenciar tokens</string>
<string name="main_qr_scan_hint">Leia o código QR para enviar fundos ou conectar-se a um aplicativo</string>
@ -1534,6 +1541,7 @@
<string name="swapping_trade_too_large_title">Negociação em grande escala</string>
<string name="tangem_pay_beta_notification_subtitle">Teremos todo o prazer em receber seu feedback.</string>
<string name="tangem_pay_beta_notification_title">O Tangem Pay agora está em versão beta.</string>
<string name="tangem_pay_card_details_unable_to_rename_card_title">Não foi possível renomear o cartão.</string>
<string name="tangem_pay_card_frozen">Cartão bloqueado</string>
<string name="tangem_pay_card_payment">Pagamento com cartão</string>
<string name="tangem_pay_deposit">Depósito</string>
@ -1567,7 +1575,9 @@
<string name="tangem_pay_unfreeze_card_success">Seu cartão foi desbloqueado.</string>
<string name="tangem_pay_withdrawal">Retirada</string>
<string name="tangempay_account_unable_to_use_rooted">Não é possível usar em dispositivos com root.</string>
<string name="tangempay_available_balance">Saldo disponível</string>
<string name="tangempay_cancel_kyc">Ocultar KYC da tela principal</string>
<string name="tangempay_card_default_name">Cartão Tangem Pay 1</string>
<string name="tangempay_card_details_add_funds">Adicionar fundos</string>
<string name="tangempay_card_details_add_funds_subtitle">Opções de recarga</string>
<string name="tangempay_card_details_add_to_wallet_button_text">Adicionar ao Google Wallet</string>
@ -1600,10 +1610,13 @@
<string name="tangempay_card_details_receive_description">Compartilhe seu endereço ou mostre o código QR.</string>
<string name="tangempay_card_details_receive_error_description">Problemas técnicos detectados. Tente novamente mais tarde ou entre em contato com o suporte.</string>
<string name="tangempay_card_details_receive_error_title">Receber indisponível agora</string>
<string name="tangempay_card_details_rename_card_invalid_description">Somente letras e números são permitidos.</string>
<string name="tangempay_card_details_rename_card_invalid_title">Caracteres inválidos</string>
<string name="tangempay_card_details_reveal_text">Revelar</string>
<string name="tangempay_card_details_show_details">Mostrar detalhes</string>
<string name="tangempay_card_details_swap_description">Troque qualquer ativo da sua carteira por um cartão.</string>
<string name="tangempay_card_details_title">Detalhes do cartão</string>
<string name="tangempay_card_details_unable_to_rename_card_description">Por favor, tente novamente mais tarde.</string>
<string name="tangempay_card_details_unfreeze_card">Descongelar cartão</string>
<string name="tangempay_card_details_view_pin_code_description">Volte ao aplicativo se você se esquecer.</string>
<string name="tangempay_card_details_view_pin_code_title">Seu código PIN</string>
@ -1611,12 +1624,16 @@
<string name="tangempay_card_details_withdraw_error_title">Retirada indisponível agora</string>
<string name="tangempay_card_details_withdraw_in_progress_description">Você não pode iniciar uma troca ou um novo saque até que o atual seja concluído.</string>
<string name="tangempay_card_details_withdraw_in_progress_title">Retirada em andamento</string>
<string name="tangempay_card_page_settings_title">Configurações do cartão</string>
<string name="tangempay_change_pin_code">Alterar código PIN</string>
<string name="tangempay_come_back_if_forget_pin">Volte ao aplicativo se você se esquecer.</string>
<string name="tangempay_digital_card">Cartão digital</string>
<string name="tangempay_factory_settings_warning_title">Entendo que perderei completamente o acesso ao meu cartão Tangem Pay e a todos os fundos nele contidos, sem possibilidade de recuperação.</string>
<string name="tangempay_failed_to_issue_card">Falha na emissão do cartão</string>
<string name="tangempay_failed_to_issue_card_retry_description">Ocorreu um erro técnico. Tente novamente clicando no botão abaixo.</string>
<string name="tangempay_failed_to_issue_card_support_description">Ocorreu um erro técnico. Por favor, entre em contato com o suporte.</string>
<string name="tangempay_feature_will_be_available_soon">O recurso estará disponível em breve.</string>
<string name="tangempay_feature_will_be_available_soon_description">Você poderá emitir cartões adicionais para sua conta de pagamento.</string>
<string name="tangempay_get_banner_description">Obtenha seu cartão virtual Visa Tangem grátis.</string>
<string name="tangempay_get_tangem_pay">Obtenha o Tangem Pay</string>
<string name="tangempay_go_to_support">Acesse o Suporte</string>
@ -1648,6 +1665,7 @@
<string name="tangempay_onboarding_security_description">Uma conta de pagamento separada será criada sem divulgar seus endereços e bens.</string>
<string name="tangempay_onboarding_security_title">Privacidade incomparável</string>
<string name="tangempay_onboarding_title">Obtenha seu cartão Tangem Pay gratuito em minutos.</string>
<string name="tangempay_pay_support">Suporte de pagamento</string>
<string name="tangempay_payment_account">Conta de pagamento</string>
<string name="tangempay_payment_account_sync_needed">A conta de pagamento não está sincronizada.</string>
<string name="tangempay_pin_validation_error_message">PIN inválido: evite sequências ou repetições.</string>
@ -1660,6 +1678,7 @@
<string name="tangempay_tangem_visa_card">Use USDC para pagamentos do dia a dia.</string>
<string name="tangempay_temporarily_unavailable">O serviço Tangem Pay está temporariamente inacessível.</string>
<string name="tangempay_title">Tangem Pay</string>
<string name="tangempay_usdc_on_polygon_network">USDC na rede Polygon</string>
<string name="tangempay_use_tangem_device_to_restore_payment_account">Clique no botão abaixo para restaurar o acesso.</string>
<string name="tangempay_withdrawal_note_description">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.</string>
<string name="tangempay_withdrawal_note_title">Observe</string>
@ -1690,6 +1709,10 @@
<string name="token_details_staking_block_title">Serviço de staking</string>
<string name="token_details_token_type_subtitle">%1$s token em %%imagem%% %2$s rede</string>
<string name="token_details_token_type_subtitle_no_standard">Token em %%imagem%% %1$s rede</string>
<string name="token_details_toolbar_subtitle_network">%s rede</string>
<string name="token_details_toolbar_subtitle_standard">%1$s em %2$s rede</string>
<string name="token_details_toolbar_title_token_in_account">%1$s em %%imagem%% %2$s</string>
<string name="token_details_toolbar_title_token_in_wallet">%1$s em %2$s %%imagem%%</string>
<string name="token_details_unable_hide_alert_message">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</string>
<string name="token_details_unable_hide_alert_title">Não foi possível ocultar %s</string>
<string name="token_market_metrics_no_data">N/A</string>

View file

@ -80,6 +80,10 @@
<string name="action_buttons_swap_not_enough_tokens_alert_title">Add tokens</string>
<string name="action_buttons_you_want_to_receive">Choose the token you want to receive</string>
<string name="action_buttons_you_want_to_swap">Choose the token you want to swap</string>
<string name="add_and_manage_sheet_manage_subtitle">Add to your portfolio</string>
<string name="add_and_manage_sheet_manage_title">Add tokens</string>
<string name="add_and_manage_sheet_organize_subtitle">Sorting and grouping</string>
<string name="add_and_manage_sheet_organize_title">Organize tokens</string>
<string name="add_custom_token_choose_network">Choose network</string>
<string name="add_custom_token_title">Add custom token</string>
<string name="add_tokens_title">Manage tokens</string>
@ -477,6 +481,11 @@
<string name="domain_receive_assets_onboarding_title">Send funds using only</string>
<string name="dynamic_addresses">Dynamic addresses</string>
<string name="dynamic_addresses_custom_token_error_on_addition">Dynamic Addresses is enabled. Custom \"change\" and \"index\" are unavailable.</string>
<string name="dynamic_addresses_disable_description">You are turning off Dynamic Addresses. Funds will now be received on your fixed address.</string>
<string name="dynamic_addresses_disable_fee_description">A network fee applies to consolidate funds into the fixed address.</string>
<string name="dynamic_addresses_disable_main_button_title">Disable Dynamic Addresses</string>
<string name="dynamic_addresses_disable_title">Disable dynamic addresses</string>
<string name="dynamic_addresses_disabled_popup_title">Dynamic addresses disabled</string>
<string name="dynamic_addresses_enabled_toast_title">Dynamic addresses enabled</string>
<string name="dynamic_addresses_enter_features_privacy_description">Use a new address for each transaction to reduce traceability and improve on-chain privacy.</string>
<string name="dynamic_addresses_enter_features_privacy_title">Enhanced Privacy</string>
@ -584,6 +593,8 @@
<string name="fee_selector_choose_token_description">Choose which token to use to pay\nthe network fee. %s</string>
<string name="fee_selector_choose_token_title">Choose token</string>
<string name="feed_market_and_news">Market &amp; News</string>
<string name="feed_search_show_all_user_assets">Show All</string>
<string name="feed_search_show_less_user_assets">Show less</string>
<string name="feed_tangem_ai">Tangem AI</string>
<string name="feed_trending_now">Trending Now</string>
<string name="feedback_data_collection_message">The following information is optional. You can erase it if you don\'t want to share it.</string>
@ -720,6 +731,7 @@
<string name="koinos_mana_exceeds_koin_balance_title">Mana limit</string>
<string name="koinos_mana_level_description">The Koinos network requires Mana for network fees. You have %1$s/%2$s Mana</string>
<string name="koinos_mana_level_title">Mana level</string>
<string name="main_add_and_manage_tokens">Add &amp; Manage</string>
<string name="main_empty_tokens_list_message">To begin tracking your crypto assets and transactions, add tokens</string>
<string name="main_manage_tokens">Manage tokens</string>
<string name="main_qr_scan_hint">Scan QR code to send funds or connect to an app</string>
@ -1569,7 +1581,9 @@
<string name="tangem_pay_unfreeze_card_success">Your card is unfrozen.</string>
<string name="tangem_pay_withdrawal">Withdrawal</string>
<string name="tangempay_account_unable_to_use_rooted">Unable to use on rooted device</string>
<string name="tangempay_available_balance">Available balance</string>
<string name="tangempay_cancel_kyc">Hide KYC from main screen</string>
<string name="tangempay_card_default_name">Tangem Pay Card 1</string>
<string name="tangempay_card_details_add_funds">Add funds</string>
<string name="tangempay_card_details_add_funds_subtitle">Top-up options</string>
<string name="tangempay_card_details_add_to_wallet_button_text">Add to Google Wallet</string>
@ -1619,10 +1633,13 @@
<string name="tangempay_card_page_settings_title">Card settings</string>
<string name="tangempay_change_pin_code">Change PIN-code</string>
<string name="tangempay_come_back_if_forget_pin">Come back to the app if you forget it.</string>
<string name="tangempay_digital_card">Digital card</string>
<string name="tangempay_factory_settings_warning_title">I understand that I will completely lose access to my Tangem Pay Card and all funds on it without the possibility of recovery</string>
<string name="tangempay_failed_to_issue_card">Failed to issue card</string>
<string name="tangempay_failed_to_issue_card_retry_description">A technical error has occurred, please try again by clicking the button below.</string>
<string name="tangempay_failed_to_issue_card_support_description">A technical error has occurred, please contact support.</string>
<string name="tangempay_feature_will_be_available_soon">Feature will be availible soon</string>
<string name="tangempay_feature_will_be_available_soon_description">You will be available to issue additional cards for your payment account</string>
<string name="tangempay_get_banner_description">Get your free Tangem Visa virtual card</string>
<string name="tangempay_get_tangem_pay">Get Tangem Pay</string>
<string name="tangempay_go_to_support">Go to Support</string>
@ -1654,6 +1671,7 @@
<string name="tangempay_onboarding_security_description">A separate payment account will be created without disclosing your addresses and assets</string>
<string name="tangempay_onboarding_security_title">Unrivaled privacy</string>
<string name="tangempay_onboarding_title">Get your free Tangem Pay Card in minutes</string>
<string name="tangempay_pay_support">Pay Support</string>
<string name="tangempay_payment_account">Payment account</string>
<string name="tangempay_payment_account_sync_needed">Payment account is not synced</string>
<string name="tangempay_pin_validation_error_message">Invalid PIN: avoid sequences or repeats</string>
@ -1666,6 +1684,7 @@
<string name="tangempay_tangem_visa_card">Use USDC for everyday payments</string>
<string name="tangempay_temporarily_unavailable">Tangem Pay is temporarily unreachable</string>
<string name="tangempay_title">Tangem Pay</string>
<string name="tangempay_usdc_on_polygon_network">USDC on Polygon network</string>
<string name="tangempay_use_tangem_device_to_restore_payment_account">Click the button below to restore access</string>
<string name="tangempay_withdrawal_note_description">Your on-chain USDC Polygon balance differs from your card balance and updates within 2 business days after a purchase. Funds from refunded purchases wont be returned your on-chain balance or be available for withdrawal, but will stay on your card balance for purchases.</string>
<string name="tangempay_withdrawal_note_title">Please note</string>

View file

@ -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
}
}
}

View file

@ -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<CryptoCurrencyStatus?>,
@Assisted private val appCurrencyProvider: Provider<AppCurrency>,
@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>(
DynamicAddressesBottomSheetConfig.Enable(
isCardScanRequired = false,
@ -45,27 +68,45 @@ internal class DynamicAddressesDelegate(
)
val bottomSheetConfig: StateFlow<DynamicAddressesBottomSheetConfig> = _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<CryptoCurrencyStatus?>,
appCurrencyProvider: Provider<AppCurrency>,
coroutineScope: CoroutineScope,
@Assisted("showBottomSheet") showBottomSheet: () -> Unit,
@Assisted("dismissBottomSheet") dismissBottomSheet: () -> Unit,
@Assisted("onDynamicAddressesStateChanged") onDynamicAddressesStateChanged: () -> Unit,
): DynamicAddressesDelegate
}
}

View file

@ -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)

View file

@ -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<DynamicAddressesBottomSheetConfig>(
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)
}
}
}
}

View file

@ -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()
}

View file

@ -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) {
)
}
}
}
}
// 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

View file

@ -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 ^