Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-26 16:27:33 +02:00
parent bce16bf692
commit 6d461cf400
16 changed files with 180 additions and 84 deletions

View file

@ -12,12 +12,20 @@
<string name="access_code_create_description">Set a %s-digit Access Code to unlock your wallet.</string>
<string name="access_code_create_title">Create Access Code</string>
<string name="access_code_navtitle">Access code</string>
<string name="account_add_limit_dialog_description">You cannot create more than %1$s accounts. Archive one to add new.</string>
<string name="account_add_limit_dialog_title">Cant add new account</string>
<string name="account_archive_success_message">Account archived</string>
<string name="account_archived_accounts">Archived accounts</string>
<string name="account_archived_recover">Recover</string>
<string name="account_archived_recover_dialog_description">Youre about to recover “%1$s”.</string>
<string name="account_archived_recover_dialog_title">Recover account</string>
<string name="account_archived_title">Archived</string>
<string name="account_create_success_message">Account created</string>
<string name="account_details_archive">Archive account</string>
<string name="account_details_archive_action">Archive</string>
<string name="account_details_archive_description">You are archiving this account, but you can always get it back.</string>
<string name="account_details_title">Account</string>
<string name="account_edit_success_message">Account saved</string>
<string name="account_form_account_index">Account #%s — used for address derivation.</string>
<string name="account_form_create_button">Add account</string>
<string name="account_form_edit_button">Save</string>
@ -26,6 +34,9 @@
<string name="account_form_placeholder_new_account">New account</string>
<string name="account_form_title_create">Add account</string>
<string name="account_form_title_edit">Edit account</string>
<string name="account_label_tokens_info">%1$s in %2$s</string>
<string name="account_recover_success_message">Account recovered</string>
<string name="account_reorder_description">Long tap on an account to reorder accounts</string>
<string name="account_unsaved_dialog_action_first">Keep Editing</string>
<string name="account_unsaved_dialog_action_second">Discard</string>
<string name="account_unsaved_dialog_message_create">Are you sure you want to discard new account?</string>
@ -117,6 +128,16 @@
<string name="biometric_lockout_warning_description">Please try again in 30 seconds or scan the card or ring</string>
<string name="biometric_lockout_warning_title">Too many attempts</string>
<string name="biometric_unavailable_warning">You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings.</string>
<string name="bitcoin_promo_activation_error">An error occurred while processing your promo code. Please try again later.</string>
<string name="bitcoin_promo_activation_error_title">Activation error</string>
<string name="bitcoin_promo_activation_success">Your promo code was successfully activated. A bonus of 10 USDT in Bitcoin will be credited to your account within 14 days.</string>
<string name="bitcoin_promo_activation_success_title">Promo Code Activated</string>
<string name="bitcoin_promo_already_activated">This promo code has already been used and cannot be activated again.</string>
<string name="bitcoin_promo_already_activated_title">Code unavailable</string>
<string name="bitcoin_promo_invalid_code">This promo code is not valid and cannot be activated.</string>
<string name="bitcoin_promo_invalid_code_title">Invalid code</string>
<string name="bitcoin_promo_no_address">A Bitcoin address is required to receive the bonus. Please add one to your wallet and retry the activation.</string>
<string name="bitcoin_promo_no_address_title">Bitcoin address required</string>
<string name="button_start_backup_process">Start backup process</string>
<string name="buy_token_description">Use a bank card or other payment methods</string>
<plurals name="card_label_card_count">
@ -152,6 +173,7 @@
<string name="cardano_max_amount_has_token_title">Not enough ADA</string>
<string name="common_accept">Accept</string>
<string name="common_access_denied">Access denied</string>
<string name="common_accounts">Accounts</string>
<string name="common_add">Add</string>
<string name="common_add_to_portfolio">Add to portfolio</string>
<string name="common_add_token">Add token</string>
@ -234,11 +256,17 @@
<string name="common_in_progress">In progress</string>
<string name="common_later">Later</string>
<string name="common_left">%1$s left</string>
<string name="common_legacy_bitcoin_address">Legacy Bitcoin</string>
<string name="common_locked">Locked</string>
<string name="common_main_network">Main network</string>
<string name="common_month">month</string>
<string name="common_network_fee_title">Network fee</string>
<string name="common_network_fee_warning_content">Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level</string>
<plurals name="common_networks_count">
<item quantity="one">%d network</item>
<item quantity="other">%d networks</item>
</plurals>
<string name="common_new_address">New address</string>
<string name="common_next">Next</string>
<string name="common_nft">NFT</string>
<string name="common_no">No</string>
@ -363,6 +391,7 @@
<string name="details_title">Details</string>
<string name="disclaimer_error_loading">Check your internet connection or switch to a different network</string>
<string name="disclaimer_title">Terms of service</string>
<string name="domain_receive_assets_default_address">Default Address</string>
<string name="domain_receive_assets_navigation_title">Receive assets</string>
<string name="domain_receive_assets_onboarding_description">Sending assets in other networks will result in permanent loss.</string>
<string name="domain_receive_assets_onboarding_network_name">%s network</string>
@ -717,6 +746,9 @@
<string name="notification_referral_promo_button">Join Now</string>
<string name="notification_referral_promo_text">Share your code - earn 5 USDT per sale. Your friend gets 10% OFF.</string>
<string name="notification_referral_promo_title">Get REWARDS for every friend!</string>
<string name="notification_sepa_button">Buy crypto</string>
<string name="notification_sepa_text">Enjoy zero fees when purchasing crypto via SEPA transfer.</string>
<string name="notification_sepa_title">Buy Crypto with SEPA</string>
<string name="onboarding_access_code_feature_1_description">Set up a single access code to protect all your devices.</string>
<string name="onboarding_access_code_feature_1_title">Protect</string>
<string name="onboarding_access_code_feature_2_description">Set an individual access code for each card or ring later.</string>

