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.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
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
import com.tangem.features.send.common.CommonSendRoute
import com.tangem.features.send.common.ui.SendContent
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.model.SendModel
import com.tangem.features.send.send.success.SendConfirmSuccessComponent
import com.tangem.features.send.subcomponents.amount.SendAmountComponent
import com.tangem.features.send.subcomponents.amount.SendAmountComponentParams
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.AssistedFactory
import dagger.assisted.AssistedInject
@ -55,15 +52,13 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
@Suppress("LargeClass", "LongParameterList")
@Suppress("LargeClass")
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,
private val sendDestinationComponentFactory: SendDestinationComponent.Factory,
) : SendComponent, AppComponentContext by appComponentContext {
private val stackNavigation = StackNavigation<CommonSendRoute>()
@ -128,7 +123,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
)
activeComponent.updateState(model.uiState.value.amountUM)
}
is DefaultSendDestinationComponent -> {
is SendDestinationComponent -> {
analyticsEventHandler.send(
CommonSendAnalyticEvents.AddressScreenOpened(
categoryName = model.analyticCategoryName,
@ -168,9 +163,9 @@ internal class DefaultSendComponent @AssistedInject constructor(
is CommonSendRoute.ConfirmSuccess -> getConfirmSuccessComponent(factoryContext)
}
private fun getDestinationComponent(factoryContext: AppComponentContext): DefaultSendDestinationComponent =
DefaultSendDestinationComponent(
appComponentContext = factoryContext,
private fun getDestinationComponent(factoryContext: AppComponentContext): SendDestinationComponent =
sendDestinationComponentFactory.create(
context = factoryContext,
params = SendDestinationComponentParams.DestinationParams(
state = model.uiState.value.destinationUM,
currentRoute = model.currentRoute.filterIsInstance<CommonSendRoute.Destination>(),
@ -182,9 +177,6 @@ internal class DefaultSendComponent @AssistedInject constructor(
cryptoCurrency = params.currency,
callback = model,
),
addressBookFeatureToggles = addressBookFeatureToggles,
contactsBlockFactory = contactsBlockFactory,
addressSelectorFactory = addressSelectorFactory,
)
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.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.SendDestinationComponent
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams
import com.tangem.features.send.common.CommonSendRoute
import com.tangem.features.send.common.ui.SendContent
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.model.NFTSendModel
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.AssistedFactory
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,
private val sendDestinationComponentFactory: SendDestinationComponent.Factory,
) : NFTSendComponent, AppComponentContext by appComponentContext {
private val stackNavigation = StackNavigation<CommonSendRoute>()
@ -103,7 +97,7 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
activeComponent.updateState(model.uiState.value)
}
}
is DefaultSendDestinationComponent -> {
is SendDestinationComponent -> {
analyticsEventHandler.send(
CommonSendAnalyticEvents.AddressScreenOpened(
categoryName = analyticsCategoryName,
@ -138,9 +132,9 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
else -> getStubComponent()
}
private fun getDestinationComponent(factoryContext: AppComponentContext): DefaultSendDestinationComponent =
DefaultSendDestinationComponent(
appComponentContext = factoryContext,
private fun getDestinationComponent(factoryContext: AppComponentContext): SendDestinationComponent =
sendDestinationComponentFactory.create(
context = factoryContext,
params = SendDestinationComponentParams.DestinationParams(
state = model.uiState.value.destinationUM,
currentRoute = model.currentRouteFlow.filterIsInstance<CommonSendRoute.Destination>(),
@ -152,9 +146,6 @@ 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

@ -8,7 +8,6 @@ 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
@ -24,7 +23,6 @@ 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
@ -160,9 +158,7 @@ internal class SendDestinationModel @Inject constructor(
.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)
_uiState.update(SendDestinationContactTransformer(contact = null))
}
}
.launchIn(modelScope)
@ -478,32 +474,19 @@ internal class SendDestinationModel @Inject constructor(
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))
val contact = if (isValidAddress) findContactByAddress(address) else null
_uiState.update(SendDestinationContactTransformer(contact))
}
private fun findContactByAddress(address: String): RecognizedContact? {
private fun findContactByAddress(address: String): Contact? {
val networkId = cryptoCurrency.network.rawId
contacts.value.forEach { contact ->
val isMatch = contact.addressEntries.any { entry ->
return contacts.value.firstOrNull { contact ->
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,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
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.subcomponents.destination.model.converter.ContactIconConverter
import com.tangem.utils.transformer.Transformer
internal class SendDestinationContactTransformer(
@ -9,6 +11,11 @@ internal class SendDestinationContactTransformer(
private val contactIcon: AccountIconUM.CryptoPortfolio?,
) : Transformer<DestinationUM> {
constructor(contact: Contact?) : this(
contactName = contact?.name?.value,
contactIcon = contact?.let(ContactIconConverter::convert),
)
override fun transform(prevState: DestinationUM): DestinationUM {
val state = prevState as? DestinationUM.Content ?: return prevState