Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-20 20:57:03 +02:00
parent 8eb46c9672
commit bb0aa53982
14 changed files with 294 additions and 27 deletions

View file

@ -40,5 +40,6 @@ sealed class SendDestinationComponentParams {
val blockClickEnableFlow: StateFlow<Boolean>,
val predefinedValues: PredefinedValues,
override val isAllowSelfSend: Boolean = false,
val isAddContactAvailable: Boolean = false,
) : SendDestinationComponentParams()
}

View file

@ -2,6 +2,7 @@ package com.tangem.features.send.api.subcomponents.destination.entity
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.account.AccountIconUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.utils.toBriefAddressFormat
@ -24,6 +25,8 @@ sealed class DestinationTextFieldUM {
val isValuePasted: Boolean,
// if value is human-readable address, this field contains the actual blockchain address
val blockchainAddress: String? = null,
val contactName: String? = null,
val contactIcon: AccountIconUM.CryptoPortfolio? = null,
) : DestinationTextFieldUM() {
val actualAddress: String

View file

@ -17,6 +17,7 @@ dependencies {
implementation(projects.features.nft.api)
implementation(projects.features.swapV2.api)
implementation(projects.features.manageTokens.api)
implementation(projects.features.addressBook.api)
/** Libs */
implementation(projects.libs.crypto)
@ -68,6 +69,7 @@ dependencies {
implementation(projects.domain.swap.models)
implementation(projects.domain.account)
implementation(projects.domain.account.status)
implementation(projects.domain.addressBook)
implementation(projects.domain.transaction)

View file

@ -28,6 +28,9 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.models.account.derivationIndex
import com.tangem.features.addressbook.AddressBookContactsBlockComponent
import com.tangem.features.addressbook.AddressBookFeatureToggles
import com.tangem.features.addressbook.AddressSelectorComponent
import com.tangem.features.send.api.FeeSelectorBlockComponent
import com.tangem.features.send.api.SendComponent
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
@ -52,12 +55,15 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
@Suppress("LargeClass")
@Suppress("LargeClass", "LongParameterList")
internal class DefaultSendComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted private val params: SendComponent.Params,
private val analyticsEventHandler: AnalyticsEventHandler,
private val feeSelectorComponentFactory: FeeSelectorBlockComponent.Factory,
private val contactsBlockFactory: AddressBookContactsBlockComponent.Factory,
private val addressSelectorFactory: AddressSelectorComponent.Factory,
private val addressBookFeatureToggles: AddressBookFeatureToggles,
) : SendComponent, AppComponentContext by appComponentContext {
private val stackNavigation = StackNavigation<CommonSendRoute>()
@ -176,6 +182,9 @@ internal class DefaultSendComponent @AssistedInject constructor(
cryptoCurrency = params.currency,
callback = model,
),
addressBookFeatureToggles = addressBookFeatureToggles,
contactsBlockFactory = contactsBlockFactory,
addressSelectorFactory = addressSelectorFactory,
)
private fun getAmountComponent(factoryContext: AppComponentContext): ComposableContentComponent {
@ -261,6 +270,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
cryptoCurrency = cryptoCurrencyStatus.currency,
blockClickEnableFlow = MutableStateFlow(true),
predefinedValues = model.predefinedValues,
isAddContactAvailable = true,
),
onResult = { },
onClick = {},

View file

@ -1,9 +1,11 @@
package com.tangem.features.send.send.confirm.model
import androidx.compose.runtime.Immutable
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.transaction.error.GetFeeError
import java.math.BigDecimal
@Immutable
data class ConfirmData(
val enteredAmount: BigDecimal?,
val reduceAmountBy: BigDecimal,

View file

@ -25,8 +25,8 @@ import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.features.send.api.subcomponents.destination.SendDestinationBlockComponent
import com.tangem.features.send.common.ui.FeeBlockSuccess
import com.tangem.features.send.common.ui.state.ConfirmUM
import com.tangem.features.send.send.ui.state.SendUM
import com.tangem.features.send.impl.R
import com.tangem.features.send.send.ui.state.SendUM
@Composable
internal fun SendConfirmSuccessContent(sendUM: SendUM, destinationBlockComponent: SendDestinationBlockComponent) {

View file

@ -18,6 +18,9 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.addressbook.AddressBookContactsBlockComponent
import com.tangem.features.addressbook.AddressBookFeatureToggles
import com.tangem.features.addressbook.AddressSelectorComponent
import com.tangem.features.send.api.NFTSendComponent
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams
@ -35,12 +38,16 @@ import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
@Suppress("LongParameterList")
internal class DefaultNFTSendComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted private val params: NFTSendComponent.Params,
private val nftSendConfirmComponentFactory: NFTSendConfirmComponent.Factory,
private val nftSendSuccessComponentFactory: NFTSendSuccessComponent.Factory,
private val analyticsEventHandler: AnalyticsEventHandler,
private val contactsBlockFactory: AddressBookContactsBlockComponent.Factory,
private val addressSelectorFactory: AddressSelectorComponent.Factory,
private val addressBookFeatureToggles: AddressBookFeatureToggles,
) : NFTSendComponent, AppComponentContext by appComponentContext {
private val stackNavigation = StackNavigation<CommonSendRoute>()
@ -145,6 +152,9 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
cryptoCurrency = model.cryptoCurrency,
callback = model,
),
addressBookFeatureToggles = addressBookFeatureToggles,
contactsBlockFactory = contactsBlockFactory,
addressSelectorFactory = addressSelectorFactory,
)
private fun getConfirmComponent(factoryContext: AppComponentContext) = nftSendConfirmComponentFactory.create(

View file

@ -39,12 +39,15 @@ internal class DefaultSendDestinationBlockComponent @AssistedInject constructor(
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
val isClickEnabled by params.blockClickEnableFlow.collectAsStateWithLifecycle()
val isAddContactVisible by model.showAddContact.collectAsStateWithLifecycle()
DestinationBlock(
destinationUM = state,
isClickDisabled = !isClickEnabled,
isEditingDisabled = params.predefinedValues is PredefinedValues.Content.Deeplink,
onClick = onClick,
showAddContact = isAddContactVisible,
onAddContactClick = model::onAddContactClick,
)
}

View file

@ -4,8 +4,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.features.addressbook.AddressBookContactsBlockComponent
import com.tangem.features.addressbook.AddressBookFeatureToggles
import com.tangem.features.addressbook.AddressSelectorComponent
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponent
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
@ -18,18 +26,62 @@ import dagger.assisted.AssistedInject
internal class DefaultSendDestinationComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted private val params: SendDestinationComponentParams.DestinationParams,
addressBookFeatureToggles: AddressBookFeatureToggles,
contactsBlockFactory: AddressBookContactsBlockComponent.Factory,
addressSelectorFactory: AddressSelectorComponent.Factory,
) : SendDestinationComponent, AppComponentContext by appComponentContext {
private val model: SendDestinationModel = getOrCreateModel(params = params)
private val contactsBlock: AddressBookContactsBlockComponent? by lazy {
if (addressBookFeatureToggles.isAddressBookEnabled) {
contactsBlockFactory.create(
context = child("send_contacts_block"),
params = AddressBookContactsBlockComponent.Params(
userWalletId = params.userWalletId,
network = params.cryptoCurrency.network,
queryFlow = model.addressQuery,
onContactClick = model::onContactClick,
onSeeAllClick = model::onSeeAllContactsClick,
),
)
} else {
null
}
}
private val addressSelectorSlot = childSlot(
source = model.addressSelectorNavigation,
serializer = null,
key = "send_address_selector_slot",
handleBackButton = true,
childFactory = { contact, componentContext ->
addressSelectorFactory.create(
context = childByContext(componentContext),
params = AddressSelectorComponent.Params(
contact = contact,
onAddressSelected = model::applySelectedContact,
onDismiss = { model.addressSelectorNavigation.dismiss() },
),
)
},
)
override fun updateState(destinationUM: DestinationUM) = model.updateState(destinationUM)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
val isBalanceHidden by params.isBalanceHidingFlow.collectAsStateWithLifecycle()
val selector by addressSelectorSlot.subscribeAsState()
SendDestinationContent(state = state, clickIntents = model, isBalanceHidden = isBalanceHidden)
SendDestinationContent(
state = state,
clickIntents = model,
isBalanceHidden = isBalanceHidden,
contactsBlock = contactsBlock,
)
selector.child?.instance?.BottomSheet()
}
@AssistedFactory

View file

@ -6,11 +6,12 @@ internal enum class EnterAddressSource {
RecentAddress,
InputField,
MyWallets,
Contact,
;
val isPasted: Boolean
get() = this != InputField
val isAutoNext: Boolean
get() = this == RecentAddress || this == MyWallets
get() = this == RecentAddress || this == MyWallets || this == Contact
}

View file

@ -3,7 +3,12 @@ package com.tangem.features.send.subcomponents.destination.model
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import arrow.core.left
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.entity.AddressBookOpenMode
import com.tangem.common.ui.account.AccountIconUM
import com.tangem.common.ui.navigationButtons.NavigationButton
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -15,8 +20,11 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
import com.tangem.domain.account.status.usecase.GetBackupProblematicWalletForAddressUseCase
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.addressbook.model.Contact
import com.tangem.domain.addressbook.usecase.GetContactsUseCase
import com.tangem.domain.feedback.SendBackupProblemEmailUseCase
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.CryptoPortfolioIcon
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.CryptoCurrencyAddress
@ -34,6 +42,9 @@ import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.addressbook.ContactSelectionListener
import com.tangem.features.addressbook.MatchedContact
import com.tangem.features.addressbook.SelectedContact
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.api.entity.PredefinedValues
@ -41,18 +52,12 @@ import com.tangem.features.send.api.subcomponents.destination.SendDestinationCom
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.common.CommonSendRoute
import com.tangem.features.send.subcomponents.destination.analytics.EnterAddressSource
import com.tangem.features.send.subcomponents.destination.analytics.SendDestinationAnalyticEvents
import com.tangem.features.send.subcomponents.destination.model.transformers.SendDestinationAddressTransformer
import com.tangem.features.send.subcomponents.destination.model.transformers.SendDestinationInitialStateTransformer
import com.tangem.features.send.subcomponents.destination.model.transformers.SendDestinationMemoTransformer
import com.tangem.features.send.subcomponents.destination.model.transformers.SendDestinationPredefinedStateTransformer
import com.tangem.features.send.subcomponents.destination.model.transformers.SendDestinationRecentListTransformer
import com.tangem.features.send.subcomponents.destination.model.transformers.SendDestinationValidationResultTransformer
import com.tangem.features.send.subcomponents.destination.model.transformers.SendDestinationValidationStartedTransformer
import com.tangem.features.send.subcomponents.destination.ui.state.DestinationWalletUM
import com.tangem.features.send.impl.R
import com.tangem.features.send.subcomponents.destination.SendDestinationAlertFactory
import com.tangem.features.send.subcomponents.destination.analytics.EnterAddressSource
import com.tangem.features.send.subcomponents.destination.analytics.SendDestinationAnalyticEvents
import com.tangem.features.send.subcomponents.destination.model.transformers.*
import com.tangem.features.send.subcomponents.destination.ui.state.DestinationWalletUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
@ -88,6 +93,8 @@ internal class SendDestinationModel @Inject constructor(
private val getBackupProblematicWalletForAddressUseCase: GetBackupProblematicWalletForAddressUseCase,
private val sendDestinationAlertFactory: SendDestinationAlertFactory,
private val sendBackupProblemEmailUseCase: SendBackupProblemEmailUseCase,
getContactsUseCase: GetContactsUseCase,
contactSelectionListener: ContactSelectionListener,
) : Model(), SendDestinationClickIntents {
private val params: SendDestinationComponentParams = paramsContainer.require()
@ -98,6 +105,36 @@ internal class SendDestinationModel @Inject constructor(
private val cryptoCurrency = params.cryptoCurrency
private val userWalletId = params.userWalletId
private val contacts: StateFlow<List<Contact>> = getContactsUseCase(query = "", userWalletId = userWalletId)
.stateIn(modelScope, SharingStarted.Eagerly, emptyList())
val addressSelectorNavigation = SlotNavigation<MatchedContact>()
val addressQuery: StateFlow<String> = uiState
.map { (it as? DestinationUM.Content)?.addressTextField?.value.orEmpty() }
.distinctUntilChanged()
.stateIn(modelScope, SharingStarted.Eagerly, "")
/**
* Whether to offer saving the recipient to the address book under the recipient block. Only for the success-screen
* block ([DestinationBlockParams.isAddContactAvailable]) and only when the recipient was NOT a contact and its
* `(address, network)` pair is not already saved in the current wallet's book.
*/
val showAddContact: StateFlow<Boolean> =
if ((params as? DestinationBlockParams)?.isAddContactAvailable == true) {
combine(uiState, contacts) { state, contactList ->
val address = (state as? DestinationUM.Content)?.addressTextField ?: return@combine false
if (address.contactName != null) return@combine false // sent via a contact
val networkId = cryptoCurrency.network.rawId
contactList.none { contact ->
contact.addressEntries.any {
it.networkId.value == networkId && it.address.equals(address.actualAddress, ignoreCase = true)
}
}
}.stateIn(modelScope, SharingStarted.Eagerly, false)
} else {
MutableStateFlow(false)
}
// In "Send with swap" flow, these are addresses in the destination network (not the actual sender addresses).
// Self-send validation must be skipped for them, so use only with params.isAllowSelfSend.
private val senderAddresses = MutableStateFlow<List<CryptoCurrencyAddress>>(emptyList())
@ -110,6 +147,63 @@ internal class SendDestinationModel @Inject constructor(
configDestinationNavigation()
subscribeOnQRScannerResult()
initialState()
resetContactOnEdit()
contactSelectionListener.resultFlow
.onEach(::applySelectedContact)
.launchIn(modelScope)
}
private fun resetContactOnEdit() {
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
params.currentRoute
.filter { it.isEditMode }
.onEach {
val content = uiState.value as? DestinationUM.Content ?: return@onEach
if (content.addressTextField.contactName != null) {
_uiState.update(SendDestinationContactTransformer(contactName = null, contactIcon = null))
_uiState.update(SendDestinationAddressTransformer(address = "", isPasted = false))
validate(address = "", memo = content.memoTextField?.value)
}
}
.launchIn(modelScope)
}
fun onContactClick(contact: MatchedContact) {
val singleEntry = contact.entries.singleOrNull()
if (singleEntry != null) {
applySelectedContact(contact.toSelectedContact(singleEntry))
} else {
addressSelectorNavigation.activate(contact)
}
}
fun onSeeAllContactsClick() {
router.push(
AppRoute.AddressBook(AddressBookOpenMode.ContactSelection(networkId = cryptoCurrency.network.rawId)),
)
}
/** Opens the contact editor pre-filled with the sent address/network to save the recipient (success screen). */
fun onAddContactClick() {
val address = (uiState.value as? DestinationUM.Content)?.addressTextField?.actualAddress ?: return
router.push(
AppRoute.AddressBook(
AddressBookOpenMode.WithContactCreation(address = address, networkId = cryptoCurrency.network.rawId),
),
)
}
fun applySelectedContact(contact: SelectedContact) {
addressSelectorNavigation.dismiss()
_uiState.update(SendDestinationAddressTransformer(address = contact.address, isPasted = true))
_uiState.update(SendDestinationContactTransformer(contactName = contact.name, contactIcon = contact.icon))
val isMemoSupported = (uiState.value as? DestinationUM.Content)?.memoTextField != null
val memo = contact.memo?.takeIf { isMemoSupported && it.isNotBlank() }
if (memo != null) {
_uiState.update(SendDestinationMemoTransformer(memo = memo, isPasted = true))
}
validate(address = contact.address, memo = memo, type = EnterAddressSource.Contact)
}
private fun initialState() {
@ -371,6 +465,7 @@ internal class SendDestinationModel @Inject constructor(
isMemoRequired = isMemoRequired,
),
)
recognizeContact(type = type, isValidAddress = addressValidationResult.isRight(), address = resolvedAddress)
if (type != null) {
autoNextFromRecipient(
type = type,
@ -381,6 +476,34 @@ internal class SendDestinationModel @Inject constructor(
}.saveIn(validationJobHolder)
}
private fun recognizeContact(type: EnterAddressSource?, isValidAddress: Boolean, address: String) {
if (type == null || type == EnterAddressSource.Contact) return
val recognized = if (isValidAddress) findContactByAddress(address) else null
_uiState.update(SendDestinationContactTransformer(recognized?.name, recognized?.icon))
}
private fun findContactByAddress(address: String): RecognizedContact? {
val networkId = cryptoCurrency.network.rawId
contacts.value.forEach { contact ->
val isMatch = contact.addressEntries.any { entry ->
entry.networkId.value == networkId && entry.address.equals(address, ignoreCase = true)
}
if (isMatch) {
return RecognizedContact(
name = contact.name.value,
// TODO([REDACTED_TASK_KEY]): take the color from the domain Contact once the data layer stores it.
icon = AccountIconUM.CryptoPortfolio(
value = CryptoPortfolioIcon.Icon.Letter,
color = CryptoPortfolioIcon.Color.Azure,
),
)
}
}
return null
}
private data class RecognizedContact(val name: String, val icon: AccountIconUM.CryptoPortfolio)
private fun autoNextFromRecipient(type: EnterAddressSource, isValidAddress: Boolean, isValidMemo: Boolean) {
if (type.isAutoNext && isValidAddress && isValidMemo) {
saveResult()

View file

@ -0,0 +1,22 @@
package com.tangem.features.send.subcomponents.destination.model.transformers
import com.tangem.common.ui.account.AccountIconUM
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
import com.tangem.utils.transformer.Transformer
internal class SendDestinationContactTransformer(
private val contactName: String?,
private val contactIcon: AccountIconUM.CryptoPortfolio?,
) : Transformer<DestinationUM> {
override fun transform(prevState: DestinationUM): DestinationUM {
val state = prevState as? DestinationUM.Content ?: return prevState
return state.copy(
addressTextField = state.addressTextField.copy(
contactName = contactName,
contactIcon = contactIcon,
),
)
}
}

View file

@ -15,17 +15,21 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.common.ui.account.AccountIcon
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.account.AccountIconSize
import com.tangem.core.ui.components.icons.identicon.IdentIcon
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.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SendConfirmScreenTestTags
import com.tangem.features.send.impl.R
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationRecipientListUM
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationTextFieldUM
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.impl.R
import kotlinx.collections.immutable.toImmutableList
@Composable
@ -34,6 +38,8 @@ internal fun DestinationBlock(
isClickDisabled: Boolean,
isEditingDisabled: Boolean,
onClick: () -> Unit,
showAddContact: Boolean = false,
onAddContactClick: () -> Unit = {},
) {
if (destinationUM !is DestinationUM.Content) return
@ -50,6 +56,16 @@ internal fun DestinationBlock(
address = destinationUM.addressTextField,
memo = destinationUM.memoTextField,
)
if (showAddContact) {
SecondaryButtonIconStart(
text = stringResourceSafe(com.tangem.core.ui.R.string.address_book_add_contact),
iconResId = com.tangem.core.ui.R.drawable.ic_plus_24,
onClick = onAddContactClick,
modifier = Modifier
.fillMaxWidth()
.padding(top = TangemTheme.dimens.spacing12),
)
}
}
}
@ -68,23 +84,33 @@ private fun AddressWithMemoBlock(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
) {
val contactName = address.contactName
val contactIcon = address.contactIcon
Column(modifier = Modifier.weight(1f)) {
Text(
text = address.value,
text = contactName ?: address.value,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
modifier = Modifier.testTag(SendConfirmScreenTestTags.RECIPIENT_ADDRESS),
)
val blockchainAddress = address.briefBlockchainAddress
if (!blockchainAddress.isNullOrBlank()) {
val recipient = if (contactName != null) address.value else address.briefBlockchainAddress
if (!recipient.isNullOrBlank()) {
Text(
text = blockchainAddress,
text = recipient,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.testTag(SendConfirmScreenTestTags.BLOCKCHAIN_ADDRESS),
)
}
}
if (contactName != null && contactIcon != null) {
AccountIcon(
name = stringReference(contactName),
icon = contactIcon,
size = AccountIconSize.Medium,
modifier = Modifier.testTag(SendConfirmScreenTestTags.RECIPIENT_ADDRESS_ICON),
)
} else {
IdentIcon(
address = address.value,
modifier = Modifier
@ -94,6 +120,7 @@ private fun AddressWithMemoBlock(
.testTag(SendConfirmScreenTestTags.RECIPIENT_ADDRESS_ICON),
)
}
}
if (memo != null && memo.value.isNotBlank()) {
Text(

View file

@ -30,6 +30,7 @@ import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.SendAddressScreenTestTags
import com.tangem.core.ui.utils.GlobalMultipleClickPreventer
import com.tangem.features.addressbook.AddressBookContactsBlockComponent
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationRecipientListUM
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationTextFieldUM
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
@ -47,6 +48,7 @@ internal fun SendDestinationContent(
state: DestinationUM,
clickIntents: SendDestinationClickIntents,
isBalanceHidden: Boolean,
contactsBlock: AddressBookContactsBlockComponent? = null,
) {
if (state !is DestinationUM.Content) return
val recipients = state.recent
@ -115,6 +117,15 @@ internal fun SendDestinationContent(
)
},
)
if (contactsBlock != null && !state.isRecentHidden) {
item(key = "CONTACTS_BLOCK_KEY") {
contactsBlock.Content(
modifier = Modifier
.fillMaxWidth()
.padding(top = 20.dp),
)
}
}
item("SPACER_KEY") {
SpacerH(16.dp)
}