View file

@ -31,7 +31,7 @@ class DefaultWalletAddressServiceRepository(
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
walletManager?.wallet?.ens
walletManager?.wallet?.ens.takeIf { it.isNullOrEmpty().not() }
}
}

View file

@ -19,10 +19,9 @@ data class TokenReceiveConfig(
data class ReceiveAddressModel(
val nameService: NameService,
val value: String,
val displayName: String,
) {
enum class NameService {
Default, Ens
Default, Legacy, Ens
}
}

View file

@ -35,7 +35,13 @@ class GetReverseResolvedEnsAddressUseCase(private val walletAddressServiceReposi
return when (reverseResolveAddressResult) {
is ReverseResolveAddressResult.Error -> EnsAddress.Error(reverseResolveAddressResult.error)
ReverseResolveAddressResult.NotSupported -> EnsAddress.NotSupported
is ReverseResolveAddressResult.Resolved -> EnsAddress.Address(reverseResolveAddressResult.name)
is ReverseResolveAddressResult.Resolved -> {
if (reverseResolveAddressResult.name.isEmpty()) {
EnsAddress.NotSupported
} else {
EnsAddress.Address(reverseResolveAddressResult.name)
}
}
}
}
}

View file

@ -26,6 +26,7 @@ import com.tangem.domain.models.ReceiveAddressModel
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
@ -399,16 +400,17 @@ internal class MarketsPortfolioModel @Inject constructor(
ReceiveAddressModel(
nameService = ReceiveAddressModel.NameService.Ens,
value = ens,
displayName = ens,
),
)
}
addresses.availableAddresses.map { address ->
add(
ReceiveAddressModel(
nameService = ReceiveAddressModel.NameService.Default,
nameService = when (address.type) {
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.NameService.Default
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.NameService.Legacy
},
value = address.value,
displayName = "${cryptoCurrency.name} (${cryptoCurrency.symbol})",
),
)
}

View file

@ -209,16 +209,17 @@ internal class NFTReceiveModel @Inject constructor(
ReceiveAddressModel(
nameService = ReceiveAddressModel.NameService.Ens,
value = ens,
displayName = ens,
),
)
}
addresses.availableAddresses.map { address ->
add(
ReceiveAddressModel(
nameService = ReceiveAddressModel.NameService.Default,
nameService = when (address.type) {
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.NameService.Default
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.NameService.Legacy
},
value = address.value,
displayName = cryptoCurrency.symbol,
),
)
}

