From ed80b29c78f3674bc9bf891395ba1f98dfac62cc Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 9 Apr 2026 14:47:25 +0500 Subject: [PATCH 01/14] Updated on 2026-08-14 --- .../SwapNotificationsComponent.kt | 2 ++ .../model/SwapNotificationsModel.kt | 32 +++++++++++++++++ .../analytics/SendWithSwapAnalyticEvents.kt | 35 +++++++++++++++++++ .../confirm/model/SendWithSwapConfirmModel.kt | 1 + .../tangem/feature/swap/model/SwapModel.kt | 8 ++--- 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/SwapNotificationsComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/SwapNotificationsComponent.kt index ecd9813de0..580b1c629a 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/SwapNotificationsComponent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/SwapNotificationsComponent.kt @@ -6,6 +6,7 @@ import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.domain.express.models.ExpressError +import com.tangem.domain.express.models.ExpressProvider import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWalletId @@ -51,6 +52,7 @@ internal class SwapNotificationsComponent( val enteredFromAmount: BigDecimal? = null, val fromCryptoCurrencyStatus: CryptoCurrencyStatus? = null, val priceImpact: PriceImpact? = null, + val provider: ExpressProvider? = null, ) } } \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt index 7a265478bb..0857fa9cca 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt @@ -2,6 +2,7 @@ package com.tangem.features.swap.v2.impl.notifications.model import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -17,6 +18,7 @@ import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent.Params.SwapNotificationData import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsUpdateListener import com.tangem.features.swap.v2.impl.notifications.entity.SwapNotificationUM +import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -29,6 +31,7 @@ import kotlinx.coroutines.launch import java.math.BigDecimal import javax.inject.Inject +@Suppress("LongParameterList") @ModelScoped internal class SwapNotificationsModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, @@ -36,6 +39,7 @@ internal class SwapNotificationsModel @Inject constructor( private val swapNotificationsUpdateTrigger: DefaultSwapNotificationsUpdateTrigger, private val swapAmountUpdateTrigger: SwapAmountUpdateTrigger, private val validateTransactionUseCase: ValidateTransactionUseCase, + private val analyticsEventHandler: AnalyticsEventHandler, paramsContainer: ParamsContainer, ) : Model() { @@ -80,6 +84,34 @@ internal class SwapNotificationsModel @Inject constructor( .isNotEmpty() swapNotificationsUpdateTrigger.callbackHasError(hasErrorNotification) uiState.value = notifications.toImmutableList() + + val fromCurrency = notificationData.fromCryptoCurrency + val toCurrency = notificationData.toCryptoCurrencyStatus?.currency + val provider = notificationData.provider + if (fromCurrency != null && toCurrency != null && provider != null) { + if (notifications.any { it is SwapNotificationUM.Warning.HighPriceImpact }) { + analyticsEventHandler.send( + SendWithSwapAnalyticEvents.HighPriceImpact( + sendToken = fromCurrency.symbol, + receiveToken = toCurrency.symbol, + sendBlockchain = fromCurrency.network.name, + receiveBlockchain = toCurrency.network.name, + providerName = provider.name, + ), + ) + } + if (notifications.any { it is SwapNotificationUM.Warning.TradeTooHigh }) { + analyticsEventHandler.send( + SendWithSwapAnalyticEvents.TradeTooLarge( + sendToken = fromCurrency.symbol, + receiveToken = toCurrency.symbol, + sendBlockchain = fromCurrency.network.name, + receiveBlockchain = toCurrency.network.name, + providerName = provider.name, + ), + ) + } + } } private suspend fun MutableList.addDestinationTagRequiredNotification() { diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/analytics/SendWithSwapAnalyticEvents.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/analytics/SendWithSwapAnalyticEvents.kt index fb20d25a67..f753ef6f26 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/analytics/SendWithSwapAnalyticEvents.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/analytics/SendWithSwapAnalyticEvents.kt @@ -133,6 +133,41 @@ internal sealed class SendWithSwapAnalyticEvents( ), ) + class HighPriceImpact( + val sendToken: String, + val receiveToken: String, + val sendBlockchain: String, + val receiveBlockchain: String, + val providerName: String, + ) : SendWithSwapAnalyticEvents( + event = "Notice - High price impact", + params = mapOf( + SEND_TOKEN to sendToken, + RECEIVE_TOKEN to receiveToken, + "Send Blockchain" to sendBlockchain, + "Receive Blockchain" to receiveBlockchain, + PROVIDER to providerName, + ), + ) + + class TradeTooLarge( + val sendToken: String, + val receiveToken: String, + val sendBlockchain: String, + val receiveBlockchain: String, + val providerName: String, + + ) : SendWithSwapAnalyticEvents( + event = "Notice - Trade too large", + params = mapOf( + SEND_TOKEN to sendToken, + RECEIVE_TOKEN to receiveToken, + "Send Blockchain" to sendBlockchain, + "Receive Blockchain" to receiveBlockchain, + PROVIDER to providerName, + ), + ) + enum class ErrorScreen { Amount, Confirm, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt index 2359164b49..a82e827dc2 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt @@ -460,6 +460,7 @@ internal class SendWithSwapConfirmModel @Inject constructor( enteredFromAmount = confirmData.enteredFromAmount, fromCryptoCurrencyStatus = confirmData.fromCryptoCurrencyStatus, priceImpact = confirmData.priceImpact, + provider = confirmData.quote?.provider, ), ) uiState.transformerUpdate( diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index dcdc0a63a5..94bcb7958f 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -798,8 +798,8 @@ internal class SwapModel @Inject constructor( analyticsEventHandler.send( SwapEvents.HighPriceImpact( sendToken = fromToken.currency.symbol, - receiveToken = toToken.currency.network.name, - sendBlockchain = fromToken.currency.symbol, + receiveToken = toToken.currency.symbol, + sendBlockchain = fromToken.currency.network.name, receiveBlockchain = toToken.currency.network.name, providerName = provider.name, ), @@ -809,8 +809,8 @@ internal class SwapModel @Inject constructor( analyticsEventHandler.send( SwapEvents.TradeTooLarge( sendToken = fromToken.currency.symbol, - receiveToken = toToken.currency.network.name, - sendBlockchain = fromToken.currency.symbol, + receiveToken = toToken.currency.symbol, + sendBlockchain = fromToken.currency.network.name, receiveBlockchain = toToken.currency.network.name, providerName = provider.name, ), From 1c1a968c39de36b12e394408f13aec79f8cf97c4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 9 Apr 2026 17:56:28 +0500 Subject: [PATCH 02/14] Updated on 2026-08-14 --- .../feature/swap/domain/AllowPermissionsHandlerImpl.kt | 4 +++- .../com/tangem/feature/swap/domain/di/SwapDomainModule.kt | 1 + .../main/java/com/tangem/feature/swap/model/SwapModel.kt | 7 +++++++ .../main/java/com/tangem/feature/swap/utils/SwapUtils.kt | 8 ++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/AllowPermissionsHandlerImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/AllowPermissionsHandlerImpl.kt index 8f73c36ee7..344a698f34 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/AllowPermissionsHandlerImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/AllowPermissionsHandlerImpl.kt @@ -1,9 +1,11 @@ package com.tangem.feature.swap.domain +import java.util.Collections.synchronizedSet + class AllowPermissionsHandlerImpl : AllowPermissionsHandler { // todo maybe need to save in store - private val allowPermissionsInProgress = mutableSetOf() + private val allowPermissionsInProgress = synchronizedSet(mutableSetOf()) override fun addAddressToInProgress(tokenAddress: String) { allowPermissionsInProgress.add(tokenAddress) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index ab15afbae5..26b53125e0 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -12,6 +12,7 @@ import javax.inject.Singleton internal class SwapDomainModule { @Provides + @Singleton fun provideAllowPermissionsHandler(): AllowPermissionsHandler { return AllowPermissionsHandlerImpl() } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 94bcb7958f..9acbb9438a 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -74,6 +74,7 @@ import com.tangem.feature.swap.analytics.SwapEvents import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridge import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter +import com.tangem.feature.swap.domain.AllowPermissionsHandler import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.feature.swap.domain.TransactionFeeResult import com.tangem.feature.swap.domain.TxFeeSealedState @@ -88,6 +89,7 @@ import com.tangem.feature.swap.router.SwapNavScreen import com.tangem.feature.swap.router.SwapRouter import com.tangem.feature.swap.ui.StateBuilder import com.tangem.feature.swap.utils.formatToUIRepresentation +import com.tangem.feature.swap.utils.getContractAddress import com.tangem.features.approval.api.GiveApprovalComponent import com.tangem.features.approval.api.GiveApprovalFeatureToggles import com.tangem.features.send.v2.api.entity.FeeSelectorUM @@ -144,6 +146,7 @@ internal class SwapModel @Inject constructor( private val appsFlyerStore: AppsFlyerStore, private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles, private val messageSender: UiMessageSender, + private val allowPermissionsHandler: AllowPermissionsHandler, chooseTokenBridgeFactory: ChooseTokenBridge.Factory, giveApprovalFeatureToggles: GiveApprovalFeatureToggles, ) : Model() { @@ -256,6 +259,10 @@ internal class SwapModel @Inject constructor( } override fun onApproveDone() { + val fromContractAddress = dataState.fromCryptoCurrency?.currency?.getContractAddress() + if (fromContractAddress != null) { + allowPermissionsHandler.addAddressToInProgress(fromContractAddress) + } approvalSlotNavigation.dismiss() updateWalletBalance() uiState = stateBuilder.loadingPermissionState(uiState) diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/utils/SwapUtils.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/utils/SwapUtils.kt index a0ef72bd20..3093dcb07b 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/utils/SwapUtils.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/utils/SwapUtils.kt @@ -5,6 +5,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.simple +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.feature.swap.domain.models.ExpressDataError import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.presentation.R @@ -52,4 +53,11 @@ internal fun getExpressErrorTitle(expressDataError: ExpressDataError): TextRefer internal fun SwapAmount.formatToUIRepresentation(): String { return value.format { simple(decimals = decimals) } +} + +internal fun CryptoCurrency.getContractAddress(): String { + return when (this) { + is CryptoCurrency.Token -> this.contractAddress + is CryptoCurrency.Coin -> "0" + } } \ No newline at end of file From 94c46ca16dd2de0064f90c25007d0d0da2b7ff7e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 9 Apr 2026 20:26:31 +0500 Subject: [PATCH 03/14] Updated on 2026-08-14 --- .../domain/account/models/AccountList.kt | 2 +- .../DefaultSingleAccountStatusListProducer.kt | 4 ++-- .../domain/nft/models/WalletNFTCollections.kt | 2 +- .../domain/nft/GetNFTCollectionsUseCase.kt | 10 ++++++--- .../createedit/AccountCreateEditModel.kt | 5 ++--- .../entity/AccountCreateEditUMBuilder.kt | 18 ++++------------ .../account/details/AccountDetailsModel.kt | 3 +-- .../portfolio/add/AvailableToAddData.kt | 2 +- .../add/impl/model/AddToPortfolioModel.kt | 8 ++----- .../portfolio/add/impl/model/AddTokenModel.kt | 8 ++----- .../impl/model/MarketsPortfolioDelegate.kt | 9 +++----- .../model/CustomTokenSelectorModel.kt | 21 ++++++------------- .../transformer/UpdateDataStateTransformer.kt | 7 ++----- .../model/HotCryptoPortfolioDataLoader.kt | 11 ++++------ .../model/AvailableSwapPairsModel.kt | 20 +++++++----------- .../SetLoadingAccountTokenListTransformer.kt | 20 +++++++----------- .../tokenlist/model/OnrampTokenListModel.kt | 10 ++------- .../referral/domain/ReferralInteractorImpl.kt | 15 ++++--------- .../feature/swap/domain/SwapInteractorImpl.kt | 12 ++--------- 19 files changed, 61 insertions(+), 126 deletions(-) diff --git a/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt b/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt index ab66eca0fd..f4ecc26395 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt @@ -102,7 +102,7 @@ data class AccountList private constructor( return accounts.flatMap { account -> when (account) { is Account.CryptoPortfolio -> account.cryptoCurrencies - is Account.Payment -> TODO("[REDACTED_JIRA]") + is Account.Payment -> emptyList() } } } diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt index 1c341528d0..a2bf732b26 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt @@ -363,7 +363,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo private fun createLoadingAccountStatusList(accountList: AccountList): AccountStatusList { return AccountStatusList( userWalletId = accountList.userWalletId, - accountStatuses = accountList.accounts.map { account -> + accountStatuses = accountList.accounts.mapNotNull { account -> when (account) { is Account.CryptoPortfolio -> { val currencyStatuses = account.cryptoCurrencies.map { @@ -380,7 +380,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo priceChangeLce = lceLoading(), ) } - is Account.Payment -> TODO("[REDACTED_JIRA]") + is Account.Payment -> null } }, totalAccounts = accountList.totalAccounts, diff --git a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/WalletNFTCollections.kt b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/WalletNFTCollections.kt index f6526e3509..726fb69b1b 100644 --- a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/WalletNFTCollections.kt +++ b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/WalletNFTCollections.kt @@ -3,7 +3,7 @@ package com.tangem.domain.nft.models import com.tangem.domain.models.account.Account data class WalletNFTCollections( - val collections: Map>, + val collections: Map>, ) { val flattenCollections by lazy { collections.values.flatten() } } \ No newline at end of file diff --git a/domain/nft/src/main/kotlin/com/tangem/domain/nft/GetNFTCollectionsUseCase.kt b/domain/nft/src/main/kotlin/com/tangem/domain/nft/GetNFTCollectionsUseCase.kt index f8432faa75..90a58d011c 100644 --- a/domain/nft/src/main/kotlin/com/tangem/domain/nft/GetNFTCollectionsUseCase.kt +++ b/domain/nft/src/main/kotlin/com/tangem/domain/nft/GetNFTCollectionsUseCase.kt @@ -18,14 +18,18 @@ class GetNFTCollectionsUseCase( @OptIn(ExperimentalCoroutinesApi::class) operator fun invoke(userWalletId: UserWalletId): Flow { return singleAccountListSupplier(userWalletId) - .mapLatest { statusList -> statusList.accounts.mapNotNull(::flowOfNFTCollections) } + .mapLatest { statusList -> + statusList.accounts.filterIsInstance().mapNotNull(::flowOfNFTCollections) + } .flatMapLatest { flows -> combine(flows) { WalletNFTCollections(it.toMap()) } } } - private fun flowOfNFTCollections(account: Account): Flow>>? { - val currencies = (account as? Account.CryptoPortfolio)?.cryptoCurrencies.orEmpty() + private fun flowOfNFTCollections( + account: Account.CryptoPortfolio, + ): Flow>>? { + val currencies = account.cryptoCurrencies if (currencies.isEmpty()) return null diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt index 50a4eaf8b6..d47f9fd8d3 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt @@ -33,7 +33,6 @@ import com.tangem.features.account.analytics.AccountSettingsAnalyticEvents.Compa import com.tangem.features.account.analytics.WalletSettingsAccountAnalyticEvents import com.tangem.features.account.createedit.entity.AccountCreateEditUM import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder -import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.portfolioIcon import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.toggleProgress import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateButton import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateColorSelect @@ -173,7 +172,7 @@ internal class AccountCreateEditModel @Inject constructor( val name = state.account.name.toDomain().getOrNull() ?: return val icon = CryptoPortfolioIconConverter.convertBack(state.account.portfolioIcon) val isNewName = name != params.account.accountName - val isNewIcon = icon != params.account.portfolioIcon + val isNewIcon = icon != params.account.icon val derivationIndex = params.account.derivationIndex.value analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonSave(name, icon, derivationIndex)) @@ -258,7 +257,7 @@ internal class AccountCreateEditModel @Inject constructor( is AccountCreateEditComponent.Params.Create -> isValidName is AccountCreateEditComponent.Params.Edit -> { val oldName = params.account.accountName.toUM() - val oldIcon = CryptoPortfolioIconConverter.convert(params.account.portfolioIcon) + val oldIcon = CryptoPortfolioIconConverter.convert(params.account.icon) val isNewName = this.account.name.trim() != oldName val isNewIcon = this.account.portfolioIcon != oldIcon diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt index b98da2e8b9..23eb2bd18c 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt @@ -7,7 +7,6 @@ import com.tangem.core.res.R import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList -import com.tangem.domain.models.account.Account import com.tangem.domain.models.account.CryptoPortfolioIcon import com.tangem.features.account.AccountCreateEditComponent import kotlinx.collections.immutable.toImmutableList @@ -37,10 +36,8 @@ internal class AccountCreateEditUMBuilder( ) is AccountCreateEditComponent.Params.Edit -> AccountCreateEditUM.Account( name = params.account.accountName.toUM(), - portfolioIcon = CryptoPortfolioIconConverter.convert(params.account.portfolioIcon), - derivationInfo = createAccountDerivationInfo( - index = (params.account as Account.CryptoPortfolio).derivationIndex.value, - ), + portfolioIcon = CryptoPortfolioIconConverter.convert(params.account.icon), + derivationInfo = createAccountDerivationInfo(index = params.account.derivationIndex.value), inputPlaceholder = resourceReference(R.string.account_form_placeholder_edit_account), onNameChange = onNameChange, ) @@ -50,7 +47,7 @@ internal class AccountCreateEditUMBuilder( fun initColorsUM(onColorSelect: (CryptoPortfolioIcon.Color) -> Unit): AccountCreateEditUM.Colors { val selected: CryptoPortfolioIcon.Color = when (params) { is AccountCreateEditComponent.Params.Create -> createIcon.color - is AccountCreateEditComponent.Params.Edit -> params.account.portfolioIcon.color + is AccountCreateEditComponent.Params.Edit -> params.account.icon.color } return AccountCreateEditUM.Colors( selected = selected, @@ -62,7 +59,7 @@ internal class AccountCreateEditUMBuilder( fun initIconsUM(onIconSelect: (CryptoPortfolioIcon.Icon) -> Unit): AccountCreateEditUM.Icons { val selected: CryptoPortfolioIcon.Icon = when (params) { is AccountCreateEditComponent.Params.Create -> createIcon.value - is AccountCreateEditComponent.Params.Edit -> params.account.portfolioIcon.value + is AccountCreateEditComponent.Params.Edit -> params.account.icon.value } return AccountCreateEditUM.Icons( selected = selected, @@ -85,13 +82,6 @@ internal class AccountCreateEditUMBuilder( } internal companion object { - - val Account.portfolioIcon: CryptoPortfolioIcon - get() = when (this) { - is Account.CryptoPortfolio -> this.icon - is Account.Payment -> TODO("[REDACTED_JIRA]") - } - fun AccountCreateEditUM.updateColorSelect(color: CryptoPortfolioIcon.Color): AccountCreateEditUM { val newIcon = this.account.portfolioIcon.copy( color = color, diff --git a/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt index d9b4788e38..f21cbe265f 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt @@ -21,7 +21,6 @@ import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.account.AccountDetailsComponent import com.tangem.features.account.analytics.AccountSettingsAnalyticEvents -import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.portfolioIcon import com.tangem.features.account.details.entity.AccountDetailsUM import com.tangem.features.account.details.entity.AccountDetailsUM.ArchiveMode import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -164,7 +163,7 @@ internal class AccountDetailsModel @Inject constructor( ?.isMultiCurrency == true return AccountDetailsUM( accountName = account.accountName.toUM().value, - accountIcon = CryptoPortfolioIconConverter.convert(account.portfolioIcon), + accountIcon = CryptoPortfolioIconConverter.convert(account.icon), onCloseClick = { router.pop() }, onAccountEditClick = { onEditAccountClick(account) }, onManageTokensClick = { onManageTokensClick(account) }, diff --git a/features/feed/api/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/AvailableToAddData.kt b/features/feed/api/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/AvailableToAddData.kt index b868b9b8be..4b491c0356 100644 --- a/features/feed/api/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/AvailableToAddData.kt +++ b/features/feed/api/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/AvailableToAddData.kt @@ -28,7 +28,7 @@ data class AvailableToAddWallet( @Serializable data class AvailableToAddAccount( - val account: AccountStatus, + val account: AccountStatus.CryptoPortfolio, val availableNetworks: Set, val addedNetworks: Set, ) { diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddToPortfolioModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddToPortfolioModel.kt index ce12bfc05a..711c38b1f3 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddToPortfolioModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddToPortfolioModel.kt @@ -14,7 +14,6 @@ import com.tangem.core.ui.message.ToastMessage import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2 import com.tangem.domain.markets.GetTokenMarketCryptoCurrency import com.tangem.domain.markets.TokenMarketInfo -import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet @@ -28,13 +27,13 @@ import com.tangem.features.feed.components.market.details.portfolio.impl.loader. import com.tangem.features.feed.components.market.details.portfolio.impl.model.PortfolioTokenUMConverter.Companion.toQuickActions import com.tangem.features.feed.impl.R import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.logging.TangemLogger import kotlinx.coroutines.Job import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import com.tangem.utils.logging.TangemLogger import javax.inject.Inject private const val TOKEN_ACTIONS_DELAY = 500L @@ -325,10 +324,7 @@ internal class AddToPortfolioModel @Inject constructor( network: TokenMarketInfo.Network, account: AvailableToAddAccount, ): CryptoCurrency? { - val accountIndex = when (val accountStatus = account.account) { - is AccountStatus.CryptoPortfolio -> accountStatus.account.derivationIndex - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } + val accountIndex = account.account.account.derivationIndex return getTokenMarketCryptoCurrency( userWalletId = userWallet.walletId, tokenMarketParams = addToPortfolioManager.token, diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddTokenModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddTokenModel.kt index 17d27f1aad..2fb83f4031 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddTokenModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddTokenModel.kt @@ -11,7 +11,6 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.message.ToastMessage import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase -import com.tangem.domain.models.account.Account import com.tangem.domain.wallets.usecase.ColdWalletAndHasMissedDerivationsUseCase import com.tangem.features.feed.components.market.details.portfolio.add.SelectedNetwork import com.tangem.features.feed.components.market.details.portfolio.add.SelectedPortfolio @@ -105,11 +104,8 @@ internal class AddTokenModel @Inject constructor( if (status == null) { processError(error = null) } else { - when (account) { - is Account.CryptoPortfolio -> if (!account.isMainAccount) { - analyticsEventHandler.send(analyticsEventBuilder.addToNotMainAccount()) - } - is Account.Payment -> TODO("[REDACTED_JIRA]") + if (!account.isMainAccount) { + analyticsEventHandler.send(analyticsEventBuilder.addToNotMainAccount()) } analyticsEventHandler.send( diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioDelegate.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioDelegate.kt index e1ed13a89e..f10bf94e1f 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioDelegate.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioDelegate.kt @@ -275,15 +275,12 @@ internal class MarketsPortfolioDelegate @AssistedInject constructor( ) } - private fun Account.toAccountPortfolioHeader(): PortfolioHeader = PortfolioHeader( + private fun Account.CryptoPortfolio.toAccountPortfolioHeader(): PortfolioHeader = PortfolioHeader( id = this.accountId.value, state = AccountTitleUM.Account( prefixText = TextReference.EMPTY, name = this.accountName.toUM().value, - icon = when (this) { - is Account.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(this.icon) - is Account.Payment -> TODO("[REDACTED_JIRA]") - }, + icon = CryptoPortfolioIconConverter.convert(this.icon), ), ) @@ -334,7 +331,7 @@ private data class Portfolio( private data class AccountWithAdded( val addedCurrency: List, - val accountStatus: AccountStatus, + val accountStatus: AccountStatus.CryptoPortfolio, ) private data class SettingsBox( diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt index 731f1757df..d0c6b34ea4 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt @@ -17,8 +17,8 @@ import com.tangem.core.ui.message.EventMessageAction import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier import com.tangem.domain.managetokens.GetSupportedNetworksUseCase -import com.tangem.domain.models.account.Account import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.account.filterCryptoPortfolio import com.tangem.domain.models.network.Network import com.tangem.features.managetokens.component.AddCustomTokenMode import com.tangem.features.managetokens.component.CustomTokenSelectorComponent @@ -214,23 +214,14 @@ internal class CustomTokenSelectorModel @Inject constructor( this.account.derivationIndex.value.toLong() == accountNode val accounts = singleAccountStatusListSupplier(mode.userWalletId) - .first().accountStatuses + .first().accountStatuses.filterCryptoPortfolio() - val accountStatus = accounts.find { account -> - when (account) { - is AccountStatus.CryptoPortfolio -> account.sameNodeAndNotMain() - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } - } - - accountStatus?.account + accounts + .find { account -> account.sameNodeAndNotMain() } + ?.account } - val accountName = when (account) { - is Account.CryptoPortfolio -> account.accountName - is Account.Payment -> TODO("[REDACTED_JIRA]") - null -> null - } + val accountName = account?.accountName if (accountName == null) { onDerivationPathSelected(derivationPath, null) diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/entity/transformer/UpdateDataStateTransformer.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/entity/transformer/UpdateDataStateTransformer.kt index 6a2f95803d..80e74ee49c 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/entity/transformer/UpdateDataStateTransformer.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/entity/transformer/UpdateDataStateTransformer.kt @@ -95,15 +95,12 @@ internal class UpdateDataStateTransformer( createNFTsUM(mainAccountCollection).toPersistentList() } - private fun Account.toAccountPortfolioUM(): NFTCollectionPortfolioUM = NFTCollectionPortfolioUM( + private fun Account.CryptoPortfolio.toAccountPortfolioUM(): NFTCollectionPortfolioUM = NFTCollectionPortfolioUM( id = this.accountId.value, title = AccountTitleUM.Account( prefixText = TextReference.EMPTY, name = this.accountName.toUM().value, - icon = when (this) { - is Account.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(this.icon) - is Account.Payment -> TODO("[REDACTED_JIRA]") - }, + icon = CryptoPortfolioIconConverter.convert(this.icon), ), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoPortfolioDataLoader.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoPortfolioDataLoader.kt index d49f018ad2..a317ada8d2 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoPortfolioDataLoader.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoPortfolioDataLoader.kt @@ -8,6 +8,7 @@ import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.account.filterCryptoPortfolio import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId @@ -42,14 +43,10 @@ internal class HotCryptoPortfolioDataLoader @Inject constructor( .invokeSync(userWalletId, hotCryptoCurrencies) .getOrNull() .orEmpty() - val accountsWithHotCrypto = walletAccounts.accountStatuses.map { accountStatus -> - val account: AccountStatus.CryptoPortfolio = when (accountStatus) { - is AccountStatus.CryptoPortfolio -> accountStatus - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } - val addedHotCrypto = mapOfAddedCurrencies[account.account].orEmpty() + val accountsWithHotCrypto = walletAccounts.accountStatuses.filterCryptoPortfolio().map { accountStatus -> + val addedHotCrypto = mapOfAddedCurrencies[accountStatus.account].orEmpty() HotCryptoPortfolioData.Account( - account = account, + account = accountStatus, addedHotCrypto = addedHotCrypto, ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt index f3b60a2dcd..23a6df2272 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt @@ -247,20 +247,16 @@ internal class AvailableSwapPairsModel @Inject constructor( val (appCurrency, isBalanceHidden) = appCurrencyAndBalanceHiding val filterByQueryAccountList: Map> = accountList + .filterCryptoPortfolio() .associate { accountStatus -> - when (accountStatus) { - is AccountStatus.CryptoPortfolio -> { - val statuses = accountStatus.tokenList.flattenCurrencies() - .filterNot { status -> - status.currency.network.backendId == selectedStatus?.currency?.network?.backendId && - status.currency.id.contractAddress == selectedStatus.currency.id.contractAddress - } - .filterByQuery(query = query) - - accountStatus.account to statuses + val statuses = accountStatus.tokenList.flattenCurrencies() + .filterNot { status -> + status.currency.network.backendId == selectedStatus?.currency?.network?.backendId && + status.currency.id.contractAddress == selectedStatus.currency.id.contractAddress } - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } + .filterByQuery(query = query) + + accountStatus.account to statuses } .filterValues { it.isNotEmpty() } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt index a7ca52c790..797dfec9df 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt @@ -21,12 +21,9 @@ internal class SetLoadingAccountTokenListTransformer( private val accountListItemConverter = LoadingAccountTokenItemConverter(appCurrency) override fun transform(prevState: TokenListUM): TokenListUM { - val totalTokensCount = accountList.sumOf { account -> - when (account) { - is AccountStatus.CryptoPortfolio -> account.tokenList.flattenCurrencies().size - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } - } + val totalTokensCount = accountList + .filterCryptoPortfolio() + .sumOf { account -> account.tokenList.flattenCurrencies().size } return prevState.copy( availableItems = persistentListOf(), @@ -40,13 +37,10 @@ internal class SetLoadingAccountTokenListTransformer( ) } else { TokenListUMData.TokenList( - tokensList = accountList.flatMap { account -> - when (account) { - is AccountStatus.CryptoPortfolio -> LoadingTokenListItemConverter.convertList( - account.tokenList.flattenCurrencies().map(CryptoCurrencyStatus::currency), - ) - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } + tokensList = accountList.filterCryptoPortfolio().flatMap { account -> + LoadingTokenListItemConverter.convertList( + account.tokenList.flattenCurrencies().map(CryptoCurrencyStatus::currency), + ) }.toPersistentList(), totalTokensCount = totalTokensCount, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index 5b62caa0f6..0b7676b1e6 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -18,7 +18,6 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.account.Account -import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.account.filterCryptoPortfolio import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.settings.usercountry.GetUserCountryUseCase @@ -240,13 +239,8 @@ internal class OnrampTokenListModel @Inject constructor( ): Map> = accountStatuses.asSequence() .filterCryptoPortfolio() .associate { accountStatus -> - when (accountStatus) { - is AccountStatus.CryptoPortfolio -> { - val filteredList = accountStatus.tokenList.flattenCurrencies().filterByQuery(query = query) - accountStatus.account to filteredList - } - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } + val filteredList = accountStatus.tokenList.flattenCurrencies().filterByQuery(query = query) + accountStatus.account to filteredList }.filter { (_, value) -> value.isNotEmpty() } private fun List.filterByQuery(query: String): List { diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt index 3dac39dddb..b3d7fdc50a 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt @@ -4,9 +4,9 @@ import com.tangem.common.core.TangemSdkError import com.tangem.domain.account.producer.SingleAccountProducer import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase import com.tangem.domain.account.supplier.SingleAccountSupplier -import com.tangem.domain.models.account.Account import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.account.DerivationIndex +import com.tangem.domain.models.account.derivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.walletmanager.WalletManagersFacade @@ -40,20 +40,13 @@ internal class ReferralInteractorImpl( val account = singleAccountSupplier.getSyncOrNull( params = SingleAccountProducer.Params(accountId = accountId), - ) - ?: error("Account not found: $accountId") - - val accountIndex = when (account) { - is Account.CryptoPortfolio -> account.derivationIndex - is Account.Payment -> TODO("[REDACTED_JIRA]") - } + ) ?: error("Account not found: $accountId") val cryptoCurrency = getCryptoCurrency( userWalletId = accountId.userWalletId, tokenData = tokenData, - accountIndex = accountIndex, - ) - ?: error("Failed to create crypto currency") + accountIndex = account.derivationIndex, + ) ?: error("Failed to create crypto currency") manageCryptoCurrenciesUseCase( accountId = accountId, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 952916beb1..dbbaa31b93 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -30,7 +30,6 @@ import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.express.models.ExpressOperationType import com.tangem.domain.models.account.Account -import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.account.filterCryptoPortfolio import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -72,11 +71,7 @@ import com.tangem.utils.logging.TangemLogger import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.* import java.math.BigDecimal import java.math.BigInteger import java.math.RoundingMode @@ -142,10 +137,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( val walletAccountCurrencyStatusesExceptInitial: Map> = walletAccountCurrencyStatuses.mapNotNull { accountStatus -> - val filteredCurrencies = when (accountStatus) { - is AccountStatus.CryptoPortfolio -> accountStatus.flattenCurrencies().filterCurrencies(currency) - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } + val filteredCurrencies = accountStatus.flattenCurrencies().filterCurrencies(currency) if (filteredCurrencies.isNotEmpty()) { accountStatus.account to filteredCurrencies From 73a6042277042f80074e84c0443f056dbca05d68 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 10 Apr 2026 12:32:14 +0500 Subject: [PATCH 04/14] Updated on 2026-08-14 --- .../tap/di/domain/AccountDomainModule.kt | 10 +- .../DefaultMultiAccountListProducer.kt | 7 +- .../DefaultMultiAccountListProducerTest.kt | 41 ++-- .../usecase/IsAccountsModeEnabledUseCase.kt | 58 ++---- .../IsAccountsModeEnabledUseCaseTest.kt | 189 +++++------------- 5 files changed, 96 insertions(+), 209 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt index 1240259f77..34a46c0524 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt @@ -6,9 +6,9 @@ import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier import com.tangem.domain.account.status.usecase.ArchiveCryptoPortfolioUseCase import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher +import com.tangem.domain.account.supplier.MultiAccountListSupplier import com.tangem.domain.account.tokens.MainAccountTokensMigration import com.tangem.domain.account.usecase.* -import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.feature.referral.data.ExternalReferralRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -94,13 +94,9 @@ internal object AccountDomainModule { @Provides @Singleton fun provideIsAccountsModeEnabledUseCase( - userWalletsListRepository: UserWalletsListRepository, - accountsCRUDRepository: AccountsCRUDRepository, + multiAccountListSupplier: MultiAccountListSupplier, ): IsAccountsModeEnabledUseCase { - return IsAccountsModeEnabledUseCase( - userWalletsListRepository = userWalletsListRepository, - crudRepository = accountsCRUDRepository, - ) + return IsAccountsModeEnabledUseCase(multiAccountListSupplier = multiAccountListSupplier) } @Provides diff --git a/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiAccountListProducer.kt b/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiAccountListProducer.kt index ac2bb1b720..edd3c2fef5 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiAccountListProducer.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiAccountListProducer.kt @@ -4,6 +4,7 @@ import arrow.core.Option import arrow.core.some import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.producer.MultiAccountListProducer +import com.tangem.domain.account.supplier.SingleAccountListSupplier import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.loadAndGet import com.tangem.domain.core.flow.FlowProducerTools @@ -22,7 +23,7 @@ import kotlinx.coroutines.flow.* * @property params params * @property flowProducerTools tools for producing flows * @property userWalletsListRepository repository for getting user wallets - * @property walletAccountListFlowFactory builder to create flows of [AccountList] for each wallet + * @property singleAccountListSupplier supplier for getting [AccountList] per wallet * @property dispatchers coroutine dispatchers provider * [REDACTED_AUTHOR] @@ -31,7 +32,7 @@ internal class DefaultMultiAccountListProducer @AssistedInject constructor( @Assisted val params: Unit, override val flowProducerTools: FlowProducerTools, private val userWalletsListRepository: UserWalletsListRepository, - private val walletAccountListFlowFactory: WalletAccountListFlowFactory, + private val singleAccountListSupplier: SingleAccountListSupplier, private val dispatchers: CoroutineDispatcherProvider, ) : MultiAccountListProducer { @@ -44,7 +45,7 @@ internal class DefaultMultiAccountListProducer @AssistedInject constructor( .distinctUntilChanged() .flatMapLatest { ids -> combine( - flows = ids.map(walletAccountListFlowFactory::create), + flows = ids.map(singleAccountListSupplier::invoke), transform = ::listOf, ) } diff --git a/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiAccountListProducerTest.kt b/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiAccountListProducerTest.kt index 5a3723c4ad..db0d4f8e61 100644 --- a/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiAccountListProducerTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiAccountListProducerTest.kt @@ -2,6 +2,7 @@ package com.tangem.data.account.producer import com.google.common.truth.Truth import com.tangem.domain.account.models.AccountList +import com.tangem.domain.account.supplier.SingleAccountListSupplier import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.core.flow.FlowProducerTools import com.tangem.domain.models.TokensSortType @@ -28,14 +29,14 @@ import org.junit.jupiter.api.TestInstance class DefaultMultiAccountListProducerTest { private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxUnitFun = true) - private val walletAccountListFlowFactory: WalletAccountListFlowFactory = mockk() + private val singleAccountListSupplier: SingleAccountListSupplier = mockk() private val flowProducerTools: FlowProducerTools = mockk() private val producer = DefaultMultiAccountListProducer( params = Unit, flowProducerTools = flowProducerTools, userWalletsListRepository = userWalletsListRepository, - walletAccountListFlowFactory = walletAccountListFlowFactory, + singleAccountListSupplier = singleAccountListSupplier, dispatchers = TestingCoroutineDispatcherProvider(), ) @@ -46,7 +47,7 @@ class DefaultMultiAccountListProducerTest { @AfterEach fun tearDownEach() { - clearMocks(userWalletsListRepository, walletAccountListFlowFactory) + clearMocks(userWalletsListRepository, singleAccountListSupplier) } @Test @@ -56,7 +57,7 @@ class DefaultMultiAccountListProducerTest { every { userWalletsListRepository.userWallets } returns userWalletsFlow val accountList = AccountList.empty(userWalletId) - every { walletAccountListFlowFactory.create(userWalletId) } returns flowOf(accountList) + every { singleAccountListSupplier.invoke(userWalletId) } returns flowOf(accountList) // Act val actual = producer.produce().let(::getEmittedValues) @@ -68,7 +69,7 @@ class DefaultMultiAccountListProducerTest { coVerifySequence { userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) } } @@ -82,7 +83,7 @@ class DefaultMultiAccountListProducerTest { val updatedAccountList = AccountList.empty(userWalletId = userWalletId, sortType = TokensSortType.NONE) val factoryFlow = MutableStateFlow(null) - every { walletAccountListFlowFactory.create(userWalletId) } returns factoryFlow.filterNotNull() + every { singleAccountListSupplier.invoke(userWalletId) } returns factoryFlow.filterNotNull() // Act (first emission) factoryFlow.value = accountList @@ -101,10 +102,10 @@ class DefaultMultiAccountListProducerTest { coVerifySequence { userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) } } @@ -117,7 +118,7 @@ class DefaultMultiAccountListProducerTest { val accountList = AccountList.empty(userWalletId) val factoryFlow = MutableStateFlow(null) - every { walletAccountListFlowFactory.create(userWalletId) } returns factoryFlow.filterNotNull() + every { singleAccountListSupplier.invoke(userWalletId) } returns factoryFlow.filterNotNull() // Act (first emission) factoryFlow.value = accountList @@ -136,10 +137,10 @@ class DefaultMultiAccountListProducerTest { coVerifySequence { userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) } } @@ -151,7 +152,7 @@ class DefaultMultiAccountListProducerTest { every { userWalletsListRepository.userWallets } returns userWalletsFlow val exception = RuntimeException("Converter error") - every { walletAccountListFlowFactory.create(userWalletId) } throws exception + every { singleAccountListSupplier.invoke(userWalletId) } throws exception // Act val actual = producer.produceWithFallback().let(::getEmittedValues) @@ -163,7 +164,7 @@ class DefaultMultiAccountListProducerTest { coVerifySequence { userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) } } @@ -183,7 +184,7 @@ class DefaultMultiAccountListProducerTest { userWalletsListRepository.load() userWalletsListRepository.userWallets } - coVerify(inverse = true) { walletAccountListFlowFactory.create(any()) } + coVerify(inverse = true) { singleAccountListSupplier.invoke(any()) } } @Test @@ -192,7 +193,7 @@ class DefaultMultiAccountListProducerTest { val userWalletsFlow = MutableStateFlow(value = listOf(userWallet)) every { userWalletsListRepository.userWallets } returns userWalletsFlow - every { walletAccountListFlowFactory.create(userWalletId) } returns emptyFlow() + every { singleAccountListSupplier.invoke(userWalletId) } returns emptyFlow() // Act val actual = producer.produce().let(::getEmittedValues) @@ -203,7 +204,7 @@ class DefaultMultiAccountListProducerTest { coVerifySequence { userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) } } @@ -219,8 +220,8 @@ class DefaultMultiAccountListProducerTest { every { userWalletsListRepository.userWallets } returns userWalletsFlow val accountList = AccountList.empty(userWalletId) - every { walletAccountListFlowFactory.create(userWalletId) } returns flowOf(accountList) - every { walletAccountListFlowFactory.create(userWalletId2) } returns emptyFlow() + every { singleAccountListSupplier.invoke(userWalletId) } returns flowOf(accountList) + every { singleAccountListSupplier.invoke(userWalletId2) } returns emptyFlow() // Act val actual = producer.produce().let(::getEmittedValues) @@ -231,8 +232,8 @@ class DefaultMultiAccountListProducerTest { coVerifySequence { userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) - walletAccountListFlowFactory.create(userWalletId2) + singleAccountListSupplier.invoke(userWalletId) + singleAccountListSupplier.invoke(userWalletId2) } } } \ No newline at end of file diff --git a/domain/account/src/main/java/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCase.kt b/domain/account/src/main/java/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCase.kt index 94e6e051af..735d837692 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCase.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCase.kt @@ -1,65 +1,35 @@ package com.tangem.domain.account.usecase -import arrow.core.Option -import arrow.core.getOrElse -import com.tangem.domain.account.repository.AccountsCRUDRepository -import com.tangem.domain.common.wallets.UserWalletsListRepository -import com.tangem.domain.common.wallets.loadAndGet -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.isMultiCurrency -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.* +import com.tangem.domain.account.models.AccountList +import com.tangem.domain.account.supplier.MultiAccountListSupplier +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map /** * Use case to determine if the accounts mode is enabled. - * Accounts mode is considered enabled if there are at least two accounts in any of the user wallets that support - * multiple currencies. + * Accounts mode is considered enabled if any [AccountList] produced for the user's wallets contains at least two + * active accounts. * - * @property crudRepository repository to perform CRUD operations on accounts. - * @property userWalletsListRepository repository to get the list of user wallets. + * @property multiAccountListSupplier supplier that provides a list of [AccountList]s for all user wallets * [REDACTED_AUTHOR] */ class IsAccountsModeEnabledUseCase( - private val crudRepository: AccountsCRUDRepository, - private val userWalletsListRepository: UserWalletsListRepository, + private val multiAccountListSupplier: MultiAccountListSupplier, ) { - @OptIn(ExperimentalCoroutinesApi::class) operator fun invoke(): Flow { - return userWalletsListRepository.loadAndGet() - .flatMapLatest { userWallets -> - val totalAccountsCountList = getTotalAccountsCountList(userWallets) - - combine(flows = totalAccountsCountList) { it.toList().isModeEnabled() } - } - .onEmpty { emit(false) } + return multiAccountListSupplier.invoke() + .map { accountsList -> accountsList.map(AccountList::activeAccounts).isModeEnabled() } .distinctUntilChanged() } suspend fun invokeSync(): Boolean { - return userWalletsListRepository.userWallets.value.orEmpty() - .map { userWallet -> - // If the wallet does not support multiple currencies, we consider its account count as 0 - if (!userWallet.isMultiCurrency) return@map 0 - - crudRepository.getTotalActiveAccountsCountSync(userWalletId = userWallet.walletId).getOrZero() - } - .isModeEnabled() + return multiAccountListSupplier.getSyncOrNull(Unit) + ?.map(AccountList::activeAccounts) + ?.isModeEnabled() == true } - private fun getTotalAccountsCountList(userWallets: List): List> { - return userWallets - .map { userWallet -> - // If the wallet does not support multiple currencies, we consider its account count as 0 - if (!userWallet.isMultiCurrency) return@map flowOf(0) - - crudRepository.getTotalActiveAccountsCount(userWalletId = userWallet.walletId) - .map { maybeCount -> maybeCount.getOrZero() } - } - } - - private fun Option.getOrZero(): Int = getOrElse { 0 } - private fun List.isModeEnabled(): Boolean = any { it >= 2 } } \ No newline at end of file diff --git a/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCaseTest.kt b/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCaseTest.kt index 894d4ec39d..71e7b32d89 100644 --- a/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCaseTest.kt +++ b/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCaseTest.kt @@ -1,15 +1,12 @@ package com.tangem.domain.account.usecase -import arrow.core.none -import arrow.core.some import com.google.common.truth.Truth -import com.tangem.domain.account.repository.AccountsCRUDRepository -import com.tangem.domain.common.wallets.UserWalletsListRepository -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.models.wallet.isMultiCurrency -import io.mockk.* -import kotlinx.coroutines.flow.MutableStateFlow +import com.tangem.domain.account.models.AccountList +import com.tangem.domain.account.supplier.MultiAccountListSupplier +import io.mockk.clearMocks +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest @@ -18,21 +15,16 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance -@Suppress("UnusedFlow") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class IsAccountsModeEnabledUseCaseTest { - private val accountsCRUDRepository: AccountsCRUDRepository = mockk() - private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxUnitFun = true) + private val multiAccountListSupplier: MultiAccountListSupplier = mockk() - private val useCase = IsAccountsModeEnabledUseCase( - crudRepository = accountsCRUDRepository, - userWalletsListRepository = userWalletsListRepository, - ) + private val useCase = IsAccountsModeEnabledUseCase(multiAccountListSupplier = multiAccountListSupplier) @AfterEach fun tearDown() { - clearMocks(userWalletsListRepository, accountsCRUDRepository) + clearMocks(multiAccountListSupplier) } @Nested @@ -40,90 +32,55 @@ class IsAccountsModeEnabledUseCaseTest { inner class Invoke { @Test - fun `returns false when loadAndGet emits one wallet with isMultiCurrency false`() = runTest { + fun `returns false when supplier emits empty list`() = runTest { // Arrange - val wallet = createUserWallet(isMultiCurrency = false) - - every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(wallet)) + every { multiAccountListSupplier.invoke() } returns flowOf(emptyList()) // Act val actual = useCase.invoke().first() // Assert Truth.assertThat(actual).isFalse() - - coVerifyOrder { - userWalletsListRepository.load() - userWalletsListRepository.userWallets - } - - verify(inverse = true) { accountsCRUDRepository.getTotalActiveAccountsCount(any()) } } @Test - fun `returns true when loadAndGet emits one wallet with isMultiCurrency true`() = runTest { + fun `returns false when supplier emits account list with one account`() = runTest { // Arrange - val wallet = createUserWallet(isMultiCurrency = true) - - every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(wallet)) - every { accountsCRUDRepository.getTotalActiveAccountsCount(wallet.walletId) } returns flowOf(2.some()) - - // Act - val actual = useCase.invoke().first() - - // Assert - Truth.assertThat(actual).isTrue() - - coVerifyOrder { - userWalletsListRepository.load() - userWalletsListRepository.userWallets - accountsCRUDRepository.getTotalActiveAccountsCount(wallet.walletId) - } - } - - @Test - fun `returns false when loadAndGet emits one wallet with isMultiCurrency true and None counts`() = runTest { - // Arrange - val wallet = createUserWallet(isMultiCurrency = true) - - every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(wallet)) - every { accountsCRUDRepository.getTotalActiveAccountsCount(wallet.walletId) } returns flowOf(none()) + val accountList = createAccountList(activeAccounts = 1) + every { multiAccountListSupplier.invoke() } returns flowOf(listOf(accountList)) // Act val actual = useCase.invoke().first() // Assert Truth.assertThat(actual).isFalse() - - coVerifyOrder { - userWalletsListRepository.load() - userWalletsListRepository.userWallets - accountsCRUDRepository.getTotalActiveAccountsCount(wallet.walletId) - } } @Test - fun `returns true when loadAndGet emits two wallets, one isMultiCurrency false, one true`() = runTest { + fun `returns true when supplier emits account list with two accounts`() = runTest { // Arrange - val wallet1 = createUserWallet(isMultiCurrency = false) - val wallet2 = createUserWallet(isMultiCurrency = true) - - every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(wallet1, wallet2)) - every { accountsCRUDRepository.getTotalActiveAccountsCount(wallet2.walletId) } returns flowOf(2.some()) + val accountList = createAccountList(activeAccounts = 2) + every { multiAccountListSupplier.invoke() } returns flowOf(listOf(accountList)) // Act val actual = useCase.invoke().first() // Assert Truth.assertThat(actual).isTrue() + } - coVerifyOrder { - userWalletsListRepository.load() - userWalletsListRepository.userWallets - accountsCRUDRepository.getTotalActiveAccountsCount(wallet2.walletId) - } + @Test + fun `returns true when supplier emits multiple account lists, one with two accounts`() = runTest { + // Arrange + val accountList1 = createAccountList(activeAccounts = 1) + val accountList2 = createAccountList(activeAccounts = 2) + every { multiAccountListSupplier.invoke() } returns flowOf(listOf(accountList1, accountList2)) - verify(inverse = true) { accountsCRUDRepository.getTotalActiveAccountsCount(wallet1.walletId) } + // Act + val actual = useCase.invoke().first() + + // Assert + Truth.assertThat(actual).isTrue() } } @@ -132,109 +89,71 @@ class IsAccountsModeEnabledUseCaseTest { inner class InvokeSync { @Test - fun `returns false when getUserWalletsSync returns empty list`() = runTest { + fun `returns false when getSyncOrNull returns null`() = runTest { // Arrange - every { userWalletsListRepository.userWallets.value } returns emptyList() + coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns null // Act val actual = useCase.invokeSync() // Assert Truth.assertThat(actual).isFalse() - - verifyOrder { - userWalletsListRepository.userWallets.value - } - - coVerify(inverse = true) { accountsCRUDRepository.getTotalActiveAccountsCountSync(any()) } } @Test - fun `returns false when getUserWalletsSync returns one wallet with isMultiCurrency false`() = runTest { + fun `returns false when getSyncOrNull returns empty list`() = runTest { // Arrange - val wallet = createUserWallet(isMultiCurrency = false) - - every { userWalletsListRepository.userWallets.value } returns listOf(wallet) + coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns emptyList() // Act val actual = useCase.invokeSync() // Assert Truth.assertThat(actual).isFalse() - - verifyOrder { - userWalletsListRepository.userWallets.value - } - - coVerify(inverse = true) { accountsCRUDRepository.getTotalActiveAccountsCountSync(any()) } } @Test - fun `returns true when getUserWalletsSync returns one wallet with isMultiCurrency true`() = runTest { + fun `returns false when getSyncOrNull returns account list with one account`() = runTest { // Arrange - val wallet = createUserWallet(isMultiCurrency = true) + val accountList = createAccountList(activeAccounts = 1) + coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns listOf(accountList) - every { userWalletsListRepository.userWallets.value } returns listOf(wallet) - coEvery { accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet.walletId) } returns 2.some() + // Act + val actual = useCase.invokeSync() + + // Assert + Truth.assertThat(actual).isFalse() + } + + @Test + fun `returns true when getSyncOrNull returns account list with two accounts`() = runTest { + // Arrange + val accountList = createAccountList(activeAccounts = 2) + coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns listOf(accountList) // Act val actual = useCase.invokeSync() // Assert Truth.assertThat(actual).isTrue() - - coVerifyOrder { - userWalletsListRepository.userWallets.value - accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet.walletId) - } } @Test - fun `returns false when getUserWalletsSync returns multi wallet with None counts`() = runTest { + fun `returns true when getSyncOrNull returns multiple account lists, one with two accounts`() = runTest { // Arrange - val wallet = createUserWallet(isMultiCurrency = true) - - every { userWalletsListRepository.userWallets.value } returns listOf(wallet) - coEvery { accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet.walletId) } returns none() - - // Act - val actual = useCase.invokeSync() - - // Assert - Truth.assertThat(actual).isFalse() - - coVerifyOrder { - userWalletsListRepository.userWallets.value - accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet.walletId) - } - } - - @Test - fun `returns true when getUserWalletsSync returns multi and single wallets`() = runTest { - // Arrange - val wallet1 = createUserWallet(isMultiCurrency = false) - val wallet2 = createUserWallet(isMultiCurrency = true) - - every { userWalletsListRepository.userWallets.value } returns listOf(wallet1, wallet2) - coEvery { accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet2.walletId) } returns 2.some() + val accountList1 = createAccountList(activeAccounts = 1) + val accountList2 = createAccountList(activeAccounts = 2) + coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns listOf(accountList1, accountList2) // Act val actual = useCase.invokeSync() // Assert Truth.assertThat(actual).isTrue() - - coVerifyOrder { - userWalletsListRepository.userWallets.value - accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet2.walletId) - } - - coVerify(inverse = true) { accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet1.walletId) } } } - private fun createUserWallet(isMultiCurrency: Boolean): UserWallet = mockk { - every { this@mockk.walletId } returns UserWalletId(stringValue = "011") - every { this@mockk.isMultiCurrency } returns isMultiCurrency + private fun createAccountList(activeAccounts: Int): AccountList = mockk { + every { this@mockk.activeAccounts } returns activeAccounts } } \ No newline at end of file From a650d0786b376e58ea66e5f82b4a5e7cd463542f Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 10 Apr 2026 13:51:14 +0400 Subject: [PATCH 05/14] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 4 ++++ gradle/tangem_dependencies.toml | 2 +- .../com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt | 2 ++ .../com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 1a45030b37..98751c086c 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -79,5 +79,9 @@ { "name": "SOLANA_TX_HISTORY_ENABLED", "version": "undefined" + }, + { + "name": "SOLANA_SCALED_UI_AMOUNT_ENABLED", + "version": "undefined" } ] diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 278d64c66f..891cb8a686 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.37-1485" +tangemBlockchainSdk = "releases-5.37-1489" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.37-603" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt index 8383c43b2e..3de6c03bd1 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt @@ -24,6 +24,7 @@ internal class WalletManagerFactoryCreator @Inject constructor( private val blockchainDataStorage: BlockchainDataStorage, private val blockchainSDKLogger: BlockchainSDKLogger, private val isSolanaTxHistoryEnabled: Boolean, + private val isSolanaScaledUiAmountEnabled: Boolean, ) { fun create(config: BlockchainSdkConfig, blockchainProviderTypes: BlockchainProviderTypes): WalletManagerFactory { @@ -37,6 +38,7 @@ internal class WalletManagerFactoryCreator @Inject constructor( isYieldSupplyEnabled = true, isPendingTransactionsEnabled = true, isSolanaTxHistoryEnabled = isSolanaTxHistoryEnabled, + isSolanaScaledUiAmountEnabled = isSolanaScaledUiAmountEnabled, ), blockchainDataStorage = blockchainDataStorage, loggers = listOf(blockchainSDKLogger), diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt index fbcf86e62b..9bdbd36f05 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt @@ -100,6 +100,9 @@ internal object BlockchainSDKFactoryModule { isSolanaTxHistoryEnabled = featureTogglesManager.isFeatureEnabled( FeatureToggles.SOLANA_TX_HISTORY_ENABLED, ), + isSolanaScaledUiAmountEnabled = featureTogglesManager.isFeatureEnabled( + FeatureToggles.SOLANA_SCALED_UI_AMOUNT_ENABLED, + ), ) } } \ No newline at end of file From fc6b1c8233cdb9239a50344493b640deac1886ef Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 10 Apr 2026 13:27:56 +0200 Subject: [PATCH 06/14] Updated on 2026-08-14 --- .../features/feed/model/news/details/NewsDetailsModel.kt | 3 +++ .../model/news/details/NewsDetailsPaginationManager.kt | 2 ++ .../features/feed/model/news/list/NewsListModel.kt | 3 +++ .../news/list/statemanager/NewsListBatchFlowManager.kt | 9 ++++++++- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsModel.kt index bdd662e7f2..8c816de944 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsModel.kt @@ -9,6 +9,7 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.DesignFeatureToggles import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency @@ -54,6 +55,7 @@ internal class NewsDetailsModel @Inject constructor( private val markArticleAsViewedUseCase: MarkArticleAsViewedUseCase, private val toggleArticleLikedUseCase: ToggleArticleLikedUseCase, private val analyticsEventHandler: AnalyticsEventHandler, + private val designFeatureToggles: DesignFeatureToggles, paramsContainer: ParamsContainer, ) : Model() { @@ -71,6 +73,7 @@ internal class NewsDetailsModel @Inject constructor( dispatchers = dispatchers, observeNewsDetailsUseCase = observeNewsDetailsUseCase, prefetchedIds = params.preselectedArticlesId.toSet(), + isRedesignEnabled = designFeatureToggles.isRedesignEnabled, ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsPaginationManager.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsPaginationManager.kt index 54ad5c4ec6..7ec0b5f531 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsPaginationManager.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsPaginationManager.kt @@ -20,12 +20,14 @@ internal class NewsDetailsPaginationManager( currentCategoryIds: Provider>, modelScope: CoroutineScope, prefetchedIds: Set, + isRedesignEnabled: Boolean, ) : NewsListBatchFlowManager( getNewsListBatchFlowUseCase = getNewsListBatchFlowUseCase, currentLanguage = currentLanguage, currentCategoryIds = currentCategoryIds, modelScope = modelScope, dispatchers = dispatchers, + isRedesignEnabled = isRedesignEnabled, ) { private val _cachedPrefetchedIds = MutableStateFlow(prefetchedIds) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt index 22ba4a7be4..9542f5be8a 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt @@ -4,6 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.ui.DesignFeatureToggles import com.tangem.core.ui.components.chip.entity.ChipUM import com.tangem.domain.news.model.NewsListConfig import com.tangem.domain.news.usecase.GetNewsCategoriesUseCase @@ -34,6 +35,7 @@ internal class NewsListModel @Inject constructor( private val getNewsCategoriesUseCase: GetNewsCategoriesUseCase, private val getNewsListBatchFlowUseCase: GetNewsListBatchFlowUseCase, private val analyticsEventHandler: AnalyticsEventHandler, + private val designFeatureToggles: DesignFeatureToggles, paramsContainer: ParamsContainer, ) : Model() { @@ -58,6 +60,7 @@ internal class NewsListModel @Inject constructor( }, modelScope = modelScope, dispatchers = dispatchers, + isRedesignEnabled = designFeatureToggles.isRedesignEnabled, ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/statemanager/NewsListBatchFlowManager.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/statemanager/NewsListBatchFlowManager.kt index ca5f6d7382..a6e5d5a2fd 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/statemanager/NewsListBatchFlowManager.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/statemanager/NewsListBatchFlowManager.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.launch @Suppress("LongParameterList") internal open class NewsListBatchFlowManager( + private val isRedesignEnabled: Boolean, getNewsListBatchFlowUseCase: GetNewsListBatchFlowUseCase, private val currentLanguage: Provider, private val currentCategoryIds: Provider>, @@ -30,7 +31,13 @@ internal open class NewsListBatchFlowManager( ) { private val actionsFlow = MutableSharedFlow>() private val converter by lazy { - ShortArticleToArticleConfigUMConverter(null) + ShortArticleToArticleConfigUMConverter( + isTrending = if (isRedesignEnabled) { + null + } else { + false + }, + ) } private val batchFlow = getNewsListBatchFlowUseCase( From badd93f3882423e57bbec72e160f0af8df87d3a6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 10 Apr 2026 18:04:55 +0300 Subject: [PATCH 07/14] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index edd3e5f352..b1d2bf912f 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -7,7 +7,7 @@ tangemBlockchainSdk = "releases-5.36-1480" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.36-600" +tangemCardSdk = "releases-5.36-607" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From e0d6b165c0d2c5f4d4795018f7acf36138c043e6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 10 Apr 2026 19:46:22 +0400 Subject: [PATCH 08/14] Updated on 2026-08-14 --- .../java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 3b7b8442ab..52f21e4feb 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -1263,7 +1263,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( ) ?.filterIsInstance() ?.firstOrNull { it.network.id == network.id && it.network.derivationPath == network.derivationPath } - ?: error("Unable to create network coin with ID: ${network.id}") + ?: currenciesRepository.createCoinCurrency(network) } private suspend fun isAllowedToSpend( From 427043c970cd59f213b5df849b1fffa7baedef05 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 11 Apr 2026 10:30:01 +0000 Subject: [PATCH 09/14] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index b1d2bf912f..891cb8a686 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,13 +5,13 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.36-1480" +tangemBlockchainSdk = "releases-5.37-1489" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.36-607" +tangemCardSdk = "releases-5.37-603" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ -tangemVico = "2.0.0-alpha.25-tangem12" +tangemVico = "tangem-master-21" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ -tangemHotSdk = "develop-549" +tangemHotSdk = "develop-550" #tangemHotSdk = "0.0.1" # Keep it! - used for local builds ^ From fb2492eabb84f3f29df79e6f4b536a5496dd42c2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 Apr 2026 11:22:18 +0400 Subject: [PATCH 10/14] Updated on 2026-08-14 --- .../v2/impl/amount/model/SwapAmountModel.kt | 10 ++-- .../SwapAmountChangeAmountTypeTransformer.kt | 51 +++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountChangeAmountTypeTransformer.kt diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt index 23ec462f60..ecc249a62b 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt @@ -215,13 +215,13 @@ internal class SwapAmountModel @Inject constructor( } } - uiState.update { amountUM -> - if (amountUM !is SwapAmountUM.Content) return@update amountUM - amountUM.copy( + uiState.transformerUpdate( + SwapAmountChangeAmountTypeTransformer( selectedAmountType = selectedAmountType, swapRateType = newSwapRateType, - ) - } + isBalanceHidden = params.isBalanceHidingFlow.value, + ), + ) startLoadingQuotesTask(isSilentReload = false) } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountChangeAmountTypeTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountChangeAmountTypeTransformer.kt new file mode 100644 index 0000000000..c1fde5d967 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountChangeAmountTypeTransformer.kt @@ -0,0 +1,51 @@ +package com.tangem.features.swap.v2.impl.amount.model.transformers + +import com.tangem.domain.express.models.ExpressRateType +import com.tangem.domain.swap.models.SwapAmountType +import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM +import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM +import com.tangem.features.swap.v2.impl.amount.model.converter.SwapAmountUpdateSubtitleConverter +import com.tangem.utils.transformer.Transformer + +internal class SwapAmountChangeAmountTypeTransformer( + private val selectedAmountType: SwapAmountType, + private val swapRateType: ExpressRateType, + private val isBalanceHidden: Boolean, +) : Transformer { + + override fun transform(prevState: SwapAmountUM): SwapAmountUM { + if (prevState !is SwapAmountUM.Content) return prevState + + val subtitleConverter = SwapAmountUpdateSubtitleConverter( + selectedAmountType = selectedAmountType, + isBalanceHidden = isBalanceHidden, + ) + + val newPrimaryAmount = (prevState.primaryAmount as? SwapAmountFieldUM.Content)?.let { field -> + subtitleConverter.updateSubtitles( + field = field, + cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus, + isAmountEmpty = true, + ) + } ?: prevState.primaryAmount + + val newSecondaryAmount = if (prevState.secondaryCryptoCurrencyStatus != null) { + (prevState.secondaryAmount as? SwapAmountFieldUM.Content)?.let { field -> + subtitleConverter.updateSubtitles( + field = field, + cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus, + isAmountEmpty = true, + ) + } ?: prevState.secondaryAmount + } else { + prevState.secondaryAmount + } + + return prevState.copy( + selectedAmountType = selectedAmountType, + swapRateType = swapRateType, + primaryAmount = newPrimaryAmount, + secondaryAmount = newSecondaryAmount, + ) + } +} \ No newline at end of file From 71507af6c3d3159e974b94922cbc70cbefd28786 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 Apr 2026 12:28:00 +0400 Subject: [PATCH 11/14] Updated on 2026-08-14 --- .../confirm/model/SwapTransactionSender.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt index 080ab91574..9f9d5ff8da 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt @@ -91,7 +91,7 @@ internal class SwapTransactionSender @AssistedInject constructor( val feeValue = confirmData.fee?.amount?.value ?: return val destination = confirmData.enteredDestination ?: return - val (amount, currencyStatus) = when (confirmData.amountType) { + val (swapDataRequestAmount, swapDataRequestCurrency) = when (confirmData.amountType) { SwapAmountType.From -> { val amountValue = confirmData.enteredFromAmount ?: return val subtracted = FeeCalculationUtils.checkAndCalculateSubtractedAmount( @@ -109,10 +109,15 @@ internal class SwapTransactionSender @AssistedInject constructor( } } + val fromTransactionAmount = when (confirmData.amountType) { + SwapAmountType.From -> swapDataRequestAmount + SwapAmountType.To -> confirmData.enteredFromAmount ?: return + } + val swapData = getSwapDataUseCase( userWallet = userWallet, fromCryptoCurrencyStatus = fromStatus, - amount = amount.toStringWithRightOffset(currencyStatus.currency.decimals), + amount = swapDataRequestAmount.toStringWithRightOffset(swapDataRequestCurrency.currency.decimals), amountType = confirmData.amountType, toCryptoCurrency = toStatus.currency, toAddress = destination, @@ -124,7 +129,7 @@ internal class SwapTransactionSender @AssistedInject constructor( ).getOrElse { error -> onExpressError(error); return } createAndSendCexTransaction( - fromAmount = amount, + fromAmount = fromTransactionAmount, fromStatus = fromStatus, fromAccount = fromAccount, toStatus = toStatus, From 589805424e910206a290e5ad8bb3c8d0fcf3d730 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 Apr 2026 16:07:12 +0500 Subject: [PATCH 12/14] Updated on 2026-08-14 --- .../DefaultPaymentAccountStatusFetcher.kt | 119 +++++++++++++----- .../domain/account/models/AccountList.kt | 18 ++- .../domain/account/models/AccountListTest.kt | 4 +- .../usecase/RecoverCryptoPortfolioUseCase.kt | 2 +- .../tokens/wallet/WalletBalanceFetcher.kt | 39 +++--- .../tokens/wallet/WalletFetchingSource.kt | 2 +- .../viewmodel/TesterAccountsViewModel.kt | 4 +- .../utils/AccountItemsDelegate.kt | 4 +- ...TangemPayHideOnboardingStateTransformer.kt | 3 +- 9 files changed, 129 insertions(+), 66 deletions(-) diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt index cdd78706de..2802bbed8c 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt @@ -10,6 +10,7 @@ import com.tangem.domain.models.account.PaymentAccountStatusValue import com.tangem.domain.models.kyc.KycStatus import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher import com.tangem.domain.pay.model.CustomerInfo +import com.tangem.domain.pay.model.OrderData import com.tangem.domain.pay.model.OrderStatus import com.tangem.domain.pay.repository.CustomerOrderRepository import com.tangem.domain.pay.repository.OnboardingRepository @@ -19,7 +20,11 @@ import com.tangem.security.DeviceSecurityInfoProvider import com.tangem.security.isSecurityExposed import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.logging.TangemLogger +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import javax.inject.Inject +import kotlin.time.Duration.Companion.minutes private const val TAG = "PaymentAccountStatusFetcher" @@ -144,53 +149,103 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( } private suspend fun proceedWithOrderId(account: Account.Payment, orderId: String): PaymentAccountStatusValue { + // Step 1: Check KYC status first + val customerInfo = onboardingRepository.getCustomerInfo(account.userWalletId).fold( + ifLeft = { error -> + logger.e("proceedWithOrderId KYC check ${account.userWalletId} error: $error") + return error.mapToPaymentAccountStatus() + }, + ifRight = { it }, + ) + + logger.i("proceedWithOrderId ${account.userWalletId} kycStatus: ${customerInfo.kycStatus}") + + when (customerInfo.kycStatus) { + KycStatus.PENDING, + KycStatus.INIT, + KycStatus.REJECTED, + -> return customerInfo.mapToPaymentAccountStatus() + KycStatus.APPROVED -> Unit // proceed to order check + } + + // Step 2: Check order status return customerOrderRepository.getOrderData(userWalletId = account.userWalletId, orderId = orderId).fold( ifLeft = { error -> logger.e("proceedWithOrderId ${account.userWalletId} orderId: $orderId error: $error") error.mapToPaymentAccountStatus() }, ifRight = { orderData -> - logger.i("proceedWithOrderId $account.userWalletId: $orderId status: ${orderData.status}") + logger.i("proceedWithOrderId ${account.userWalletId}: $orderId status: ${orderData.status}") when (orderData.status) { - // Kyc is passed and user waits for order creation -> no need to get customer info + OrderStatus.CANCELED -> handleCanceledOrder(account, orderData) + OrderStatus.COMPLETED -> handleCompletedOrder(account) + OrderStatus.UNKNOWN -> PaymentAccountStatusValue.Error.Unavailable OrderStatus.NEW, OrderStatus.PROCESSING, - -> PaymentAccountStatusValue.IssuingCard(source = StatusSource.ACTUAL) - - OrderStatus.CANCELED -> { - onboardingRepository.getCustomerInfo(userWalletId = account.userWalletId) - .fold( - ifLeft = { - PaymentAccountStatusValue.Error.CardIssueFailed( - customerId = orderData.customerId, - ) - }, - ifRight = { customerInfo -> - if (customerInfo.kycStatus == KycStatus.REJECTED) { - customerInfo.mapToPaymentAccountStatus() - } else { - PaymentAccountStatusValue.Error.CardIssueFailed( - customerId = orderData.customerId, - ) - } - }, - ) + -> { + paymentAccountStatusesStore.store( + userWalletId = account.userWalletId, + status = AccountStatus.Payment( + account = account, + value = PaymentAccountStatusValue.IssuingCard(source = StatusSource.ACTUAL), + ), + ) + // Start polling for terminal state + pollOrderStatus(account = account, orderId = orderId) } - OrderStatus.COMPLETED -> { - // Order was completed -> clear order id and get customer info - onboardingRepository.clearOrderId(account.userWalletId) - onboardingRepository.getCustomerInfo(userWalletId = account.userWalletId) - .fold( - ifLeft = { it.mapToPaymentAccountStatus() }, - ifRight = { customerInfo -> customerInfo.mapToPaymentAccountStatus() }, - ) - } - OrderStatus.UNKNOWN -> PaymentAccountStatusValue.Error.Unavailable } }, ) } + private suspend fun pollOrderStatus(account: Account.Payment, orderId: String): PaymentAccountStatusValue { + while (currentCoroutineContext().isActive) { + delay(1.minutes) + + val result = customerOrderRepository.getOrderData( + userWalletId = account.userWalletId, + orderId = orderId, + ) + + result.fold( + ifLeft = { error -> + logger.e("pollOrderStatus ${account.userWalletId} orderId: $orderId error: $error") + // Continue polling on transient errors + }, + ifRight = { orderData -> + logger.i("pollOrderStatus ${account.userWalletId}: $orderId status: ${orderData.status}") + when (orderData.status) { + OrderStatus.CANCELED -> return handleCanceledOrder(account, orderData) + OrderStatus.COMPLETED -> return handleCompletedOrder(account) + OrderStatus.NEW, + OrderStatus.PROCESSING, + OrderStatus.UNKNOWN, + -> Unit // Continue polling + } + }, + ) + } + + return PaymentAccountStatusValue.IssuingCard(source = StatusSource.ACTUAL) + } + + private suspend fun handleCanceledOrder( + account: Account.Payment, + orderData: OrderData, + ): PaymentAccountStatusValue { + onboardingRepository.clearOrderId(account.userWalletId) + return PaymentAccountStatusValue.Error.CardIssueFailed(orderData.customerId) + } + + private suspend fun handleCompletedOrder(account: Account.Payment): PaymentAccountStatusValue { + onboardingRepository.clearOrderId(account.userWalletId) + return onboardingRepository.getCustomerInfo(userWalletId = account.userWalletId) + .fold( + ifLeft = { it.mapToPaymentAccountStatus() }, + ifRight = { customerInfo -> customerInfo.mapToPaymentAccountStatus() }, + ) + } + private fun CustomerInfo.mapToPaymentAccountStatus(): PaymentAccountStatusValue { val cardInfo = this.cardInfo val productInstance = this.productInstance diff --git a/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt b/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt index f4ecc26395..9775180441 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt @@ -41,8 +41,8 @@ data class AccountList private constructor( get() = accounts.first { it is Account.CryptoPortfolio && it.isMainAccount } as Account.CryptoPortfolio /** Returns true if more accounts can be added (the maximum number of accounts has not been reached) */ - val canAddMoreAccounts: Boolean - get() = accounts.size < MAX_ACCOUNTS_COUNT + val canAddMoreCryptoAccounts: Boolean + get() = accounts.filterIsInstance().size < MAX_CRYPTO_PORTFOLIO_ACCOUNTS_COUNT /** Returns the number of active accounts in the list */ val activeAccounts: Int @@ -151,6 +151,11 @@ data class AccountList private constructor( override fun toString(): String = "$tag: The number of accounts must not exceed 20" } + data object ExceedsMaxPaymentAccountsCount : Error { + override fun toString(): String = + "$tag: The number of payment accounts must not exceed $MAX_PAYMENT_ACCOUNTS_COUNT" + } + @Serializable data object DuplicateAccountIds : Error { override fun toString(): String = "$tag: Account list contains duplicate account IDs" @@ -169,7 +174,8 @@ data class AccountList private constructor( companion object { - const val MAX_ACCOUNTS_COUNT = 20 + const val MAX_PAYMENT_ACCOUNTS_COUNT = 1 + const val MAX_CRYPTO_PORTFOLIO_ACCOUNTS_COUNT = 20 const val MAX_ARCHIVED_ACCOUNTS_COUNT = 1000 private const val MAX_MAIN_ACCOUNTS_COUNT = 1 @@ -191,7 +197,11 @@ data class AccountList private constructor( ): Either = either { ensure(accounts.isNotEmpty()) { Error.EmptyAccountsList } - ensure(accounts.size <= MAX_ACCOUNTS_COUNT) { Error.ExceedsMaxAccountsCount } + val paymentAccounts = accounts.filterIsInstance() + ensure(paymentAccounts.size <= MAX_PAYMENT_ACCOUNTS_COUNT) { Error.ExceedsMaxPaymentAccountsCount } + + val cryptoAccounts = accounts.filterIsInstance() + ensure(cryptoAccounts.size <= MAX_CRYPTO_PORTFOLIO_ACCOUNTS_COUNT) { Error.ExceedsMaxAccountsCount } val mainAccountsCount = accounts.mainAccountsCount() ensure(mainAccountsCount == MAX_MAIN_ACCOUNTS_COUNT) { diff --git a/domain/account/src/test/kotlin/com/tangem/domain/account/models/AccountListTest.kt b/domain/account/src/test/kotlin/com/tangem/domain/account/models/AccountListTest.kt index a242546b21..2457408d52 100644 --- a/domain/account/src/test/kotlin/com/tangem/domain/account/models/AccountListTest.kt +++ b/domain/account/src/test/kotlin/com/tangem/domain/account/models/AccountListTest.kt @@ -45,8 +45,8 @@ internal class AccountListTest { val fullAccountList = MockAccounts.fullAccountList // Act & Assert - Truth.assertThat(accountList.canAddMoreAccounts).isTrue() - Truth.assertThat(fullAccountList.canAddMoreAccounts).isFalse() + Truth.assertThat(accountList.canAddMoreCryptoAccounts).isTrue() + Truth.assertThat(fullAccountList.canAddMoreCryptoAccounts).isFalse() } @Test diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt index 36a47dc929..aa119597d6 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt @@ -46,7 +46,7 @@ class RecoverCryptoPortfolioUseCase( val accountList = getAccountList(userWalletId = accountId.userWalletId) - ensure(accountList.canAddMoreAccounts) { + ensure(accountList.canAddMoreCryptoAccounts) { raise(Error.AccountListRequirementsNotMet(cause = AccountList.Error.ExceedsMaxAccountsCount)) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt index 27a9af10ba..7d6d8f4051 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt @@ -160,33 +160,30 @@ class WalletBalanceFetcher internal constructor( paymentAccountRefactorEnabled: Boolean, ) { coroutineScope { - val errorDeferreds = fetchingSources.map { source -> - async { - when (source) { - is WalletFetchingSource.Balance -> { - balanceFetchingOperations.fetchAll( - userWalletId = userWalletId, - currencies = currencies, - sources = source.sources, - ).mapKeys { (fetchingSource, _) -> fetchingSource.name } - } - is WalletFetchingSource.TangemPay -> { - fetchPaymentAccount(userWalletId, paymentAccountRefactorEnabled) - .leftOrNull() - ?.let { error -> mapOf(FetchErrorFormatter.TANGEM_PAY_SOURCE_NAME to error) } - .orEmpty() - } + // Fetch balance sources in parallel + val balanceErrors = fetchingSources.filterIsInstance() + .map { source -> + async { + balanceFetchingOperations.fetchAll( + userWalletId = userWalletId, + currencies = currencies, + sources = source.sources, + ).mapKeys { (fetchingSource, _) -> fetchingSource.name } } } - } + .awaitAll() + .fold(emptyMap()) { acc, map -> acc + map } - val errors = errorDeferreds.awaitAll().fold(emptyMap()) { acc, map -> acc + map } - - check(errors.isEmpty()) { - val message = FetchErrorFormatter.formatWalletErrors(userWalletId, errors) + check(balanceErrors.isEmpty()) { + val message = FetchErrorFormatter.formatWalletErrors(userWalletId, balanceErrors) TangemLogger.e(message) message } + + // Fetch TangemPay separately — may run long-polling, so it must not block balance error checking + if (fetchingSources.any { it is WalletFetchingSource.TangemPay }) { + fetchPaymentAccount(userWalletId, paymentAccountRefactorEnabled) + } } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletFetchingSource.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletFetchingSource.kt index 69a2e52b58..56d00faf1e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletFetchingSource.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletFetchingSource.kt @@ -14,7 +14,7 @@ sealed class WalletFetchingSource { /** * TangemPay account fetching source. - * Handled separately from standard balance sources via [PaymentAccountStatusFetcher]. + * Handled separately from standard balance sources via [com.tangem.domain.pay.flow.PaymentAccountStatusFetcher]. */ data object TangemPay : WalletFetchingSource() diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/viewmodel/TesterAccountsViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/viewmodel/TesterAccountsViewModel.kt index 2bc39f10d0..df07d56fe8 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/viewmodel/TesterAccountsViewModel.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/viewmodel/TesterAccountsViewModel.kt @@ -204,7 +204,7 @@ internal class TesterAccountsViewModel @Inject constructor( var nextIndex = accountList.totalAccounts @Suppress("LoopWithTooManyJumpStatements") // never mind for Tester Menu - while (accountList.canAddMoreAccounts) { + while (accountList.canAddMoreCryptoAccounts) { val derivationIndex = DerivationIndex(nextIndex).getOrNull() ?: break val newAccount = Account.CryptoPortfolio.invoke( @@ -246,7 +246,7 @@ internal class TesterAccountsViewModel @Inject constructor( withContext(dispatchers.default) { val updatedAccountList = AccountList.invoke( userWalletId = accountList.userWalletId, - accounts = if (possibleToArchive > AccountList.MAX_ACCOUNTS_COUNT - 1) { + accounts = if (possibleToArchive > AccountList.MAX_CRYPTO_PORTFOLIO_ACCOUNTS_COUNT - 1) { listOf(accountList.mainAccount) } else { accountList.accounts.subList(fromIndex = 0, toIndex = accountList.accounts.size - possibleToArchive) diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/AccountItemsDelegate.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/AccountItemsDelegate.kt index 56be825aca..c53c4fab07 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/AccountItemsDelegate.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/AccountItemsDelegate.kt @@ -97,7 +97,7 @@ internal class AccountItemsDelegate @Inject constructor( add(header) addAll(accounts.map(::mapAccount).applySortingOrder(order = accountsOrder)) - val isAddAccountEnabled = accounts.size < AccountList.MAX_ACCOUNTS_COUNT + val isAddAccountEnabled = accounts.size < AccountList.MAX_CRYPTO_PORTFOLIO_ACCOUNTS_COUNT val shouldShowDescription = accounts.size > 1 val isArchivedAccountsEnabled = accountStatusList.accountStatuses.size != accountStatusList.totalAccounts @@ -165,7 +165,7 @@ internal class AccountItemsDelegate @Inject constructor( title = resourceReference(R.string.account_add_limit_dialog_title), message = resourceReference( id = R.string.account_add_limit_dialog_description, - formatArgs = wrappedList(AccountList.MAX_ACCOUNTS_COUNT.toString()), + formatArgs = wrappedList(AccountList.MAX_CRYPTO_PORTFOLIO_ACCOUNTS_COUNT.toString()), ), firstActionBuilder = { firstAction }, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayHideOnboardingStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayHideOnboardingStateTransformer.kt index 8c1f641c38..9997dc0bcb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayHideOnboardingStateTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayHideOnboardingStateTransformer.kt @@ -4,6 +4,7 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM +import com.tangem.features.tangempay.entity.TangemPayMainUM internal class TangemPayHideOnboardingStateTransformer( userWalletId: UserWalletId, @@ -11,7 +12,7 @@ internal class TangemPayHideOnboardingStateTransformer( override fun transform(prevState: WalletState): WalletState { return if (prevState is WalletState.MultiCurrency.Content) { - prevState.copy(tangemPayState = TangemPayState.Empty) + prevState.copy(tangemPayState = TangemPayState.Empty, tangemPayMainUM = TangemPayMainUM.Empty) } else { prevState } From c448a1aebbaa95447121e185f62cc7493d2f5f31 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 Apr 2026 14:22:01 +0400 Subject: [PATCH 13/14] Updated on 2026-08-14 --- .../tap/di/domain/WalletsDomainModule.kt | 9 ++++ .../usecase/IsMemoRequiredUseCase.kt | 20 +++++++++ .../send/v2/api/SendNotificationsComponent.kt | 2 +- .../notifications/model/NotificationsModel.kt | 43 ++++++------------- .../model/SwapNotificationsModel.kt | 27 +++++------- .../confirm/SendWithSwapConfirmComponent.kt | 10 ++--- .../confirm/model/SendWithSwapConfirmModel.kt | 9 ++-- .../confirm/ui/SendWithSwapConfirmContent.kt | 1 - 8 files changed, 64 insertions(+), 57 deletions(-) create mode 100644 domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/IsMemoRequiredUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 04f3106b33..e0b447a00d 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -7,6 +7,7 @@ import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.transaction.WalletAddressServiceRepository import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase +import com.tangem.domain.transaction.usecase.IsMemoRequiredUseCase import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.delegate.DefaultUserWalletsSyncDelegate @@ -210,6 +211,14 @@ internal object WalletsDomainModule { return ValidateWalletMemoUseCase(walletAddressServiceRepository = walletAddressServiceRepository) } + @Provides + @Singleton + fun providesIsMemoRequiredUseCase( + walletAddressServiceRepository: WalletAddressServiceRepository, + ): IsMemoRequiredUseCase { + return IsMemoRequiredUseCase(walletAddressServiceRepository = walletAddressServiceRepository) + } + @Provides @Singleton fun providesParseSharedAddressUseCase( diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/IsMemoRequiredUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/IsMemoRequiredUseCase.kt new file mode 100644 index 0000000000..410f8fe048 --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/IsMemoRequiredUseCase.kt @@ -0,0 +1,20 @@ +package com.tangem.domain.transaction.usecase + +import com.tangem.domain.models.network.Network +import com.tangem.domain.transaction.WalletAddressServiceRepository + +class IsMemoRequiredUseCase( + private val walletAddressServiceRepository: WalletAddressServiceRepository, +) { + + suspend operator fun invoke(network: Network, destinationAddress: String): Boolean { + return try { + walletAddressServiceRepository.isMemoRequired( + network = network, + destinationAddress = destinationAddress, + ) + } catch (_: Throwable) { + false + } + } +} \ No newline at end of file diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendNotificationsComponent.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendNotificationsComponent.kt index 5ec0bf75e3..b6c92acf3a 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendNotificationsComponent.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendNotificationsComponent.kt @@ -33,7 +33,7 @@ interface SendNotificationsComponent { val callback: ModelCallback, ) { data class NotificationData( - val destinationAddress: String, + val destinationAddress: String?, val memo: String?, val amountValue: BigDecimal, val reduceAmountBy: BigDecimal, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt index 5cf7dc551b..8d0fd0bb25 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt @@ -34,7 +34,6 @@ import com.tangem.domain.tokens.GetCurrencyCheckUseCase import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase -import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase import com.tangem.domain.utils.convertToSdkAmount import com.tangem.features.send.v2.api.SendNotificationsComponent import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData @@ -69,7 +68,6 @@ internal class NotificationsModel @Inject constructor( private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, private val validateTransactionUseCase: ValidateTransactionUseCase, - private val validateWalletMemoUseCase: ValidateWalletMemoUseCase, private val getTronFeeNotificationShowCountUseCase: GetTronFeeNotificationShowCountUseCase, private val incrementNotificationsShowCountUseCase: IncrementNotificationsShowCountUseCase, private val notificationsUpdateTrigger: SendNotificationsUpdateTrigger, @@ -302,7 +300,7 @@ internal class NotificationsModel @Inject constructor( } private suspend fun MutableList.addWarningNotifications( - destinationAddress: String, + destinationAddress: String?, memo: String?, enteredAmount: BigDecimal, sendingAmount: BigDecimal, @@ -311,14 +309,18 @@ internal class NotificationsModel @Inject constructor( isFeeCoverage: Boolean, currencyCheck: CryptoCurrencyCheck, ) { - val validationError = validateTransactionUseCase( - userWalletId = userWalletId, - amount = enteredAmount.convertToSdkAmount(cryptoCurrencyStatus), - fee = fee, - memo = memo, - destination = destinationAddress, - network = cryptoCurrencyStatus.currency.network, - ).leftOrNull() + val validationError = if (destinationAddress != null) { + validateTransactionUseCase( + userWalletId = userWalletId, + amount = enteredAmount.convertToSdkAmount(cryptoCurrencyStatus), + fee = fee, + memo = memo, + destination = destinationAddress, + network = cryptoCurrencyStatus.currency.network, + ).leftOrNull() + } else { + null + } addRentExemptionNotification( rentWarning = currencyCheck.rentWarning, @@ -352,10 +354,6 @@ internal class NotificationsModel @Inject constructor( params.callback.onAmountReduceTo(reduceTo) }, ) - addDestinationTagRequiredNotification( - isMemoRequired = currencyCheck.isMemoRequired, - memo = memo, - ) addHighFeeWarningNotification( enteredAmountValue = enteredAmount, cryptoCurrencyStatus = cryptoCurrencyStatus, @@ -376,21 +374,6 @@ internal class NotificationsModel @Inject constructor( addTronNetworkFeesNotification() } - private suspend fun MutableList.addDestinationTagRequiredNotification( - isMemoRequired: Boolean, - memo: String?, - ) { - if (!isMemoRequired || contains(NotificationUM.Error.DestinationTagRequired)) return - val isMemoInvalid = memo.isNullOrEmpty() || validateWalletMemoUseCase( - userWalletId = userWalletId, - cryptoCurrency = currency, - memo = memo, - ).isLeft() - if (isMemoInvalid) { - add(NotificationUM.Error.DestinationTagRequired) - } - } - private suspend fun MutableList.addTronNetworkFeesNotification() { val cryptoCurrency = cryptoCurrencyStatus.currency val isTronToken = cryptoCurrency is CryptoCurrency.Token && diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt index 0857fa9cca..f69bf413d1 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt @@ -1,6 +1,5 @@ package com.tangem.features.swap.v2.impl.notifications.model -import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped @@ -9,8 +8,7 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.express.models.ExpressError -import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase -import com.tangem.domain.utils.convertToSdkAmount +import com.tangem.domain.transaction.usecase.IsMemoRequiredUseCase import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger import com.tangem.features.swap.v2.impl.amount.entity.PriceImpact import com.tangem.features.swap.v2.impl.notifications.DefaultSwapNotificationsUpdateTrigger @@ -28,7 +26,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import java.math.BigDecimal import javax.inject.Inject @Suppress("LongParameterList") @@ -38,7 +35,7 @@ internal class SwapNotificationsModel @Inject constructor( private val swapNotificationsUpdateListener: SwapNotificationsUpdateListener, private val swapNotificationsUpdateTrigger: DefaultSwapNotificationsUpdateTrigger, private val swapAmountUpdateTrigger: SwapAmountUpdateTrigger, - private val validateTransactionUseCase: ValidateTransactionUseCase, + private val isMemoRequiredUseCase: IsMemoRequiredUseCase, private val analyticsEventHandler: AnalyticsEventHandler, paramsContainer: ParamsContainer, ) : Model() { @@ -116,20 +113,18 @@ internal class SwapNotificationsModel @Inject constructor( private suspend fun MutableList.addDestinationTagRequiredNotification() { val toCryptoCurrencyStatus = notificationData.toCryptoCurrencyStatus ?: return - val userWalletId = notificationData.userWalletId ?: return val destinationAddress = notificationData.destinationAddress if (destinationAddress.isEmpty()) return - val validationError = validateTransactionUseCase( - amount = BigDecimal.ZERO.convertToSdkAmount(toCryptoCurrencyStatus), - fee = null, - memo = notificationData.memo, - destination = destinationAddress, - userWalletId = userWalletId, - network = toCryptoCurrencyStatus.currency.network, - ).leftOrNull() - - if (validationError is BlockchainSdkError.DestinationTagRequired) { + val isMemoRequired = if (notificationData.memo.isNullOrEmpty()) { + isMemoRequiredUseCase( + network = toCryptoCurrencyStatus.currency.network, + destinationAddress = destinationAddress, + ) + } else { + false + } + if (isMemoRequired) { add(NotificationUM.Error.DestinationTagRequired) } } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt index d5728b155e..8e24591f1f 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt @@ -11,7 +11,6 @@ import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.account.Account -import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.swap.models.SwapDirection @@ -113,10 +112,11 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor( appCurrency = params.appCurrency, callback = model, notificationData = SendNotificationsComponent.Params.NotificationData( - destinationAddress = when (val currency = model.primaryCurrencyStatus.currency) { - is CryptoCurrency.Token -> currency.contractAddress - is CryptoCurrency.Coin -> "0" - }, + /** + * Null when destination is unknown at this point (e.g. CEX swap — address is only known + * after receiving exchange-data). For DEX / DEX_BRIDGE, the address is known upfront. + */ + destinationAddress = null, memo = null, amountValue = model.confirmData.enteredFromAmount.orZero(), reduceAmountBy = model.confirmData.reduceAmountBy.orZero(), diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt index a82e827dc2..3c71e14050 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt @@ -436,10 +436,11 @@ internal class SendWithSwapConfirmModel @Inject constructor( feeUMV2?.feeExtraInfo?.feeCryptoCurrencyStatus ?: params.primaryFeePaidCurrencyStatusFlow.value sendNotificationsUpdateTrigger.triggerUpdate( data = NotificationData( - destinationAddress = when (val currency = primaryCurrencyStatus.currency) { - is CryptoCurrency.Token -> currency.contractAddress - is CryptoCurrency.Coin -> "0" - }, + /** + * Null when destination is unknown at this point (e.g. CEX swap — address is only known + * after receiving exchange-data). For DEX / DEX_BRIDGE, the address is known upfront. + */ + destinationAddress = null, memo = null, amountValue = confirmData.enteredFromAmount.orZero(), reduceAmountBy = confirmData.reduceAmountBy, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/ui/SendWithSwapConfirmContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/ui/SendWithSwapConfirmContent.kt index 1688c9b88a..7005c6acdf 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/ui/SendWithSwapConfirmContent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/ui/SendWithSwapConfirmContent.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.unit.dp import com.tangem.common.ui.notifications.NotificationUM import com.tangem.common.ui.notifications.notifications import com.tangem.core.ui.components.SpacerH16 -import com.tangem.core.ui.extensions.* import com.tangem.features.send.v2.api.FeeSelectorBlockComponent import com.tangem.features.send.v2.api.SendNotificationsComponent import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent From 0098be2fe5fd6d521715ce77ad405642dc6a2738 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 Apr 2026 10:22:25 +0000 Subject: [PATCH 14/14] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 891cb8a686..7dce45b0c8 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,9 +5,9 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.37-1489" +tangemBlockchainSdk = "develop-1487" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.37-603" +tangemCardSdk = "develop-602" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "tangem-master-21" #tangemVico = "0.0.1" # Keep it! - used for local builds ^