Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-21 13:28:00 +05:00
parent 4ea8243da3
commit f009a78876
12 changed files with 104 additions and 52 deletions

View file

@ -2,7 +2,9 @@ package com.tangem.features.send.v2.api.subcomponents.destination.entity
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
@ -11,6 +13,7 @@ data class DestinationRecipientListUM(
val id: String,
val title: TextReference = TextReference.Companion.EMPTY,
val subtitle: TextReference = TextReference.Companion.EMPTY,
val accountTitleUM: AccountTitleUM.Account? = null,
val timestamp: TextReference? = null,
val subtitleEndOffset: Int = 0,
@DrawableRes val subtitleIconRes: Int? = null,
@ -18,5 +21,6 @@ data class DestinationRecipientListUM(
val isLoading: Boolean = false,
val userWalletId: UserWalletId? = null,
val network: Network? = null,
val accountId: AccountId? = null,
val address: String? = null,
)

View file

@ -1,7 +1,6 @@
package com.tangem.features.send.v2.api.subcomponents.destination.entity
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.account.AccountTitleUM
import kotlinx.collections.immutable.ImmutableList
@Immutable
@ -16,10 +15,10 @@ sealed class DestinationUM {
val recent: ImmutableList<DestinationRecipientListUM>,
val wallets: ImmutableList<DestinationRecipientListUM>,
val networkName: String,
val accountTitleUM: AccountTitleUM?,
val isValidating: Boolean = false,
val isInitialized: Boolean = false,
val isRecentHidden: Boolean,
val isAccountsMode: Boolean? = null,
) : DestinationUM()
data class Empty(

View file

@ -3,7 +3,6 @@ package com.tangem.features.send.v2.send.confirm.model
import android.os.SystemClock
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import arrow.core.left
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.TransactionData
import com.tangem.common.routing.AppRouter
@ -22,6 +21,7 @@ import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
@ -33,6 +33,7 @@ import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
import com.tangem.domain.settings.NeverShowTapHelpUseCase
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
@ -108,6 +109,8 @@ internal class SendConfirmModel @Inject constructor(
private val sendAmountReduceTrigger: SendAmountReduceTrigger,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
private val currenciesRepository: CurrenciesRepository,
sendBalanceUpdaterFactory: SendBalanceUpdater.Factory,
) : Model(), SendConfirmClickIntents, FeeSelectorModelCallback, SendNotificationsComponent.ModelCallback {
@ -433,10 +436,10 @@ internal class SendConfirmModel @Inject constructor(
modelScope.launch(dispatchers.default) {
if (accountsFeatureToggles.isFeatureEnabled) {
// val tokenToAdd = currenciesRepository.createTokenCurrency(cryptoCurrency, network)
// saveCryptoCurrenciesUseCase(accountId = accountId, add = tokenToAdd)
// TODO account
IllegalStateException("Not implemented yet").left()
val accountId = receivingUserWallet.accountId ?: return@launch
val tokenToAdd = currenciesRepository.createTokenCurrency(cryptoCurrency, network)
manageCryptoCurrenciesUseCase(accountId = accountId, add = tokenToAdd)
} else {
withContext(NonCancellable) {
addCryptoCurrenciesUseCase(
@ -445,8 +448,7 @@ internal class SendConfirmModel @Inject constructor(
network = network,
)
}
}
.onLeft(Timber::e)
}.onLeft(Timber::e)
}
}

View file

@ -180,8 +180,8 @@ internal class SendAmountModel @Inject constructor(
private fun initialState(
cryptoCurrencyStatus: CryptoCurrencyStatus,
@Suppress("UnusedParameter") account: Account.CryptoPortfolio?,
@Suppress("UnusedParameter") isAccountsMode: Boolean,
account: Account.CryptoPortfolio?,
isAccountsMode: Boolean,
) {
if (uiState.value is AmountState.Empty && userWallet != null) {
val isOnlyOneWallet = getWalletsUseCase.invokeSync().size == 1

View file

@ -12,8 +12,9 @@ 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.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.network.CryptoCurrencyAddress
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isLocked
@ -68,9 +69,9 @@ internal class SendDestinationModel @Inject constructor(
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
private val parseQrCodeUseCase: ParseQrCodeUseCase,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
) : Model(), SendDestinationClickIntents {
private val params: SendDestinationComponentParams = paramsContainer.require()
@ -189,8 +190,14 @@ internal class SendDestinationModel @Inject constructor(
private fun getWalletsAndRecent() {
combine(
flow = getWalletsUseCase().conflate().map {
waitForDelay(RECENT_LOAD_DELAY) { it.toAvailableWallets() }
flow = if (accountsFeatureToggles.isFeatureEnabled) {
getAddedAddresses()
} else {
getWalletsUseCase().conflate().map {
waitForDelay(RECENT_LOAD_DELAY) {
it.toAvailableWallets()
}
}
},
flow2 = getFixedTxHistoryItemsUseCase(
userWalletId = userWalletId,
@ -200,12 +207,7 @@ internal class SendDestinationModel @Inject constructor(
waitForDelay(RECENT_LOAD_DELAY) { it }
}.conflate(),
flow3 = isAccountsModeEnabledUseCase().distinctUntilChanged(),
flow4 = if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCurrencyStatusUseCase(userWalletId, cryptoCurrency).distinctUntilChanged()
} else {
flowOf(null)
},
) { destinationWalletList, txHistoryList, isAccountsMode, accountCurrencyStatus ->
) { destinationWalletList, txHistoryList, isAccountsMode ->
val isSelfSendAvailable = isSelfSendAvailableUseCase.invokeSync(
userWalletId = userWalletId,
network = cryptoCurrency.network,
@ -217,7 +219,6 @@ internal class SendDestinationModel @Inject constructor(
isSelfSendAvailable = isSelfSendAvailable,
destinationWalletList = destinationWalletList,
txHistoryList = txHistoryList,
account = accountCurrencyStatus?.account,
isAccountsMode = isAccountsMode,
),
)
@ -266,6 +267,46 @@ internal class SendDestinationModel @Inject constructor(
}
}
private fun getAddedAddresses(): Flow<List<DestinationWalletUM>> {
return combine(
flow = getWalletsUseCase().conflate(),
flow2 = multiAccountStatusListSupplier(Unit).conflate(),
) { wallets, accountList ->
val cryptoCurrencyNetwork = cryptoCurrency.network
coroutineScope {
accountList.mapNotNull { accountStatusList ->
val wallet =
wallets.filterNot { it.isLocked }.firstOrNull { it.walletId == accountStatusList.userWalletId }
?: return@mapNotNull null
async {
accountStatusList.accountStatuses.map { accountStatus ->
async {
accountStatus.flattenCurrencies()
.filter { it.currency.network.rawId == cryptoCurrencyNetwork.rawId }
.mapNotNull { cryptoCurrencyStatus ->
val address = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
?: return@mapNotNull null
async {
DestinationWalletUM(
name = wallet.name,
address = address,
cryptoCurrency = cryptoCurrencyStatus.currency,
userWalletId = wallet.walletId,
account = accountStatus.account as? Account.CryptoPortfolio,
)
}
}.awaitAll()
}
}.awaitAll().flatten()
}
}.awaitAll().flatten()
}
}.flowOn(dispatchers.default)
}
private fun validate(address: String, memo: String?, type: EnterAddressSource? = null) {
modelScope.launch {
_uiState.update(SendDestinationValidationStartedTransformer)
@ -291,7 +332,12 @@ internal class SendDestinationModel @Inject constructor(
),
)
}
_uiState.update(SendDestinationValidationResultTransformer(addressValidationResult, memoValidationResult))
_uiState.update(
SendDestinationValidationResultTransformer(
addressValidationResult,
memoValidationResult,
),
)
autoNextFromRecipient(type, addressValidationResult.isRight(), memoValidationResult.isRight())
}.saveIn(validationJobHolder)
}

View file

@ -1,12 +1,15 @@
package com.tangem.features.send.v2.subcomponents.destination.model.converters
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.common.ui.account.toUM
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationRecipientListUM
import com.tangem.features.send.v2.subcomponents.destination.model.transformers.WALLET_DEFAULT_COUNT
import com.tangem.features.send.v2.subcomponents.destination.model.transformers.WALLET_KEY_TAG
import com.tangem.features.send.v2.subcomponents.destination.model.transformers.emptyListState
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationRecipientListUM
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationWalletUM
import com.tangem.utils.StringsSigns
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
@ -14,6 +17,7 @@ import kotlinx.collections.immutable.toPersistentList
internal class SendRecipientWalletListConverter(
private val senderAddress: String?,
private val isSelfSendAvailable: Boolean,
private val isAccountsMode: Boolean,
) :
Converter<List<DestinationWalletUM?>, PersistentList<DestinationRecipientListUM>> {
override fun convert(value: List<DestinationWalletUM?>): PersistentList<DestinationRecipientListUM> {
@ -46,13 +50,24 @@ internal class SendRecipientWalletListConverter(
wallet.name
}
val account = wallet.account
DestinationRecipientListUM(
id = "${WALLET_KEY_TAG}${walletsCounter++}",
title = stringReference(wallet.address),
subtitle = stringReference(name),
accountTitleUM = if (account != null && isAccountsMode) {
AccountTitleUM.Account(
name = account.accountName.toUM().value,
icon = account.icon.toUM(),
prefixText = stringReference(StringsSigns.DOT),
)
} else {
null
},
address = wallet.address,
userWalletId = wallet.userWalletId,
network = wallet.cryptoCurrency.network,
accountId = account?.accountId,
)
}
}

View file

@ -54,12 +54,12 @@ internal class SendDestinationInitialStateTransformer(
isValuePasted = false,
)
},
accountTitleUM = null,
wallets = loadingListState(WALLET_KEY_TAG, WALLET_DEFAULT_COUNT),
recent = loadingListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT),
networkName = cryptoCurrency.network.name,
isValidating = false,
isRecentHidden = false,
isAccountsMode = null,
)
}
}

View file

@ -1,17 +1,11 @@
package com.tangem.features.send.v2.subcomponents.destination.model.transformers
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.common.ui.account.toUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.TxInfo
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.v2.subcomponents.destination.model.converters.SendRecipientHistoryListConverter
import com.tangem.features.send.v2.subcomponents.destination.model.converters.SendRecipientWalletListConverter
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationWalletUM
import com.tangem.utils.StringsSigns
import com.tangem.utils.transformer.Transformer
@Suppress("LongParameterList")
@ -21,25 +15,17 @@ internal class SendDestinationRecentListTransformer(
private val isSelfSendAvailable: Boolean,
private val destinationWalletList: List<DestinationWalletUM>,
private val txHistoryList: List<TxInfo>,
private val account: Account.CryptoPortfolio?,
private val isAccountsMode: Boolean,
) : Transformer<DestinationUM> {
override fun transform(prevState: DestinationUM): DestinationUM {
val state = prevState as? DestinationUM.Content ?: return prevState
return state.copy(
accountTitleUM = if (account != null && isAccountsMode) {
AccountTitleUM.Account(
name = account.accountName.toUM().value,
icon = account.icon.toUM(),
prefixText = stringReference(StringsSigns.DOT),
)
} else {
AccountTitleUM.Text(TextReference.EMPTY)
},
isAccountsMode = isAccountsMode,
wallets = SendRecipientWalletListConverter(
senderAddress = senderAddress,
isSelfSendAvailable = isSelfSendAvailable,
isAccountsMode = isAccountsMode,
).convert(destinationWalletList),
recent = SendRecipientHistoryListConverter(
cryptoCurrency = cryptoCurrency,

View file

@ -144,7 +144,6 @@ private class DestinationBlockPreviewProvider : PreviewParameterProvider<Destina
isValidating = false,
isInitialized = true,
isRecentHidden = false,
accountTitleUM = null,
)
override val values: Sequence<DestinationUM.Content>

View file

@ -21,7 +21,6 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.containers.FooterContainer
@ -69,18 +68,18 @@ internal fun SendDestinationContent(
onMemoChange = clickIntents::onRecipientMemoValueChange,
)
listHeaderItem(
titleRes = when (state.accountTitleUM) {
is AccountTitleUM.Account -> R.string.common_accounts
else -> R.string.send_recipient_wallets_title
titleRes = if (state.isAccountsMode == true) {
R.string.common_accounts
} else {
R.string.send_recipient_wallets_title
},
isLoading = state.accountTitleUM == null,
isLoading = state.isAccountsMode == null,
isVisible = wallets.isNotEmpty() && wallets.first().isVisible && !state.isRecentHidden,
isFirst = true,
)
listItem(
list = wallets,
isLast = recipients.any { !it.isVisible },
accountTitleUM = state.accountTitleUM,
isBalanceHidden = false,
isRecentHidden = state.isRecentHidden,
onClick = { title ->
@ -92,7 +91,7 @@ internal fun SendDestinationContent(
)
listHeaderItem(
titleRes = R.string.send_recent_transactions,
isLoading = state.accountTitleUM == null,
isLoading = state.isAccountsMode == null,
isVisible = recipients.isNotEmpty() && recipients.first().isVisible && !state.isRecentHidden,
isFirst = wallets.any { !it.isVisible },
)
@ -245,7 +244,6 @@ private fun LazyListScope.listItem(
isBalanceHidden: Boolean,
isRecentHidden: Boolean,
onClick: (String) -> Unit,
accountTitleUM: AccountTitleUM? = null,
) {
items(
count = list.size,
@ -259,7 +257,7 @@ private fun LazyListScope.listItem(
ListItemWithIcon(
title = title,
subtitle = item.subtitle.orMaskWithStars(isBalanceHidden).resolveReference(),
accountTitleUM = accountTitleUM,
accountTitleUM = item.accountTitleUM,
info = item.timestamp?.resolveReference(),
subtitleEndOffset = item.subtitleEndOffset,
subtitleIconRes = item.subtitleIconRes,

View file

@ -1,6 +1,7 @@
package com.tangem.features.send.v2.subcomponents.destination.ui.state
import androidx.compose.runtime.Immutable
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
@ -11,6 +12,7 @@ import com.tangem.domain.models.wallet.UserWalletId
* @property userWalletId wallet id
* @property address blockchain address
* @property cryptoCurrency selected crypto currency
* @property account associated account (if in accounts mode)
*/
@Immutable
data class DestinationWalletUM(
@ -18,4 +20,5 @@ data class DestinationWalletUM(
val userWalletId: UserWalletId,
val address: String,
val cryptoCurrency: CryptoCurrency,
val account: Account.CryptoPortfolio? = null,
)

View file

@ -310,7 +310,7 @@ private fun SendWithSwapSuccessContent_Preview() {
isValidating = false,
isInitialized = false,
isRecentHidden = false,
accountTitleUM = AccountTitleUM.Text(resourceReference(R.string.send_recipient_wallets_title)),
isAccountsMode = false,
),
feeSelectorUM = FeeSelectorUM.Content(
fees = TransactionFee.Single(