View file

@ -77,7 +77,7 @@ internal class DefaultTokenReceiveComponent @AssistedInject constructor(
is TokenReceiveRoutes.QrCode -> TokenReceiveQrCodeComponent(
appComponentContext = appComponentContext,
params = TokenReceiveQrCodeComponent.TokenReceiveQrCodeParams(
network = model.params.config.cryptoCurrency.network.name,
cryptoCurrency = model.params.config.cryptoCurrency,
address = model.state.value.addresses[config.addressId] ?: error("Address has to be there"),
callback = model,
onDismiss = ::dismiss,

View file

@ -7,6 +7,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.tokenreceive.entity.ReceiveAddress
import com.tangem.features.tokenreceive.model.TokenReceiveQrCodeModel
import com.tangem.features.tokenreceive.ui.TokenReceiveQrCodeContent
@ -31,7 +32,7 @@ internal class TokenReceiveQrCodeComponent(
data class TokenReceiveQrCodeParams(
val id: Int,
val network: String,
val cryptoCurrency: CryptoCurrency,
val address: ReceiveAddress,
val callback: TokenReceiveQrCodeModelCallback,
val onDismiss: () -> Unit,

View file

@ -1,16 +1,27 @@
package com.tangem.features.tokenreceive.entity
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
internal data class ReceiveAddress(
val value: String,
val type: Type,
) {
@Immutable
sealed interface Type {
data object Ens : Type
sealed interface Primary : Type {
val displayName: TextReference
data class Default(
val displayName: TextReference,
) : Type
val title: TextReference?,
override val displayName: TextReference,
) : Primary
data class Legacy(override val displayName: TextReference) : Primary
}
}
}

View file

@ -44,7 +44,6 @@ internal class TokenReceiveAssetsModel @Inject constructor(
showMemoDisclaimer = params.showMemoDisclaimer,
isEnsResultLoading = false,
notificationConfigs = params.notificationConfigs,
fullName = params.fullName,
),
)

View file

@ -12,8 +12,8 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.models.Asset
import com.tangem.domain.models.ReceiveAddressModel
@ -25,6 +25,8 @@ import com.tangem.domain.transaction.usecase.GetReverseResolvedEnsAddressUseCase
import com.tangem.features.tokenreceive.TokenReceiveComponent
import com.tangem.features.tokenreceive.component.TokenReceiveModelCallback
import com.tangem.features.tokenreceive.entity.ReceiveAddress
import com.tangem.features.tokenreceive.entity.ReceiveAddress.Type.Ens
import com.tangem.features.tokenreceive.entity.ReceiveAddress.Type.Primary
import com.tangem.features.tokenreceive.route.TokenReceiveRoutes
import com.tangem.features.tokenreceive.ui.state.TokenReceiveUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -103,17 +105,33 @@ internal class TokenReceiveModel @Inject constructor(
}
}
private fun mapAddresses(addresses: List<ReceiveAddressModel>): ImmutableMap<Int, ReceiveAddress> {
private fun mapAddresses(
addresses: List<ReceiveAddressModel>,
networkName: String,
): ImmutableMap<Int, ReceiveAddress> {
val conditionToUseTitle = addresses.any { it.nameService == ReceiveAddressModel.NameService.Legacy }
return buildMap {
addresses.mapIndexed { index, model ->
val type = when (model.nameService) {
ReceiveAddressModel.NameService.Default -> {
ReceiveAddress.Type.Default(
displayName = stringReference(model.displayName),
Primary.Default(
displayName = TextReference.Res(
id = R.string.domain_receive_assets_onboarding_network_name,
formatArgs = wrappedList(networkName),
),
title = if (conditionToUseTitle) {
TextReference.Res(R.string.domain_receive_assets_default_address)
} else {
null
},
)
}
ReceiveAddressModel.NameService.Ens -> ReceiveAddress.Type.Ens
ReceiveAddressModel.NameService.Ens -> Ens
ReceiveAddressModel.NameService.Legacy -> Primary.Legacy(
displayName = TextReference.Res(R.string.common_legacy_bitcoin_address),
)
}
put(
key = index,
value = ReceiveAddress(
@ -146,10 +164,18 @@ internal class TokenReceiveModel @Inject constructor(
val newEnsAddresses = reverseResolveResult
.filterIsInstance<EnsAddress.Address>()
.filterNot { it.name in currentAddressValues }
.map { ensAddress -> ReceiveAddress(value = ensAddress.name, type = ReceiveAddress.Type.Ens) }
.map { ensAddress -> ReceiveAddress(value = ensAddress.name, type = Ens) }
val combinedAddresses = (state.value.addresses.values + newEnsAddresses)
.sortedWith(compareByDescending { it.type is ReceiveAddress.Type.Ens })
.sortedWith(
compareBy { address ->
when (address.type) {
is Ens -> 0
is Primary.Default -> 1
is Primary.Legacy -> 2
}
},
)
val updatedAddresses = combinedAddresses
.mapIndexed { index, address -> index to address }
@ -191,7 +217,10 @@ internal class TokenReceiveModel @Inject constructor(
private fun getInitState(): TokenReceiveUM {
return TokenReceiveUM(
addresses = mapAddresses(params.config.receiveAddress),
addresses = mapAddresses(
addresses = params.config.receiveAddress,
networkName = params.config.cryptoCurrency.network.name,
),
iconState = iconStateConverter.convert(params.config.cryptoCurrency),
network = params.config.cryptoCurrency.network.name,
isEnsResultLoading = false,
@ -201,13 +230,13 @@ internal class TokenReceiveModel @Inject constructor(
private fun sendCopyActionAnalytic(receiveAddress: ReceiveAddress) {
val event = when (receiveAddress.type) {
is ReceiveAddress.Type.Default -> {
is Primary -> {
TokenReceiveNewAnalyticsEvent.ButtonCopyAddress(
token = getTokenName(),
blockchainName = params.config.cryptoCurrency.network.name,
)
}
ReceiveAddress.Type.Ens -> {
Ens -> {
TokenReceiveNewAnalyticsEvent.ButtonCopyEns(
token = getTokenName(),
blockchainName = params.config.cryptoCurrency.network.name,

View file

@ -6,7 +6,6 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.tokenreceive.component.TokenReceiveQrCodeComponent
import com.tangem.features.tokenreceive.entity.ReceiveAddress
import com.tangem.features.tokenreceive.ui.state.QrCodeUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
@ -25,9 +24,9 @@ internal class TokenReceiveQrCodeModel @Inject constructor(
internal val state: StateFlow<QrCodeUM>
field = MutableStateFlow<QrCodeUM>(
QrCodeUM(
network = params.network,
network = params.cryptoCurrency.network.name,
addressValue = params.address.value,
addressName = (params.address.type as? ReceiveAddress.Type.Default)?.displayName ?: TextReference.EMPTY,
addressName = TextReference.Str("${params.cryptoCurrency.name} (${params.cryptoCurrency.symbol})"),
onCopyClick = { params.callback.onCopyClick(params.id) },
onShareClick = params.callback::onShareClick,
),

View file

@ -27,6 +27,7 @@ import androidx.compose.ui.util.fastForEach
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.res.getStringSafe
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.SpacerW12
@ -36,6 +37,7 @@ import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.components.buttons.small.TangemIconButton
import com.tangem.core.ui.components.icons.identicon.IdentIcon
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
@ -78,7 +80,6 @@ internal fun TokenReceiveAssetsContent(assetsUM: ReceiveAssetsUM) {
onOpenQrCodeClick = assetsUM.onOpenQrCodeClick,
addresses = assetsUM.addresses,
snackbarHostState = snackbarHostState,
fullName = assetsUM.fullName,
)
if (assetsUM.isEnsResultLoading) {
@ -125,7 +126,6 @@ private fun AddressBlock(
onCopyClick: (id: Int) -> Unit,
addresses: ImmutableMap<Int, ReceiveAddress>,
snackbarHostState: SnackbarHostState,
fullName: String,
) {
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
@ -133,8 +133,8 @@ private fun AddressBlock(
val resources = context.resources
addresses.entries.toList().fastForEach { entry ->
when (entry.value.type) {
is ReceiveAddress.Type.Default -> {
when (val type = entry.value.type) {
is ReceiveAddress.Type.Primary -> {
key(entry.key) {
AddressItem(
onCopyClick = {
@ -152,8 +152,8 @@ private fun AddressBlock(
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onOpenQrCodeClick(entry.key)
},
fullName = fullName,
address = entry.value.value,
primaryType = type,
)
SpacerH8()
}
@ -184,9 +184,9 @@ private fun AddressBlock(
@Composable
private fun AddressItem(
onOpenQrCodeClick: () -> Unit,
fullName: String,
onCopyClick: () -> Unit,
address: String,
primaryType: ReceiveAddress.Type.Primary,
modifier: Modifier = Modifier,
) {
Card(
@ -195,10 +195,25 @@ private fun AddressItem(
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors.background.action),
onClick = onOpenQrCodeClick,
) {
Column(
modifier = Modifier.padding(vertical = 14.dp, horizontal = 12.dp),
) {
when (primaryType) {
is ReceiveAddress.Type.Primary.Default -> {
primaryType.title?.let {
Text(
text = it.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
SpacerH(12.dp)
}
}
is ReceiveAddress.Type.Primary.Legacy -> Unit
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 14.dp, horizontal = 12.dp),
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
IdentIcon(
@ -212,7 +227,7 @@ private fun AddressItem(
Column(modifier = Modifier.weight(1f)) {
EllipsisText(
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_network_name, fullName),
text = primaryType.displayName.resolveReference(),
ellipsis = TextEllipsis.Middle,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
@ -244,6 +259,7 @@ private fun AddressItem(
}
}
}
}
@Composable
private fun EnsItem(onCopyClick: () -> Unit, address: String, modifier: Modifier = Modifier) {
@ -318,8 +334,9 @@ private fun Preview_TokenReceiveAssetsContent(
private class TokenReceiveAssetsContentProvider : PreviewParameterProvider<ReceiveAssetsUM> {
val address = ReceiveAddress(
value = "0xe5178c7d4d0e861ed2e9414e045b501226b0de8d",
type = ReceiveAddress.Type.Default(
type = ReceiveAddress.Type.Primary.Default(
displayName = stringReference("Etherium address"),
title = null,
),
)
private val config = ReceiveAssetsUM(
@ -338,7 +355,6 @@ private class TokenReceiveAssetsContentProvider : PreviewParameterProvider<Recei
onCopyClick = {},
onOpenQrCodeClick = {},
isEnsResultLoading = true,
fullName = "Etherium",
)
override val values: Sequence<ReceiveAssetsUM>

View file

@ -12,5 +12,4 @@ internal data class ReceiveAssetsUM(
val onCopyClick: (id: Int) -> Unit,
val isEnsResultLoading: Boolean,
val notificationConfigs: ImmutableList<NotificationUM>,
val fullName: String,
)

View file

@ -1089,16 +1089,17 @@ internal class TokenDetailsModel @Inject constructor(
ReceiveAddressModel(
nameService = ReceiveAddressModel.NameService.Ens,
value = ens,
displayName = ens,
),
)
}
addresses.availableAddresses.map { address ->
add(
ReceiveAddressModel(
nameService = ReceiveAddressModel.NameService.Default,
nameService = when (address.type) {
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.NameService.Default
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.NameService.Legacy
},
value = address.value,
displayName = "${cryptoCurrency.name} (${cryptoCurrency.symbol})",
),
)
}

View file

@ -673,16 +673,17 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
ReceiveAddressModel(
nameService = ReceiveAddressModel.NameService.Ens,
value = ens,
displayName = ens,
),
)
}
networkAddress.availableAddresses.map { address ->
add(
ReceiveAddressModel(
nameService = ReceiveAddressModel.NameService.Default,
nameService = when (address.type) {
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.NameService.Default
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.NameService.Legacy
},
value = address.value,
displayName = "${cryptoCurrencyStatus.currency.name} (${cryptoCurrencyStatus.currency.symbol})",
),
)
}