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

@ -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
data class Default(
val displayName: TextReference,
) : Type
sealed interface Primary : Type {
val displayName: TextReference
data class Default(
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,52 +195,68 @@ private fun AddressItem(
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors.background.action),
onClick = onOpenQrCodeClick,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 14.dp, horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically,
Column(
modifier = Modifier.padding(vertical = 14.dp, horizontal = 12.dp),
) {
IdentIcon(
address = address,
modifier = Modifier
.size(size = 36.dp)
.clip(shape = RoundedCornerShape(18.dp)),
)
SpacerW12()
Column(modifier = Modifier.weight(1f)) {
EllipsisText(
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_network_name, fullName),
ellipsis = TextEllipsis.Middle,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
)
EllipsisText(
text = address,
ellipsis = TextEllipsis.Middle,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
)
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
}
SpacerW12()
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
IdentIcon(
address = address,
modifier = Modifier
.size(size = 36.dp)
.clip(shape = RoundedCornerShape(18.dp)),
)
TangemIconButton(
modifier = Modifier.size(TangemTheme.dimens.size28),
iconRes = R.drawable.ic_qrcode_new_24,
onClick = onOpenQrCodeClick,
)
SpacerW12()
SpacerW8()
Column(modifier = Modifier.weight(1f)) {
EllipsisText(
text = primaryType.displayName.resolveReference(),
ellipsis = TextEllipsis.Middle,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
)
TangemIconButton(
modifier = Modifier.size(TangemTheme.dimens.size28),
iconRes = R.drawable.ic_copy_new_24,
onClick = onCopyClick,
)
EllipsisText(
text = address,
ellipsis = TextEllipsis.Middle,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
)
}
SpacerW12()
TangemIconButton(
modifier = Modifier.size(TangemTheme.dimens.size28),
iconRes = R.drawable.ic_qrcode_new_24,
onClick = onOpenQrCodeClick,
)
SpacerW8()
TangemIconButton(
modifier = Modifier.size(TangemTheme.dimens.size28),
iconRes = R.drawable.ic_copy_new_24,
onClick = onCopyClick,
)
}
}
}
}
@ -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,
)