Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-18 12:36:46 +05:00
parent 26201c5c0b
commit 0be1358760
39 changed files with 581 additions and 124 deletions

View file

@ -25,4 +25,17 @@ internal class SendDestinationAlertFactory @Inject constructor(
),
)
}
fun showRecipientBackupErrorAlert(onContactSupport: () -> Unit) {
messageSender.send(
DialogMessage(
title = resourceReference(id = R.string.warning_backup_error_add_funds_title),
message = resourceReference(id = R.string.warning_backup_error_add_funds_message),
firstAction = EventMessageAction(
title = resourceReference(R.string.common_contact_support),
onClick = onContactSupport,
),
),
)
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.features.send.subcomponents.destination.model
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import arrow.core.left
import com.tangem.common.routing.AppRoute
import com.tangem.common.ui.navigationButtons.NavigationButton
import com.tangem.common.ui.navigationButtons.NavigationUM
@ -12,12 +13,15 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
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.feedback.SendBackupProblemEmailUseCase
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.CryptoCurrencyAddress
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
@ -48,6 +52,7 @@ import com.tangem.features.send.subcomponents.destination.model.transformers.Sen
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.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
@ -58,6 +63,7 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
@Stable
@ -79,6 +85,9 @@ internal class SendDestinationModel @Inject constructor(
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
private val getBackupProblematicWalletForAddressUseCase: GetBackupProblematicWalletForAddressUseCase,
private val sendDestinationAlertFactory: SendDestinationAlertFactory,
private val sendBackupProblemEmailUseCase: SendBackupProblemEmailUseCase,
) : Model(), SendDestinationClickIntents {
private val params: SendDestinationComponentParams = paramsContainer.require()
@ -95,6 +104,8 @@ internal class SendDestinationModel @Inject constructor(
private val validationJobHolder = JobHolder()
private val backupProblematicWalletCache = AtomicReference<Pair<String, UserWalletId?>?>(null)
init {
configDestinationNavigation()
subscribeOnQRScannerResult()
@ -288,17 +299,41 @@ internal class SendDestinationModel @Inject constructor(
}
}
private suspend fun resolveBackupProblematicWallet(address: String): UserWalletId? {
backupProblematicWalletCache.get()?.let { if (it.first == address) return it.second }
return getBackupProblematicWalletForAddressUseCase(address)
.also { backupProblematicWalletCache.set(address to it) }
}
private fun contactBackupSupport(userWalletId: UserWalletId) {
modelScope.launch { sendBackupProblemEmailUseCase(userWalletId) }
}
private fun validate(address: String, memo: String?, type: EnterAddressSource? = null) {
modelScope.launch {
_uiState.update(SendDestinationValidationStartedTransformer)
val addressValidationResult = validateWalletAddressUseCase(
var addressValidationResult = validateWalletAddressUseCase(
userWalletId = userWalletId,
network = cryptoCurrency.network,
address = address,
senderAddresses = senderAddresses.value,
allowSelfSend = params.isAllowSelfSend,
)
if (addressValidationResult.isRight()) {
val problematicWalletId = resolveBackupProblematicWallet(address)
if (problematicWalletId != null) {
addressValidationResult = AddressValidation.Error.RecipientWalletBackupError.left()
if (type != null) {
sendDestinationAlertFactory.showRecipientBackupErrorAlert(
onContactSupport = { contactBackupSupport(problematicWalletId) },
)
}
}
}
val memoValidationResult = validateWalletMemoUseCase(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,

View file

@ -24,14 +24,7 @@ internal class SendDestinationValidationResultTransformer(
val isValidAddress = addressValidationResult.isRight()
val isValidMemo = shouldDisableMemo || memoValidationResult.isRight()
val addressErrorText = addressValidationResult.mapLeft { error ->
when (error) {
is AddressValidation.Error.DataError,
AddressValidation.Error.InvalidAddress,
-> R.string.send_recipient_address_error
AddressValidation.Error.AddressInWallet -> R.string.send_error_address_same_as_wallet
}
}.leftOrNull()
val addressErrorText = resolveAddressErrorText()
val blockchainAddress =
(addressValidationResult.getOrNull() as? AddressValidation.Success.ValidNamedAddress)?.blockchainAddress
@ -61,6 +54,16 @@ internal class SendDestinationValidationResultTransformer(
)
}
private fun resolveAddressErrorText(): Int? = addressValidationResult.mapLeft { error ->
when (error) {
is AddressValidation.Error.DataError,
AddressValidation.Error.InvalidAddress,
-> R.string.send_recipient_address_error
AddressValidation.Error.AddressInWallet -> R.string.send_error_address_same_as_wallet
AddressValidation.Error.RecipientWalletBackupError -> R.string.warning_backup_error_add_funds_message
}
}.leftOrNull()
private fun buildMemoField(
memoField: DestinationTextFieldUM.RecipientMemo?,
isValidMemo: Boolean,