diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 6af2b76c57..527a4d4667 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -39,7 +39,8 @@ import com.tangem.features.swap.SwapComponent import com.tangem.features.swap.v2.api.SendWithSwapComponent import com.tangem.features.tangempay.components.TangemPayDetailsComponent import com.tangem.features.tangempay.components.TangemPayOnboardingComponent -import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.* +import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.ContinueOnboarding +import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.Deeplink import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.wallet.WalletEntryComponent import com.tangem.features.walletconnect.components.WalletConnectEntryComponent @@ -141,7 +142,6 @@ internal class ChildFactory @Inject constructor( is AppRoute.ManageTokens -> { val source = when (route.source) { AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS - AppRoute.ManageTokens.Source.ONBOARDING -> ManageTokensSource.ONBOARDING AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES } diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 351230132a..8e041577cc 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -126,9 +126,12 @@ sealed class AppRoute(val path: String) : Route { val portfolioId: PortfolioId? = null, ) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/${portfolioId?.stringValue}") { + /** + * Source of launching the screen. + * ManageTokens screen launched from Onboarding by another route. See `OnboardingRoute.ManageTokens`. + */ enum class Source { STORIES, - ONBOARDING, SETTINGS, } } diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt index c5c8b51cdd..0d30385164 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt @@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.blockchainsdk.utils.toNetworkId +import com.tangem.data.common.account.WalletAccountsFetcher import com.tangem.data.common.api.safeApiCall import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.UserTokensResponseFactory @@ -21,15 +22,13 @@ import com.tangem.datasource.local.config.testnet.TestnetTokensStorage import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.common.TapWorkarounds.isTestCard -import com.tangem.domain.card.common.extensions.canHandleBlockchain -import com.tangem.domain.card.common.extensions.canHandleToken -import com.tangem.domain.card.common.extensions.hotWalletExcludedBlockchains -import com.tangem.domain.card.common.extensions.supportedBlockchains -import com.tangem.domain.card.common.extensions.supportedTokens +import com.tangem.domain.card.common.extensions.* import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.managetokens.model.* import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork import com.tangem.domain.managetokens.repository.ManageTokensRepository +import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId @@ -40,7 +39,7 @@ import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher.Request import com.tangem.pagination.toBatchFlow import com.tangem.utils.coroutines.CoroutineDispatcherProvider -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") internal class DefaultManageTokensRepository( private val tangemTechApi: TangemTechApi, private val userWalletsStore: UserWalletsStore, @@ -51,6 +50,7 @@ internal class DefaultManageTokensRepository( private val excludedBlockchains: ExcludedBlockchains, private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val dispatchers: CoroutineDispatcherProvider, + private val walletAccountsFetcher: WalletAccountsFetcher, networkFactory: NetworkFactory, ) : ManageTokensRepository { @@ -94,7 +94,6 @@ internal class DefaultManageTokensRepository( }, ) - @Suppress("ComplexCondition") private suspend fun fetchCurrencies( userWallet: UserWallet?, request: Request, @@ -128,20 +127,22 @@ internal class DefaultManageTokensRepository( ) val tokensResponse = request.params.userWalletId?.let { userWalletId -> - if (loadUserTokensFromRemote && userWallet != null) { - safeApiCall({ tangemTechApi.getUserTokens(userWalletId.stringValue).bind() }) { - // save tokens response only if loadUserTokensFromRemote is true and it means onboarding call - createAndSaveDefaultUserTokensResponse(userWallet = userWallet) + when (val params = request.params) { + is ManageTokensListConfig.Account -> { + fetchUserTokens(userWallet, userWalletId, params, loadUserTokensFromRemote) + } + is ManageTokensListConfig.Wallet -> { + fetchUserTokensLegacy(userWallet, userWalletId, loadUserTokensFromRemote) } - } else { - getSavedUserTokensResponseSync(userWalletId) } } - val items = if (isFirstBatchFetching && + + val isCreateWithCustom = isFirstBatchFetching && tokensResponse != null && userWallet != null && query == null - ) { + + val items = if (isCreateWithCustom) { managedCryptoCurrencyFactory.createWithCustomTokens( coinsResponse = updatedCoinsResponse, tokensResponse = tokensResponse, @@ -162,6 +163,56 @@ internal class DefaultManageTokensRepository( ) } + private suspend fun fetchUserTokens( + userWallet: UserWallet?, + userWalletId: UserWalletId, + params: ManageTokensListConfig.Account, + loadUserTokensFromRemote: Boolean, + ): UserTokensResponse? { + val accountId = when { + params.accountId == null -> { + return null + } + loadUserTokensFromRemote -> { + AccountId.forCryptoPortfolio(userWalletId = userWalletId, derivationIndex = DerivationIndex.Main) + } + else -> requireNotNull(params.accountId) + } + + val response = if (loadUserTokensFromRemote && userWallet != null) { + runCatching { walletAccountsFetcher.fetch(userWalletId = userWallet.walletId) }.getOrNull() + } else { + walletAccountsFetcher.getSaved(userWalletId) + } + + val account = response?.accounts?.firstOrNull { it.id == accountId.value } + ?: return null + + return UserTokensResponse( + group = response.wallet.group, + sort = response.wallet.sort, + tokens = account.tokens.orEmpty(), + ) + } + + private suspend fun fetchUserTokensLegacy( + userWallet: UserWallet?, + userWalletId: UserWalletId, + loadUserTokensFromRemote: Boolean, + ): UserTokensResponse? { + return if (loadUserTokensFromRemote && userWallet != null) { + safeApiCall( + call = { tangemTechApi.getUserTokens(userWalletId.stringValue).bind() }, + onError = { + // save tokens response only if loadUserTokensFromRemote is true and it means onboarding call + createAndSaveDefaultUserTokensResponse(userWallet = userWallet) + }, + ) + } else { + getSavedUserTokensResponseSync(userWalletId) + } + } + private suspend fun createAndSaveDefaultUserTokensResponse(userWallet: UserWallet): UserTokensResponse { val userTokensResponse = createDefaultUserTokensResponse(userWallet) userTokenSaver.store(userWallet.walletId, userTokensResponse, useEnricher = false) diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt index 412155dcda..28341d6124 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt @@ -1,6 +1,7 @@ package com.tangem.data.managetokens.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.account.WalletAccountsFetcher import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.common.network.NetworkFactory @@ -38,6 +39,7 @@ internal object ManageTokensDataModule { excludedBlockchains: ExcludedBlockchains, cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, networkFactory: NetworkFactory, + walletAccountsFetcher: WalletAccountsFetcher, ): ManageTokensRepository { return DefaultManageTokensRepository( tangemTechApi = tangemTechApi, @@ -50,6 +52,7 @@ internal object ManageTokensDataModule { cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, networkFactory = networkFactory, dispatchers = dispatchers, + walletAccountsFetcher = walletAccountsFetcher, ) } 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 e8c9a00296..8a5f3346ec 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 @@ -53,25 +53,27 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo params = SingleAccountListProducer.Params(params.userWalletId), ) + @Suppress("UnusedFlow") return accountListFlow.flatMapLatest { accountList -> - val accountStatusFlows = accountList.accounts - .filterIsInstance() - .map { account -> - if (account.cryptoCurrencies.isEmpty()) { - createEmptyAccountStatusFlow(account) - } else { - val userWallet = userWalletsListRepository.userWalletsSync().first { - it.walletId == params.userWalletId - } + val accountStatusFlows = accountList.accounts.mapNotNull { account -> + if (account !is Account.CryptoPortfolio) return@mapNotNull null - getAccountStatusFlow( - userWallet = userWallet, - account = account, - groupType = accountList.groupType, - sortType = accountList.sortType, - ) + if (account.cryptoCurrencies.isEmpty()) { + createEmptyAccountStatusFlow(account) + } else { + val userWallet = userWalletsListRepository.userWalletsSync().first { + it.walletId == params.userWalletId } + + getAccountStatusFlow( + userWallet = userWallet, + account = account, + groupType = accountList.groupType, + sortType = accountList.sortType, + ) } + .distinctUntilChanged() + } combine(accountStatusFlows) { accountStatuses -> val balances = accountStatuses.map { it.tokenList.totalFiatBalance } @@ -84,6 +86,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo ) } } + .distinctUntilChanged() .flowOn(dispatchers.default) } @@ -108,6 +111,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo ): Flow { val statusesFlows = account.cryptoCurrencies.map { currency -> cryptoCurrencyStatusesFlowFactory.create(userWallet = userWallet, currency = currency) + .distinctUntilChanged() } return combine(statusesFlows) { statuses -> @@ -123,6 +127,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo priceChangeLce = PriceChangeCalculator.calculate(statuses = statusList), ) } + .distinctUntilChanged() } @AssistedFactory diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyStatusesFlowFactory.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyStatusesFlowFactory.kt index bd7df0e9f4..b4597e4746 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyStatusesFlowFactory.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyStatusesFlowFactory.kt @@ -62,7 +62,6 @@ internal class CryptoCurrencyStatusesFlowFactory @Inject constructor( CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading), ) } - .conflate() .distinctUntilChanged() } @@ -88,6 +87,7 @@ internal class CryptoCurrencyStatusesFlowFactory @Inject constructor( val quoteStatusFlow = currency.id.rawCurrencyId?.let(::getQuoteStatusFlow) return combine(networkStatusFlow, yieldBalanceFlow, quoteStatusFlow) + .distinctUntilChanged() } private fun combine( @@ -120,7 +120,6 @@ internal class CryptoCurrencyStatusesFlowFactory @Inject constructor( return singleNetworkStatusSupplier( params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network), ) - .conflate() .distinctUntilChanged() } @@ -128,7 +127,6 @@ internal class CryptoCurrencyStatusesFlowFactory @Inject constructor( return singleQuoteStatusSupplier( params = SingleQuoteStatusProducer.Params(rawCurrencyId = rawCurrencyId), ) - .conflate() .distinctUntilChanged() } @@ -147,7 +145,6 @@ internal class CryptoCurrencyStatusesFlowFactory @Inject constructor( singleYieldBalanceSupplier( params = SingleYieldBalanceProducer.Params(userWalletId = userWalletId, stakingId = stakingId), ) - .conflate() .distinctUntilChanged() } else { flowOf(null) diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/ManageTokensListConfig.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/ManageTokensListConfig.kt index 9c4cda460b..8ac66e350c 100644 --- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/ManageTokensListConfig.kt +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/ManageTokensListConfig.kt @@ -1,8 +1,26 @@ package com.tangem.domain.managetokens.model +import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.wallet.UserWalletId -data class ManageTokensListConfig( - val userWalletId: UserWalletId?, - val searchText: String?, -) \ No newline at end of file +sealed interface ManageTokensListConfig { + + val userWalletId: UserWalletId? + val searchText: String? + + // old way + data class Wallet( + override val userWalletId: UserWalletId?, + override val searchText: String?, + ) : ManageTokensListConfig + + // new way + data class Account( + val accountId: AccountId?, + override val searchText: String?, + ) : ManageTokensListConfig { + + override val userWalletId: UserWalletId? + get() = accountId?.userWalletId + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt index ae20c719d8..60b26282fb 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt @@ -9,9 +9,13 @@ import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.message.DialogMessage +import com.tangem.domain.managetokens.CreateCryptoCurrencyUseCase +import com.tangem.domain.managetokens.FindTokenUseCase +import com.tangem.domain.managetokens.ValidateTokenFormUseCase import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network +import com.tangem.domain.wallets.usecase.ColdWalletAndHasMissedDerivationsUseCase import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent import com.tangem.features.managetokens.component.CustomTokenFormComponent import com.tangem.features.managetokens.entity.customtoken.ClickableFieldUM @@ -41,6 +45,10 @@ internal class CustomTokenFormModel @Inject constructor( private val messageSender: UiMessageSender, private val customTokenFormManager: CustomCurrencyFormBuilder, private val analyticsEventHandler: AnalyticsEventHandler, + private val coldWalletAndHasMissedDerivationsUseCase: ColdWalletAndHasMissedDerivationsUseCase, + private val createCryptoCurrencyUseCase: CreateCryptoCurrencyUseCase, + private val findTokenUseCase: FindTokenUseCase, + private val validateTokenFormUseCase: ValidateTokenFormUseCase, paramsContainer: ParamsContainer, customTokenFormUseCasesFacadeFactory: CustomTokenFormUseCasesFacade.Factory, ) : Model() { @@ -48,7 +56,13 @@ internal class CustomTokenFormModel @Inject constructor( private val params: CustomTokenFormComponent.Params = paramsContainer.require() private var createdCurrency: CryptoCurrency? = null private var useCasesFacade: CustomTokenFormUseCasesFacade = customTokenFormUseCasesFacadeFactory.create(params.mode) - private val customCurrencyValidator = CustomCurrencyValidator(useCasesFacade) + private val customCurrencyValidator = CustomCurrencyValidator( + userWalletId = params.mode.userWalletId, + useCasesFacade = useCasesFacade, + createCryptoCurrencyUseCase = createCryptoCurrencyUseCase, + findTokenUseCase = findTokenUseCase, + validateTokenFormUseCase = validateTokenFormUseCase, + ) val state: MutableStateFlow = MutableStateFlow( value = getInitialState(), @@ -164,8 +178,9 @@ internal class CustomTokenFormModel @Inject constructor( isAlreadyAdded: Boolean, isCustom: Boolean, ) = modelScope.launch { - val needColdWalletInteraction = useCasesFacade.needColdWalletInteraction( - network = mapOf(currency.network.backendId to getDerivationPath().value), + val needColdWalletInteraction = coldWalletAndHasMissedDerivationsUseCase.invoke( + userWalletId = params.mode.userWalletId, + networksWithDerivationPath = mapOf(currency.network.backendId to getDerivationPath().value), ) state.update { state -> diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyValidator.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyValidator.kt index feabc9cb4d..698d65bd5b 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyValidator.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyValidator.kt @@ -1,11 +1,15 @@ package com.tangem.features.managetokens.utils import arrow.core.getOrElse +import com.tangem.domain.managetokens.CreateCryptoCurrencyUseCase +import com.tangem.domain.managetokens.FindTokenUseCase +import com.tangem.domain.managetokens.ValidateTokenFormUseCase import com.tangem.domain.managetokens.model.AddCustomTokenForm import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException import com.tangem.domain.managetokens.model.exceptoin.FindTokenException import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.features.managetokens.utils.list.CustomTokenFormUseCasesFacade import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveInAndJoin @@ -15,7 +19,11 @@ import kotlinx.coroutines.launch import timber.log.Timber internal class CustomCurrencyValidator( + private val userWalletId: UserWalletId, private val useCasesFacade: CustomTokenFormUseCasesFacade, + private val createCryptoCurrencyUseCase: CreateCryptoCurrencyUseCase, + private val findTokenUseCase: FindTokenUseCase, + private val validateTokenFormUseCase: ValidateTokenFormUseCase, ) { private val validateFormJobHolder = JobHolder() @@ -41,10 +49,7 @@ internal class CustomCurrencyValidator( ) = coroutineScope { updateStatus(Status.Validating) - val result = useCasesFacade.validateTokenFormUseCase( - networkId = networkId, - formValues = formValues, - ) + val result = validateTokenFormUseCase(networkId = networkId, formValues = formValues) val validatedForm = result.getOrElse { e -> updateStatus(Status.FormValidationException(e)) @@ -90,7 +95,8 @@ internal class CustomCurrencyValidator( updateStatus(Status.SearchingToken) - val foundToken = useCasesFacade.findTokenUseCase( + val foundToken = findTokenUseCase.invoke( + userWalletId = userWalletId, contractAddress = validatedForm.contractAddress, networkId = networkId, derivationPath = derivationPath, @@ -121,7 +127,8 @@ internal class CustomCurrencyValidator( ) { updateStatus(Status.SearchingToken) - val token = useCasesFacade.findTokenUseCase( + val token = findTokenUseCase.invoke( + userWalletId = userWalletId, contractAddress = validatedForm.contractAddress, networkId = networkId, derivationPath = derivationPath, @@ -148,7 +155,8 @@ internal class CustomCurrencyValidator( derivationPath: Network.DerivationPath, validatedForm: AddCustomTokenForm.Validated.All?, ) { - val currency = useCasesFacade.createCryptoCurrencyUseCase( + val currency = createCryptoCurrencyUseCase.invoke( + userWalletId = userWalletId, networkId = networkId, derivationPath = derivationPath, formValues = validatedForm, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/CustomTokenFormUseCasesFacade.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/CustomTokenFormUseCasesFacade.kt index fbbeebc628..7e9bcaafc8 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/CustomTokenFormUseCasesFacade.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/CustomTokenFormUseCasesFacade.kt @@ -1,47 +1,30 @@ package com.tangem.features.managetokens.utils.list import arrow.core.Either -import arrow.core.NonEmptyList +import arrow.core.left import arrow.core.right +import com.tangem.domain.account.producer.SingleAccountProducer import com.tangem.domain.account.status.usecase.SaveCryptoCurrenciesUseCase +import com.tangem.domain.account.supplier.SingleAccountSupplier import com.tangem.domain.managetokens.CheckIsCurrencyNotAddedUseCase -import com.tangem.domain.managetokens.CreateCryptoCurrencyUseCase -import com.tangem.domain.managetokens.FindTokenUseCase -import com.tangem.domain.managetokens.ValidateTokenFormUseCase -import com.tangem.domain.managetokens.model.AddCustomTokenForm -import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException -import com.tangem.domain.managetokens.model.exceptoin.FindTokenException import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase -import com.tangem.domain.wallets.usecase.ColdWalletAndHasMissedDerivationsUseCase import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase import com.tangem.features.managetokens.component.AddCustomTokenMode import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -@Suppress("LongParameterList") internal class CustomTokenFormUseCasesFacade @AssistedInject constructor( private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, private val derivePublicKeysUseCase: DerivePublicKeysUseCase, - private val validateTokenFormUseCase: ValidateTokenFormUseCase, - private val createCryptoCurrencyUseCase: CreateCryptoCurrencyUseCase, - private val findTokenUseCase: FindTokenUseCase, private val checkIsCurrencyNotAddedUseCase: CheckIsCurrencyNotAddedUseCase, - private val coldWalletAndHasMissedDerivationsUseCase: ColdWalletAndHasMissedDerivationsUseCase, private val saveCryptoCurrenciesUseCase: SaveCryptoCurrenciesUseCase, + private val singleAccountSupplier: SingleAccountSupplier, @Assisted private val mode: AddCustomTokenMode, ) { - suspend fun needColdWalletInteraction(network: Map): Boolean = when (mode) { - is AddCustomTokenMode.Account -> TODO("Account") - is AddCustomTokenMode.Wallet -> coldWalletAndHasMissedDerivationsUseCase.invoke( - userWalletId = mode.userWalletId, - networksWithDerivationPath = network, - ) - } - suspend fun addCryptoCurrenciesUseCase(currency: CryptoCurrency): Either = when (mode) { is AddCustomTokenMode.Account -> { saveCryptoCurrenciesUseCase(accountId = mode.accountId, add = currency) @@ -67,7 +50,19 @@ internal class CustomTokenFormUseCasesFacade @AssistedInject constructor( derivationPath: Network.DerivationPath, contractAddress: String?, ): Either = when (mode) { - is AddCustomTokenMode.Account -> TODO("Account") + is AddCustomTokenMode.Account -> { + val account = singleAccountSupplier.getSyncOrNull( + params = SingleAccountProducer.Params(accountId = mode.accountId), + ) + ?: return IllegalStateException("Account not found").left() + + account.cryptoCurrencies.none { currency -> + networkId == currency.network.id && + derivationPath == currency.network.derivationPath && + contractAddress.equals(currency.id.contractAddress, ignoreCase = true) + } + .right() + } is AddCustomTokenMode.Wallet -> checkIsCurrencyNotAddedUseCase.invoke( userWalletId = mode.userWalletId, networkId = networkId, @@ -76,45 +71,6 @@ internal class CustomTokenFormUseCasesFacade @AssistedInject constructor( ) } - suspend fun createCryptoCurrencyUseCase( - networkId: Network.ID, - derivationPath: Network.DerivationPath, - formValues: AddCustomTokenForm.Validated.All?, - ): Either = when (mode) { - is AddCustomTokenMode.Account -> TODO("Account") - is AddCustomTokenMode.Wallet -> createCryptoCurrencyUseCase.invoke( - userWalletId = mode.userWalletId, - networkId = networkId, - derivationPath = derivationPath, - formValues = formValues, - ) - } - - suspend fun findTokenUseCase( - contractAddress: String, - networkId: Network.ID, - derivationPath: Network.DerivationPath, - ): Either = when (mode) { - is AddCustomTokenMode.Account -> TODO("Account") - is AddCustomTokenMode.Wallet -> findTokenUseCase.invoke( - userWalletId = mode.userWalletId, - contractAddress = contractAddress, - networkId = networkId, - derivationPath = derivationPath, - ) - } - - suspend fun validateTokenFormUseCase( - networkId: Network.ID, - formValues: AddCustomTokenForm.Raw, - ): Either, AddCustomTokenForm.Validated> = when (mode) { - is AddCustomTokenMode.Account -> TODO("Account") - is AddCustomTokenMode.Wallet -> validateTokenFormUseCase.invoke( - networkId = networkId, - formValues = formValues, - ) - } - @AssistedFactory interface Factory { fun create(mode: AddCustomTokenMode): CustomTokenFormUseCasesFacade diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt index 9398d31472..59bf275e11 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt @@ -90,8 +90,9 @@ internal class ManageTokensListManager @AssistedInject constructor( */ suspend fun launchPagination(isCollapsed: Boolean) = coroutineScope { val loadUserTokensFromRemote = when (mode) { - is ManageTokensMode.Wallet -> source == ManageTokensSource.ONBOARDING + is ManageTokensMode.Wallet, is ManageTokensMode.Account, + -> source == ManageTokensSource.ONBOARDING ManageTokensMode.None, -> false } diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUseCasesFacade.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUseCasesFacade.kt index 8340f73bba..4fc1bd6e8f 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUseCasesFacade.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUseCasesFacade.kt @@ -2,7 +2,11 @@ package com.tangem.features.managetokens.utils.list import arrow.core.Either import arrow.core.left +import arrow.core.right +import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles +import com.tangem.domain.account.producer.SingleAccountProducer import com.tangem.domain.account.status.usecase.SaveCryptoCurrenciesUseCase +import com.tangem.domain.account.supplier.SingleAccountSupplier import com.tangem.domain.managetokens.* import com.tangem.domain.managetokens.model.CurrencyUnsupportedState import com.tangem.domain.managetokens.model.ManageTokensListConfig @@ -28,6 +32,8 @@ internal class ManageTokensUseCasesFacade @AssistedInject constructor( private val saveManagedTokensUseCase: SaveManagedTokensUseCase, private val saveCryptoCurrenciesUseCase: SaveCryptoCurrenciesUseCase, private val customTokensRepository: CustomTokensRepository, + private val accountsFeatureToggles: AccountsFeatureToggles, + private val singleAccountSupplier: SingleAccountSupplier, @Assisted private val mode: ManageTokensMode, ) { @@ -35,12 +41,21 @@ internal class ManageTokensUseCasesFacade @AssistedInject constructor( get() = IllegalStateException("Unsupported") fun manageTokensListConfig(searchText: String?): ManageTokensListConfig { - val userWalletId: UserWalletId? = when (mode) { - is ManageTokensMode.Account -> TODO("Account") - ManageTokensMode.None -> null - is ManageTokensMode.Wallet -> mode.userWalletId + return when (mode) { + is ManageTokensMode.Account -> { + ManageTokensListConfig.Account(accountId = mode.accountId, searchText = searchText) + } + is ManageTokensMode.Wallet -> { + ManageTokensListConfig.Wallet(userWalletId = mode.userWalletId, searchText = searchText) + } + ManageTokensMode.None -> { + if (accountsFeatureToggles.isFeatureEnabled) { + ManageTokensListConfig.Account(accountId = null, searchText = searchText) + } else { + ManageTokensListConfig.Wallet(userWalletId = null, searchText = searchText) + } + } } - return ManageTokensListConfig(userWalletId, searchText) } suspend fun removeCustomCurrencyUseCase(customCurrency: ManagedCryptoCurrency.Custom): Either { @@ -67,7 +82,21 @@ internal class ManageTokensUseCasesFacade @AssistedInject constructor( tempRemovedTokens: Map>, ): Either { return when (mode) { - is ManageTokensMode.Account -> TODO("Account") + is ManageTokensMode.Account -> { + val added = tempAddedTokens.mapToCryptoCurrencies(userWalletId = mode.accountId.userWalletId) + val removed = tempRemovedTokens.mapToCryptoCurrencies(userWalletId = mode.accountId.userWalletId) + + val account = singleAccountSupplier.getSyncOrNull( + params = SingleAccountProducer.Params(accountId = mode.accountId), + ) + ?: return IllegalStateException("Account not found").left() + + (account.cryptoCurrencies + added - removed).any { + it is CryptoCurrency.Token && it.network.backendId == network.backendId && + it.network.derivationPath == network.derivationPath + } + .right() + } is ManageTokensMode.Wallet -> checkHasLinkedTokensUseCase.invoke( userWalletId = mode.userWalletId, network = network, @@ -82,7 +111,10 @@ internal class ManageTokensUseCasesFacade @AssistedInject constructor( sourceNetwork: ManagedCryptoCurrency.SourceNetwork, ): Either { return when (mode) { - is ManageTokensMode.Account -> TODO("Account") + is ManageTokensMode.Account -> checkCurrencyUnsupportedUseCase.invoke( + userWalletId = mode.accountId.userWalletId, + sourceNetwork = sourceNetwork, + ) is ManageTokensMode.Wallet -> checkCurrencyUnsupportedUseCase.invoke( userWalletId = mode.userWalletId, sourceNetwork = sourceNetwork, @@ -92,7 +124,10 @@ internal class ManageTokensUseCasesFacade @AssistedInject constructor( } suspend fun needColdWalletInteraction(network: Map): Boolean = when (mode) { - is ManageTokensMode.Account -> TODO("Account") + is ManageTokensMode.Account -> coldWalletAndHasMissedDerivationsUseCase.invoke( + userWalletId = mode.accountId.userWalletId, + networksWithDerivationPath = network, + ) is ManageTokensMode.Wallet -> coldWalletAndHasMissedDerivationsUseCase.invoke( userWalletId = mode.userWalletId, networksWithDerivationPath = network,