Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-22 14:11:36 +02:00
parent bb0aa53982
commit e65bf8538b
5 changed files with 43 additions and 55 deletions

View file

@ -28,25 +28,22 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.models.account.derivationIndex 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.FeeSelectorBlockComponent
import com.tangem.features.send.api.SendComponent import com.tangem.features.send.api.SendComponent
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
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.SendDestinationComponentParams
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.common.CommonSendRoute import com.tangem.features.send.common.CommonSendRoute
import com.tangem.features.send.common.ui.SendContent import com.tangem.features.send.common.ui.SendContent
import com.tangem.features.send.common.ui.state.ConfirmUM import com.tangem.features.send.common.ui.state.ConfirmUM
import com.tangem.features.send.impl.R
import com.tangem.features.send.send.confirm.SendConfirmComponent import com.tangem.features.send.send.confirm.SendConfirmComponent
import com.tangem.features.send.send.model.SendModel import com.tangem.features.send.send.model.SendModel
import com.tangem.features.send.send.success.SendConfirmSuccessComponent import com.tangem.features.send.send.success.SendConfirmSuccessComponent
import com.tangem.features.send.subcomponents.amount.SendAmountComponent import com.tangem.features.send.subcomponents.amount.SendAmountComponent
import com.tangem.features.send.subcomponents.amount.SendAmountComponentParams import com.tangem.features.send.subcomponents.amount.SendAmountComponentParams
import com.tangem.features.send.subcomponents.destination.DefaultSendDestinationBlockComponent import com.tangem.features.send.subcomponents.destination.DefaultSendDestinationBlockComponent
import com.tangem.features.send.subcomponents.destination.DefaultSendDestinationComponent
import com.tangem.features.send.impl.R
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
@ -55,15 +52,13 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Suppress("LargeClass", "LongParameterList") @Suppress("LargeClass")
internal class DefaultSendComponent @AssistedInject constructor( internal class DefaultSendComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext, @Assisted appComponentContext: AppComponentContext,
@Assisted private val params: SendComponent.Params, @Assisted private val params: SendComponent.Params,
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
private val feeSelectorComponentFactory: FeeSelectorBlockComponent.Factory, private val feeSelectorComponentFactory: FeeSelectorBlockComponent.Factory,
private val contactsBlockFactory: AddressBookContactsBlockComponent.Factory, private val sendDestinationComponentFactory: SendDestinationComponent.Factory,
private val addressSelectorFactory: AddressSelectorComponent.Factory,
private val addressBookFeatureToggles: AddressBookFeatureToggles,
) : SendComponent, AppComponentContext by appComponentContext { ) : SendComponent, AppComponentContext by appComponentContext {
private val stackNavigation = StackNavigation<CommonSendRoute>() private val stackNavigation = StackNavigation<CommonSendRoute>()
@ -128,7 +123,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
) )
activeComponent.updateState(model.uiState.value.amountUM) activeComponent.updateState(model.uiState.value.amountUM)
} }
is DefaultSendDestinationComponent -> { is SendDestinationComponent -> {
analyticsEventHandler.send( analyticsEventHandler.send(
CommonSendAnalyticEvents.AddressScreenOpened( CommonSendAnalyticEvents.AddressScreenOpened(
categoryName = model.analyticCategoryName, categoryName = model.analyticCategoryName,
@ -168,9 +163,9 @@ internal class DefaultSendComponent @AssistedInject constructor(
is CommonSendRoute.ConfirmSuccess -> getConfirmSuccessComponent(factoryContext) is CommonSendRoute.ConfirmSuccess -> getConfirmSuccessComponent(factoryContext)
} }
private fun getDestinationComponent(factoryContext: AppComponentContext): DefaultSendDestinationComponent = private fun getDestinationComponent(factoryContext: AppComponentContext): SendDestinationComponent =
DefaultSendDestinationComponent( sendDestinationComponentFactory.create(
appComponentContext = factoryContext, context = factoryContext,
params = SendDestinationComponentParams.DestinationParams( params = SendDestinationComponentParams.DestinationParams(
state = model.uiState.value.destinationUM, state = model.uiState.value.destinationUM,
currentRoute = model.currentRoute.filterIsInstance<CommonSendRoute.Destination>(), currentRoute = model.currentRoute.filterIsInstance<CommonSendRoute.Destination>(),
@ -182,9 +177,6 @@ internal class DefaultSendComponent @AssistedInject constructor(
cryptoCurrency = params.currency, cryptoCurrency = params.currency,
callback = model, callback = model,
), ),
addressBookFeatureToggles = addressBookFeatureToggles,
contactsBlockFactory = contactsBlockFactory,
addressSelectorFactory = addressSelectorFactory,
) )
private fun getAmountComponent(factoryContext: AppComponentContext): ComposableContentComponent { private fun getAmountComponent(factoryContext: AppComponentContext): ComposableContentComponent {

View file

@ -18,36 +18,30 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.decompose.navigation.inner.InnerRouter import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference 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.NFTSendComponent
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
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.SendDestinationComponentParams
import com.tangem.features.send.common.CommonSendRoute import com.tangem.features.send.common.CommonSendRoute
import com.tangem.features.send.common.ui.SendContent import com.tangem.features.send.common.ui.SendContent
import com.tangem.features.send.common.ui.state.ConfirmUM import com.tangem.features.send.common.ui.state.ConfirmUM
import com.tangem.features.send.impl.R
import com.tangem.features.send.sendnft.confirm.NFTSendConfirmComponent import com.tangem.features.send.sendnft.confirm.NFTSendConfirmComponent
import com.tangem.features.send.sendnft.model.NFTSendModel import com.tangem.features.send.sendnft.model.NFTSendModel
import com.tangem.features.send.sendnft.success.NFTSendSuccessComponent import com.tangem.features.send.sendnft.success.NFTSendSuccessComponent
import com.tangem.features.send.subcomponents.destination.DefaultSendDestinationComponent
import com.tangem.features.send.impl.R
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Suppress("LongParameterList")
internal class DefaultNFTSendComponent @AssistedInject constructor( internal class DefaultNFTSendComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext, @Assisted appComponentContext: AppComponentContext,
@Assisted private val params: NFTSendComponent.Params, @Assisted private val params: NFTSendComponent.Params,
private val nftSendConfirmComponentFactory: NFTSendConfirmComponent.Factory, private val nftSendConfirmComponentFactory: NFTSendConfirmComponent.Factory,
private val nftSendSuccessComponentFactory: NFTSendSuccessComponent.Factory, private val nftSendSuccessComponentFactory: NFTSendSuccessComponent.Factory,
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
private val contactsBlockFactory: AddressBookContactsBlockComponent.Factory, private val sendDestinationComponentFactory: SendDestinationComponent.Factory,
private val addressSelectorFactory: AddressSelectorComponent.Factory,
private val addressBookFeatureToggles: AddressBookFeatureToggles,
) : NFTSendComponent, AppComponentContext by appComponentContext { ) : NFTSendComponent, AppComponentContext by appComponentContext {
private val stackNavigation = StackNavigation<CommonSendRoute>() private val stackNavigation = StackNavigation<CommonSendRoute>()
@ -103,7 +97,7 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
activeComponent.updateState(model.uiState.value) activeComponent.updateState(model.uiState.value)
} }
} }
is DefaultSendDestinationComponent -> { is SendDestinationComponent -> {
analyticsEventHandler.send( analyticsEventHandler.send(
CommonSendAnalyticEvents.AddressScreenOpened( CommonSendAnalyticEvents.AddressScreenOpened(
categoryName = analyticsCategoryName, categoryName = analyticsCategoryName,
@ -138,9 +132,9 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
else -> getStubComponent() else -> getStubComponent()
} }
private fun getDestinationComponent(factoryContext: AppComponentContext): DefaultSendDestinationComponent = private fun getDestinationComponent(factoryContext: AppComponentContext): SendDestinationComponent =
DefaultSendDestinationComponent( sendDestinationComponentFactory.create(
appComponentContext = factoryContext, context = factoryContext,
params = SendDestinationComponentParams.DestinationParams( params = SendDestinationComponentParams.DestinationParams(
state = model.uiState.value.destinationUM, state = model.uiState.value.destinationUM,
currentRoute = model.currentRouteFlow.filterIsInstance<CommonSendRoute.Destination>(), currentRoute = model.currentRouteFlow.filterIsInstance<CommonSendRoute.Destination>(),
@ -152,9 +146,6 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
cryptoCurrency = model.cryptoCurrency, cryptoCurrency = model.cryptoCurrency,
callback = model, callback = model,
), ),
addressBookFeatureToggles = addressBookFeatureToggles,
contactsBlockFactory = contactsBlockFactory,
addressSelectorFactory = addressSelectorFactory,
) )
private fun getConfirmComponent(factoryContext: AppComponentContext) = nftSendConfirmComponentFactory.create( private fun getConfirmComponent(factoryContext: AppComponentContext) = nftSendConfirmComponentFactory.create(

View file

@ -8,7 +8,6 @@ import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.entity.AddressBookOpenMode 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.NavigationButton
import com.tangem.common.ui.navigationButtons.NavigationUM import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -24,7 +23,6 @@ import com.tangem.domain.addressbook.model.Contact
import com.tangem.domain.addressbook.usecase.GetContactsUseCase import com.tangem.domain.addressbook.usecase.GetContactsUseCase
import com.tangem.domain.feedback.SendBackupProblemEmailUseCase import com.tangem.domain.feedback.SendBackupProblemEmailUseCase
import com.tangem.domain.models.account.AccountStatus 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.account.PaymentAccountStatusValue
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.CryptoCurrencyAddress import com.tangem.domain.models.network.CryptoCurrencyAddress
@ -160,9 +158,7 @@ internal class SendDestinationModel @Inject constructor(
.onEach { .onEach {
val content = uiState.value as? DestinationUM.Content ?: return@onEach val content = uiState.value as? DestinationUM.Content ?: return@onEach
if (content.addressTextField.contactName != null) { if (content.addressTextField.contactName != null) {
_uiState.update(SendDestinationContactTransformer(contactName = null, contactIcon = null)) _uiState.update(SendDestinationContactTransformer(contact = null))
_uiState.update(SendDestinationAddressTransformer(address = "", isPasted = false))
validate(address = "", memo = content.memoTextField?.value)
} }
} }
.launchIn(modelScope) .launchIn(modelScope)
@ -478,32 +474,19 @@ internal class SendDestinationModel @Inject constructor(
private fun recognizeContact(type: EnterAddressSource?, isValidAddress: Boolean, address: String) { private fun recognizeContact(type: EnterAddressSource?, isValidAddress: Boolean, address: String) {
if (type == null || type == EnterAddressSource.Contact) return if (type == null || type == EnterAddressSource.Contact) return
val recognized = if (isValidAddress) findContactByAddress(address) else null val contact = if (isValidAddress) findContactByAddress(address) else null
_uiState.update(SendDestinationContactTransformer(recognized?.name, recognized?.icon)) _uiState.update(SendDestinationContactTransformer(contact))
} }
private fun findContactByAddress(address: String): RecognizedContact? { private fun findContactByAddress(address: String): Contact? {
val networkId = cryptoCurrency.network.rawId val networkId = cryptoCurrency.network.rawId
contacts.value.forEach { contact -> return contacts.value.firstOrNull { contact ->
val isMatch = contact.addressEntries.any { entry -> contact.addressEntries.any { entry ->
entry.networkId.value == networkId && entry.address.equals(address, ignoreCase = true) 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) { private fun autoNextFromRecipient(type: EnterAddressSource, isValidAddress: Boolean, isValidMemo: Boolean) {
if (type.isAutoNext && isValidAddress && isValidMemo) { if (type.isAutoNext && isValidAddress && isValidMemo) {
saveResult() saveResult()

View file

@ -0,0 +1,15 @@
package com.tangem.features.send.subcomponents.destination.model.converter
import com.tangem.common.ui.account.AccountIconUM
import com.tangem.domain.addressbook.model.Contact
import com.tangem.domain.models.account.CryptoPortfolioIcon
import com.tangem.utils.converter.Converter
internal object ContactIconConverter : Converter<Contact, AccountIconUM.CryptoPortfolio> {
override fun convert(value: Contact): AccountIconUM.CryptoPortfolio = AccountIconUM.CryptoPortfolio(
value = CryptoPortfolioIcon.Icon.Letter,
color = CryptoPortfolioIcon.Color.entries.firstOrNull { it.name == value.iconColor }
?: CryptoPortfolioIcon.Color.Azure,
)
}

View file

@ -1,7 +1,9 @@
package com.tangem.features.send.subcomponents.destination.model.transformers package com.tangem.features.send.subcomponents.destination.model.transformers
import com.tangem.common.ui.account.AccountIconUM import com.tangem.common.ui.account.AccountIconUM
import com.tangem.domain.addressbook.model.Contact
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.subcomponents.destination.model.converter.ContactIconConverter
import com.tangem.utils.transformer.Transformer import com.tangem.utils.transformer.Transformer
internal class SendDestinationContactTransformer( internal class SendDestinationContactTransformer(
@ -9,6 +11,11 @@ internal class SendDestinationContactTransformer(
private val contactIcon: AccountIconUM.CryptoPortfolio?, private val contactIcon: AccountIconUM.CryptoPortfolio?,
) : Transformer<DestinationUM> { ) : Transformer<DestinationUM> {
constructor(contact: Contact?) : this(
contactName = contact?.name?.value,
contactIcon = contact?.let(ContactIconConverter::convert),
)
override fun transform(prevState: DestinationUM): DestinationUM { override fun transform(prevState: DestinationUM): DestinationUM {
val state = prevState as? DestinationUM.Content ?: return prevState val state = prevState as? DestinationUM.Content ?: return prevState