From 9b23c9a7a2473b10e49f29cf5bee6819f3ed1856 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Aug 2024 15:32:52 +0500 Subject: [PATCH 1/2] Updated on 2026-08-14 --- .../tap/di/domain/ManageTokensDomainModule.kt | 15 ++ .../card/DefaultDerivationsRepository.kt | 7 +- .../domain/card/MissedDerivationsFinder.kt | 37 ++-- .../data/common/currency/TokensOperations.kt | 7 +- data/manage-tokens/build.gradle.kts | 2 + .../DefaultManageTokensRepository.kt | 204 +++++++++++++++++- .../managetokens/di/ManageTokensDataModule.kt | 12 +- .../utils/ManageTokensUpdateFetcher.kt | 4 +- .../utils/ManagedCryptoCurrencyFactory.kt | 26 ++- .../repository/DefaultCurrenciesRepository.kt | 2 +- .../utils/UserTokensBackwardCompatibility.kt | 2 +- .../card/repository/DerivationsRepository.kt | 4 + .../DefaultWalletManagersFacade.kt | 38 +++- .../walletmanager/WalletManagersFacade.kt | 3 + .../domain/walletmanager/model/TokenInfo.kt | 12 ++ domain/manage-tokens/build.gradle.kts | 9 +- .../model/ManagedCryptoCurrency.kt | 5 +- .../managetokens/SaveManagedTokensUseCase.kt | 33 +++ .../model/ManageTokensUpdateAction.kt | 2 +- .../repository/ManageTokensRepository.kt | 9 + .../PreviewCustomTokenSelectorComponent.kt | 12 +- .../preview/PreviewManageTokensComponent.kt | 13 +- .../entity/item/CurrencyNetworkUM.kt | 4 +- .../entity/managetokens/ManageTokensUM.kt | 3 + .../managetokens/model/ManageTokensModel.kt | 39 +++- .../managetokens/ui/ManageTokensScreen.kt | 9 +- .../utils/list/ChangedCurrenciesManager.kt | 36 ++-- .../utils/list/ManageTokensListManager.kt | 26 +-- .../utils/list/ManageTokensUiActions.kt | 6 +- .../utils/list/ManageTokensUiManager.kt | 8 +- .../utils/mapper/CurrencyNetworksMapper.kt | 4 +- .../utils/ui/CurrencyItemOperations.kt | 4 +- .../utils/ui/CurrencyNetworkOperations.kt | 2 +- 33 files changed, 484 insertions(+), 115 deletions(-) create mode 100644 domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/TokenInfo.kt create mode 100644 domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt index 0dd7b11804..1513f4d484 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt @@ -1,5 +1,8 @@ package com.tangem.tap.di.domain +import com.tangem.domain.card.repository.DerivationsRepository +import com.tangem.domain.managetokens.SaveManagedTokensUseCase +import com.tangem.domain.managetokens.GetManagedTokensUseCase import com.tangem.domain.managetokens.* import com.tangem.domain.managetokens.repository.CustomTokensRepository import com.tangem.domain.managetokens.repository.ManageTokensRepository @@ -44,4 +47,16 @@ internal object ManageTokensDomainModule { ): CheckIsCurrencyNotAddedUseCase { return CheckIsCurrencyNotAddedUseCase(customTokensRepository) } + + @Provides + @Singleton + fun provideSaveManagedTokensUseCase( + manageTokensRepository: ManageTokensRepository, + derivationsRepository: DerivationsRepository, + ): SaveManagedTokensUseCase { + return SaveManagedTokensUseCase( + manageTokensRepository = manageTokensRepository, + derivationsRepository = derivationsRepository, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt index 5f4b695932..a5d6f883b7 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt @@ -12,6 +12,7 @@ import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.ExtendedPublicKeysMap @@ -30,6 +31,10 @@ internal class DefaultDerivationsRepository( ) : DerivationsRepository { override suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List) { + derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network)) + } + + override suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List) { val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found") if (!userWallet.scanResponse.card.settings.isHDWalletAllowed) { @@ -38,7 +43,7 @@ internal class DefaultDerivationsRepository( } val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse) - .find(currencies) + .findByNetworks(networks) .ifEmpty { Timber.d("Nothing to derive") return diff --git a/app/src/main/java/com/tangem/tap/domain/card/MissedDerivationsFinder.kt b/app/src/main/java/com/tangem/tap/domain/card/MissedDerivationsFinder.kt index 97c0a7bf8f..af980fd382 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/MissedDerivationsFinder.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/MissedDerivationsFinder.kt @@ -11,6 +11,7 @@ import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.models.scan.KeyWalletPublicKey import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Network import com.tangem.operations.derivation.ExtendedPublicKeysMap private typealias DerivationData = Pair> @@ -26,8 +27,12 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) { /** Find missed derivations for given currencies [currencies] */ fun find(currencies: List): Derivations { + return currencies.map { it.network }.let(::findByNetworks) + } + + fun findByNetworks(networks: List): Derivations { return buildMap> { - currencies + networks .mapToNewDerivations() .forEach { data -> val current = this[data.first] @@ -41,25 +46,25 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) { } } - private fun List.mapToNewDerivations(): List { + private fun List.mapToNewDerivations(): List { val config = CardConfig.createConfig(scanResponse.card) - return mapNotNull { currency -> - val blockchain = Blockchain.fromId(id = currency.network.id.value) + return mapNotNull { network -> + val blockchain = Blockchain.fromId(id = network.id.value) val curve = config.primaryCurve(blockchain) ?: return@mapNotNull null - findNewDerivations(curve = curve, scanResponse = scanResponse, currency = currency) + findNewDerivations(curve = curve, scanResponse = scanResponse, network = network) } } private fun findNewDerivations( curve: EllipticCurve, scanResponse: ScanResponse, - currency: CryptoCurrency, + network: Network, ): DerivationData? { val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null val publicKey = wallet.publicKey.toMapKey() - val derivationCandidates = currency + val derivationCandidates = network .getDerivationCandidates(curve) .ifEmpty { return null } .filterAlreadyDerivedKeys(publicKey) @@ -68,13 +73,13 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) { return publicKey to derivationCandidates } - private fun CryptoCurrency.getDerivationCandidates(curve: EllipticCurve): List { - val blockchain = Blockchain.fromId(id = network.id.value) + private fun Network.getDerivationCandidates(curve: EllipticCurve): List { + val blockchain = Blockchain.fromId(id = this.id.value) return buildList { add(blockchain.getDerivationPath(curve = curve)) - add(blockchain.getCustomDerivationPath(curve = curve, currency = this@getDerivationCandidates)) - add(blockchain.getCardanoDerivationPathIfNeeded(currency = this@getDerivationCandidates)) + add(blockchain.getCustomDerivationPath(curve = curve, network = this@getDerivationCandidates)) + add(blockchain.getCardanoDerivationPathIfNeeded(network = this@getDerivationCandidates)) } .filterNotNull() .distinct() @@ -88,17 +93,17 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) { } } - private fun Blockchain.getCustomDerivationPath(curve: EllipticCurve, currency: CryptoCurrency): DerivationPath? { + private fun Blockchain.getCustomDerivationPath(curve: EllipticCurve, network: Network): DerivationPath? { return if (getSupportedCurves().contains(curve)) { - currency.network.derivationPath.value?.let(::DerivationPath) + network.derivationPath.value?.let(::DerivationPath) } else { null } } - private fun Blockchain.getCardanoDerivationPathIfNeeded(currency: CryptoCurrency): DerivationPath? { - return if (currency is CryptoCurrency.Coin && this == Blockchain.Cardano) { - currency.network.derivationPath.value?.let { + private fun Blockchain.getCardanoDerivationPathIfNeeded(network: Network): DerivationPath? { + return if (this == Blockchain.Cardano) { + network.derivationPath.value?.let { CardanoUtils.extendedDerivationPath(derivationPath = DerivationPath(it)) } } else { diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/TokensOperations.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/TokensOperations.kt index 2c2c8d95ad..724649c959 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/TokensOperations.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/TokensOperations.kt @@ -4,7 +4,6 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.IconsUtil import com.tangem.blockchainsdk.utils.toCoinId import com.tangem.datasource.api.tangemTech.models.UserTokensResponse -import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency.ID import com.tangem.domain.tokens.model.Network import com.tangem.blockchain.common.Token as SdkToken @@ -65,10 +64,10 @@ fun getCoinIconUrl(blockchain: Blockchain): String? { return coinId?.let(::getTokenIconUrlFromDefaultHost) } -fun List.hasCoinForToken(token: CryptoCurrency.Token): Boolean { +fun List.hasCoinForToken(network: Network): Boolean { return any { - val blockchain = getBlockchain(networkId = token.network.id) - val tokenDerivation = token.network.derivationPath.value + val blockchain = getBlockchain(networkId = network.id) + val tokenDerivation = network.derivationPath.value it.id == blockchain.toCoinId() && it.derivationPath == tokenDerivation } } diff --git a/data/manage-tokens/build.gradle.kts b/data/manage-tokens/build.gradle.kts index 137a893c81..a6acc9a452 100644 --- a/data/manage-tokens/build.gradle.kts +++ b/data/manage-tokens/build.gradle.kts @@ -16,10 +16,12 @@ dependencies { implementation(projects.domain.manageTokens) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) + implementation(projects.domain.legacy) /** Project - Data */ implementation(projects.core.datasource) implementation(projects.data.common) + implementation(projects.data.tokens) /** Project - Utils */ implementation(projects.core.utils) 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 bb45239024..caea014d2c 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 @@ -3,15 +3,27 @@ package com.tangem.data.managetokens import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.isSupportedInApp import com.tangem.blockchainsdk.utils.toNetworkId +import com.tangem.data.common.api.safeApiCall +import com.tangem.data.common.currency.CryptoCurrencyFactory +import com.tangem.data.common.currency.UserTokensResponseFactory +import com.tangem.data.common.currency.getBlockchain +import com.tangem.data.common.currency.hasCoinForToken import com.tangem.data.common.utils.retryOnError import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory +import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.datasource.api.express.TangemExpressApi +import com.tangem.datasource.api.express.models.TangemExpressValues +import com.tangem.datasource.api.express.models.request.AssetsRequestBody +import com.tangem.datasource.api.express.models.request.LeastTokenInfo import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull +import com.tangem.datasource.local.preferences.utils.storeObject +import com.tangem.datasource.local.token.ExpressAssetsStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.common.extensions.supportedBlockchains import com.tangem.domain.common.util.cardTypesResolver @@ -20,7 +32,11 @@ import com.tangem.domain.managetokens.model.ManageTokensListBatchFlow import com.tangem.domain.managetokens.model.ManageTokensListBatchingContext import com.tangem.domain.managetokens.model.ManageTokensListConfig import com.tangem.domain.managetokens.model.ManagedCryptoCurrency +import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork import com.tangem.domain.managetokens.repository.ManageTokensRepository +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.walletmanager.model.TokenInfo import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.pagination.BatchFetchResult @@ -28,16 +44,27 @@ import com.tangem.pagination.BatchListSource import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher import com.tangem.pagination.toBatchFlow import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext +import timber.log.Timber +@Suppress("LongParameterList") internal class DefaultManageTokensRepository( private val tangemTechApi: TangemTechApi, private val userWalletsStore: UserWalletsStore, - private val managedCryptoCurrencyFactory: ManagedCryptoCurrencyFactory, private val manageTokensUpdateFetcher: ManageTokensUpdateFetcher, private val appPreferencesStore: AppPreferencesStore, private val dispatchers: CoroutineDispatcherProvider, + private val walletManagerFacade: WalletManagersFacade, + private val tangemExpressApi: TangemExpressApi, + private val expressAssetsStore: ExpressAssetsStore, ) : ManageTokensRepository { + private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory() + private val cryptoCurrencyFactory = CryptoCurrencyFactory() + private val userTokensResponseFactory = UserTokensResponseFactory() + private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility() + + // region getTokenListBatchFlow override fun getTokenListBatchFlow( context: ManageTokensListBatchingContext, batchSize: Int, @@ -58,7 +85,7 @@ internal class DefaultManageTokensRepository( prefetchDistance = batchSize, batchSize = batchSize, subFetcher = { request, _, isFirstBatchFetching -> - val userWallet = getUserWallet(request.params.userWalletId) + val userWallet = request.params.userWalletId?.let { getUserWallet(it) } val supportedBlockchains = getSupportedBlockchains(userWallet) val searchText = request.params.searchText?.takeIf { it.isNotBlank() } @@ -118,13 +145,9 @@ internal class DefaultManageTokensRepository( } } - private suspend fun getUserWallet(userWalletId: UserWalletId?): UserWallet? { - if (userWalletId == null) { - return null - } - + private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet { return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { - "User wallet not found" + "Unable to find a user wallet with provided ID: $userWalletId" } } @@ -135,4 +158,169 @@ internal class DefaultManageTokensRepository( !it.isTestnet() && it.isSupportedInApp() } } + // endregion + + // region addManagedCurrencies + override suspend fun saveManagedCurrencies( + userWalletId: UserWalletId, + currenciesToAdd: Map>, + currenciesToRemove: Map>, + ) { + return withContext(dispatchers.io) { + val savedCurrencies = requireNotNull( + value = getSavedUserTokensResponseSync(key = userWalletId), + lazyMessage = { "Saved tokens empty. Can not perform add currencies action" }, + ) + + val filteredTokens = currenciesToAdd.mapToTokenDataList() + .filterNot { tokenData -> + val blockchain = getBlockchain(networkId = tokenData.sourceNetwork.network.id) + val networkId = blockchain.toNetworkId() + val contractAddress = (tokenData.sourceNetwork as? SourceNetwork.Default)?.contractAddress + savedCurrencies.tokens.firstOrNull { token -> + token.contractAddress == contractAddress && + token.networkId == networkId && + token.derivationPath == tokenData.sourceNetwork.network.derivationPath.value + } != null + } + val newCoins = createCoinsForNewTokens( + userWalletId = userWalletId, + newTokensNetworks = filteredTokens.mapNotNull { tokenData -> + (tokenData.sourceNetwork as? SourceNetwork.Default)?.network + }, + savedCurrencies = savedCurrencies.tokens, + ) + val tokensToSave = filteredTokens.mapToUserResponseToken() + + val tokensToRemove = currenciesToRemove.mapToTokenDataList() + removeCurrenciesFromWalletManager(userWalletId = userWalletId, tokensDataList = tokensToRemove) + + val newCurrencies = (newCoins + tokensToSave).distinct() + val updatedResponse = savedCurrencies.copy( + tokens = savedCurrencies.tokens + newCurrencies - tokensToRemove.mapToUserResponseToken().toSet(), + ) + storeAndPushTokens(userWalletId = userWalletId, response = updatedResponse) + fetchExchangeableUserMarketCoinsByIds(userWalletId = userWalletId, userTokens = updatedResponse) + } + } + + private suspend fun storeAndPushTokens(userWalletId: UserWalletId, response: UserTokensResponse) { + val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response) + appPreferencesStore.storeObject( + key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue), + value = compatibleUserTokensResponse, + ) + + pushTokens(userWalletId, response) + } + + private suspend fun pushTokens(userWalletId: UserWalletId, response: UserTokensResponse) { + safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, response).bind() }) { + Timber.e(it, "Unable to save user tokens for: ${userWalletId.stringValue}") + } + } + + private suspend fun fetchExchangeableUserMarketCoinsByIds( + userWalletId: UserWalletId, + userTokens: UserTokensResponse, + ) { + try { + val tokensList = userTokens.tokens + .map { + LeastTokenInfo( + contractAddress = it.contractAddress ?: TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE, + network = it.networkId, + ) + } + + if (tokensList.isNotEmpty()) { + val response = tangemExpressApi.getAssets( + AssetsRequestBody( + tokensList = tokensList, + ), + ) + + expressAssetsStore.store(userWalletId, response.getOrThrow()) + } + } catch (e: Throwable) { + Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}") + } + } + + private suspend fun removeCurrenciesFromWalletManager( + userWalletId: UserWalletId, + tokensDataList: List, + ) { + walletManagerFacade.removeTokensByTokenInfo( + userWalletId = userWalletId, + tokenInfos = tokensDataList.mapNotNullTo(hashSetOf()) { tokenData -> + val contractAddress = + (tokenData.sourceNetwork as? SourceNetwork.Default)?.contractAddress ?: return@mapNotNullTo null + TokenInfo( + network = tokenData.sourceNetwork.network, + name = tokenData.token.name, + symbol = tokenData.token.symbol, + contractAddress = contractAddress, + decimals = tokenData.sourceNetwork.decimals, + id = tokenData.token.id.value, + ) + }, + ) + walletManagerFacade.remove( + userWalletId = userWalletId, + networks = tokensDataList.mapNotNullTo(hashSetOf()) { tokenData -> + (tokenData.sourceNetwork as? SourceNetwork.Main)?.network + }, + ) + } + + private suspend fun createCoinsForNewTokens( + userWalletId: UserWalletId, + newTokensNetworks: List, + savedCurrencies: List, + ): List { + return newTokensNetworks + .filterNot { savedCurrencies.hasCoinForToken(it) } // tokens without coins + .mapNotNull { + cryptoCurrencyFactory.createCoin( + blockchain = getBlockchain(networkId = it.id), + extraDerivationPath = it.derivationPath.value, + derivationStyleProvider = getUserWallet(userWalletId).scanResponse.derivationStyleProvider, + ) + } + .distinct() + .map(userTokensResponseFactory::createResponseToken) + } + + private fun List.mapToUserResponseToken(): List { + return map { tokenData -> + val blockchain = getBlockchain(tokenData.sourceNetwork.network.id) + UserTokensResponse.Token( + id = tokenData.token.id.value, + networkId = blockchain.toNetworkId(), + derivationPath = tokenData.sourceNetwork.network.derivationPath.value, + name = tokenData.token.name, + symbol = tokenData.token.symbol, + decimals = tokenData.sourceNetwork.decimals, + contractAddress = (tokenData.sourceNetwork as? SourceNetwork.Default)?.contractAddress, + ) + } + } + + private fun Map>.mapToTokenDataList(): List { + return flatMap { (token, networks) -> + token.availableNetworks + .filter { sourceNetwork -> networks.contains(sourceNetwork.network) } + .map { sourceNetwork -> TokenWithSourceNetwork(token = token, sourceNetwork = sourceNetwork) } + } + } + + private suspend fun getSavedUserTokensResponseSync(key: UserWalletId): UserTokensResponse? { + return appPreferencesStore.getObjectSyncOrNull( + key = PreferencesKeys.getUserTokensKey(key.stringValue), + ) + } + // endregion + + data class TokenWithSourceNetwork(val token: ManagedCryptoCurrency.Token, val sourceNetwork: SourceNetwork) } \ No newline at end of file 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 64d77526c1..37d4a334fb 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 @@ -3,12 +3,14 @@ package com.tangem.data.managetokens.di import com.tangem.data.managetokens.DefaultCustomTokensRepository import com.tangem.data.managetokens.DefaultManageTokensRepository import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher -import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory +import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.token.ExpressAssetsStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.managetokens.repository.CustomTokensRepository import com.tangem.domain.managetokens.repository.ManageTokensRepository +import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -25,18 +27,22 @@ internal object ManageTokensDataModule { fun provideManageTokensRepository( tangemTechApi: TangemTechApi, userWalletsStore: UserWalletsStore, - managedCryptoCurrencyFactory: ManagedCryptoCurrencyFactory, manageTokensUpdateFetcher: ManageTokensUpdateFetcher, appPreferencesStore: AppPreferencesStore, dispatchers: CoroutineDispatcherProvider, + walletManagersFacade: WalletManagersFacade, + tangemExpressApi: TangemExpressApi, + expressAssetsStore: ExpressAssetsStore, ): ManageTokensRepository { return DefaultManageTokensRepository( tangemTechApi, userWalletsStore, - managedCryptoCurrencyFactory, manageTokensUpdateFetcher, appPreferencesStore, dispatchers, + walletManagersFacade, + tangemExpressApi, + expressAssetsStore, ) } diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManageTokensUpdateFetcher.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManageTokensUpdateFetcher.kt index 6c23f37b0b..66bfc00bc5 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManageTokensUpdateFetcher.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManageTokensUpdateFetcher.kt @@ -31,9 +31,9 @@ internal class ManageTokensUpdateFetcher @Inject constructor() : is ManagedCryptoCurrency.Token -> { currency.copy( addedIn = if (updateRequest.isSelected) { - currency.addedIn + updateRequest.networkId + currency.addedIn + updateRequest.network } else { - currency.addedIn - updateRequest.networkId + currency.addedIn - updateRequest.network }, ) } diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt index de239a1f09..c848b36699 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt @@ -13,11 +13,8 @@ import com.tangem.domain.common.DerivationStyleProvider import com.tangem.domain.managetokens.model.ManagedCryptoCurrency import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork import com.tangem.domain.tokens.model.Network -import javax.inject.Inject -import javax.inject.Singleton -@Singleton -internal class ManagedCryptoCurrencyFactory @Inject constructor() { +internal class ManagedCryptoCurrencyFactory { fun create( coinsResponse: CoinsResponse, @@ -100,7 +97,7 @@ internal class ManagedCryptoCurrencyFactory @Inject constructor() { availableNetworks = coinResponse.networks.mapNotNull { network -> createSource(network, derivationStyleProvider) }, - addedIn = findAddedInNetworksIds(coinResponse.id, tokensResponse), + addedIn = findAddedInNetworks(coinResponse.id, tokensResponse, derivationStyleProvider), ) } @@ -119,26 +116,35 @@ internal class ManagedCryptoCurrencyFactory @Inject constructor() { return if (contractAddress.isNullOrBlank()) { SourceNetwork.Main( network = network, + decimals = blockchain.decimals(), ) } else { SourceNetwork.Default( network = network, + decimals = requireNotNull(networkResponse.decimalCount?.toInt()), contractAddress = contractAddress, ) } } - private fun findAddedInNetworksIds(currencyId: String, tokensResponse: UserTokensResponse?): Set { + private fun findAddedInNetworks( + currencyId: String, + tokensResponse: UserTokensResponse?, + derivationStyleProvider: DerivationStyleProvider?, + ): Set { if (tokensResponse == null) return emptySet() return tokensResponse.tokens .filter { it.id == currencyId } - .map { it.networkId } - .mapNotNullTo(mutableSetOf()) { networkId -> - val blockchain = Blockchain.fromNetworkId(networkId) + .mapNotNullTo(mutableSetOf()) { token -> + val blockchain = Blockchain.fromNetworkId(token.networkId) if (blockchain != null && blockchain.isSupportedInApp()) { - Network.ID(blockchain.id) + getNetwork( + blockchain = blockchain, + extraDerivationPath = token.derivationPath, + derivationStyleProvider = derivationStyleProvider, + ) } else { null } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 83d87b583b..93ad36b66f 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -132,7 +132,7 @@ internal class DefaultCurrenciesRepository( savedCurrencies: List, ): List { return newTokens - .filterNot { savedCurrencies.hasCoinForToken(it) } // tokens without coins + .filterNot { savedCurrencies.hasCoinForToken(it.network) } // tokens without coins .mapNotNull { cryptoCurrencyFactory.createCoin( blockchain = getBlockchain(networkId = it.network.id), diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt index ec40d6e52c..a4b3f4116d 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt @@ -9,7 +9,7 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse * Helper to apply compatibility changes for [UserTokensResponse] to support old saved tokens * in new application with new IDs */ -internal class UserTokensBackwardCompatibility { +class UserTokensBackwardCompatibility { fun applyCompatibilityAndGetUpdated(userTokensResponse: UserTokensResponse): UserTokensResponse { return userTokensResponse.copy( diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt index d6b474839a..5c6512a557 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt @@ -3,6 +3,7 @@ package com.tangem.domain.card.repository import com.tangem.common.extensions.ByteArrayKey import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.ExtendedPublicKeysMap @@ -11,6 +12,9 @@ interface DerivationsRepository { @Throws suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List) + @Throws + suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List) + @Throws suspend fun derivePublicKeys( userWalletId: UserWalletId, diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 7b187af646..6d6c0b3a4b 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -31,6 +31,7 @@ import com.tangem.domain.txhistory.models.PaginationWrapper import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryState import com.tangem.domain.walletmanager.model.SmartContractMethod +import com.tangem.domain.walletmanager.model.TokenInfo import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult import com.tangem.domain.walletmanager.utils.* import com.tangem.domain.walletmanager.utils.WalletManagerFactory @@ -103,24 +104,45 @@ class DefaultWalletManagersFacade( tokens .groupBy(CryptoCurrency.Token::network) .forEach { (network, networkTokens) -> - removeTokens(userWalletId, network, networkTokens) + removeTokens( + userWalletId = userWalletId, + network = network, + networkTokens = sdkTokenConverter.convertList(networkTokens), + ) } } - private suspend fun removeTokens( - userWalletId: UserWalletId, - network: Network, - networkTokens: List, - ) { + override suspend fun removeTokensByTokenInfo(userWalletId: UserWalletId, tokenInfos: Set) { + if (tokenInfos.isEmpty()) return + + tokenInfos + .groupBy { it.network } + .forEach { (network, tokenInfoList) -> + removeTokens( + userWalletId = userWalletId, + network = network, + networkTokens = tokenInfoList.map { + Token( + name = it.name, + symbol = it.symbol, + contractAddress = it.contractAddress, + decimals = it.decimals, + id = it.id, + ) + }, + ) + } + } + + private suspend fun removeTokens(userWalletId: UserWalletId, network: Network, networkTokens: List) { withContext(dispatchers.io) { val walletManager = walletManagersStore.getSyncOrNull( userWalletId = userWalletId, blockchain = Blockchain.fromId(network.id.value), derivationPath = network.derivationPath.value, ) ?: return@withContext - val tokensToRemove = sdkTokenConverter.convertList(networkTokens) - tokensToRemove.forEach { token -> + networkTokens.forEach { token -> walletManager.removeToken(token) } diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt index e55a359854..5104db3554 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -17,6 +17,7 @@ import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.txhistory.models.PaginationWrapper import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryState +import com.tangem.domain.walletmanager.model.TokenInfo import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow @@ -54,6 +55,8 @@ interface WalletManagersFacade { suspend fun removeTokens(userWalletId: UserWalletId, tokens: Set) + suspend fun removeTokensByTokenInfo(userWalletId: UserWalletId, tokenInfos: Set) + /** * Returns [UpdateWalletManagerResult] with last pending transactions * diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/TokenInfo.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/TokenInfo.kt new file mode 100644 index 0000000000..9d1952b890 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/TokenInfo.kt @@ -0,0 +1,12 @@ +package com.tangem.domain.walletmanager.model + +import com.tangem.domain.tokens.model.Network + +data class TokenInfo( + val network: Network, + val name: String, + val symbol: String, + val contractAddress: String, + val decimals: Int, + val id: String? = null, +) \ No newline at end of file diff --git a/domain/manage-tokens/build.gradle.kts b/domain/manage-tokens/build.gradle.kts index b24f993032..51a0e5f6ab 100644 --- a/domain/manage-tokens/build.gradle.kts +++ b/domain/manage-tokens/build.gradle.kts @@ -1,8 +1,13 @@ plugins { - alias(deps.plugins.kotlin.jvm) + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) id("configuration") } +android { + namespace = "com.tangem.domain.managetokens" +} + dependencies { /* Domain */ @@ -10,6 +15,8 @@ dependencies { api(projects.domain.core) implementation(projects.domain.wallets.models) implementation(projects.domain.tokens.models) + implementation(projects.domain.card) + implementation(projects.domain.legacy) /* Core */ api(projects.core.pagination) diff --git a/domain/manage-tokens/models/src/main/kotlin/com/tangem/domain/managetokens/model/ManagedCryptoCurrency.kt b/domain/manage-tokens/models/src/main/kotlin/com/tangem/domain/managetokens/model/ManagedCryptoCurrency.kt index f7f82002cf..595a8e1077 100644 --- a/domain/manage-tokens/models/src/main/kotlin/com/tangem/domain/managetokens/model/ManagedCryptoCurrency.kt +++ b/domain/manage-tokens/models/src/main/kotlin/com/tangem/domain/managetokens/model/ManagedCryptoCurrency.kt @@ -42,7 +42,7 @@ sealed class ManagedCryptoCurrency { override val symbol: String, override val iconUrl: String, val availableNetworks: List, - val addedIn: Set, + val addedIn: Set, ) : ManagedCryptoCurrency() { val isAdded: Boolean = addedIn.isNotEmpty() @@ -54,6 +54,7 @@ sealed class ManagedCryptoCurrency { sealed class SourceNetwork { abstract val network: Network + abstract val decimals: Int val id: Network.ID get() = network.id @@ -73,10 +74,12 @@ sealed class ManagedCryptoCurrency { data class Main( override val network: Network, + override val decimals: Int, ) : SourceNetwork() data class Default( override val network: Network, + override val decimals: Int, val contractAddress: String, ) : SourceNetwork() diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt new file mode 100644 index 0000000000..5a6ad0a7ce --- /dev/null +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt @@ -0,0 +1,33 @@ +package com.tangem.domain.managetokens + +import arrow.core.Either +import arrow.core.flatten +import com.tangem.domain.card.repository.DerivationsRepository +import com.tangem.domain.managetokens.model.ManagedCryptoCurrency +import com.tangem.domain.managetokens.repository.ManageTokensRepository +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.wallets.models.UserWalletId + +class SaveManagedTokensUseCase( + private val manageTokensRepository: ManageTokensRepository, + private val derivationsRepository: DerivationsRepository, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + currenciesToAdd: Map>, + currenciesToRemove: Map>, + ): Either { + return Either.catch { + derivationsRepository.derivePublicKeysByNetworks( + userWalletId = userWalletId, + networks = currenciesToAdd.values.flatten(), + ) + manageTokensRepository.saveManagedCurrencies( + userWalletId = userWalletId, + currenciesToAdd = currenciesToAdd, + currenciesToRemove = currenciesToRemove, + ) + } + } +} \ No newline at end of file diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/ManageTokensUpdateAction.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/ManageTokensUpdateAction.kt index 75b1b2e4ad..0d3af7a007 100644 --- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/ManageTokensUpdateAction.kt +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/ManageTokensUpdateAction.kt @@ -6,7 +6,7 @@ sealed class ManageTokensUpdateAction { data class AddCurrency( val currencyId: ManagedCryptoCurrency.ID, - val networkId: Network.ID, + val network: Network, val isSelected: Boolean, ) : ManageTokensUpdateAction() } \ No newline at end of file diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/repository/ManageTokensRepository.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/repository/ManageTokensRepository.kt index 3104d98d64..fab91ff3bb 100644 --- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/repository/ManageTokensRepository.kt +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/repository/ManageTokensRepository.kt @@ -2,8 +2,17 @@ package com.tangem.domain.managetokens.repository import com.tangem.domain.managetokens.model.ManageTokensListBatchFlow import com.tangem.domain.managetokens.model.ManageTokensListBatchingContext +import com.tangem.domain.managetokens.model.ManagedCryptoCurrency +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.wallets.models.UserWalletId interface ManageTokensRepository { fun getTokenListBatchFlow(context: ManageTokensListBatchingContext, batchSize: Int): ManageTokensListBatchFlow + + suspend fun saveManagedCurrencies( + userWalletId: UserWalletId, + currenciesToAdd: Map>, + currenciesToRemove: Map>, + ) } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt index 7d596d3f8d..1ebcff8b5d 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt @@ -51,7 +51,17 @@ internal class PreviewCustomTokenSelectorComponent( ) CurrencyNetworkUM( - networkId = n.id, + network = Network( + id = n.id, + backendId = n.id.value, + name = "", + currencySymbol = "", + derivationPath = Network.DerivationPath.Card(""), + isTestnet = false, + standardType = Network.StandardType.ERC20, + hasFiatFeeRate = false, + canHandleTokens = false, + ), name = "Network $index", type = "N$index", iconResId = R.drawable.ic_eth_16, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt index efa89d5b8e..76be782c89 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt @@ -54,6 +54,7 @@ internal class PreviewManageTokensComponent : ManageTokensComponent { isNextBatchLoading = true, loadMore = { false }, saveChanges = {}, + isSavingInProgress = false, ), ) @@ -134,7 +135,17 @@ internal class PreviewManageTokensComponent : ManageTokensComponent { private fun getCurrencyNetworks(currencyIndex: Int) = List(size = 3) { networkIndex -> CurrencyNetworkUM( - networkId = Network.ID(networkIndex.toString()), + network = Network( + id = Network.ID(networkIndex.toString()), + backendId = networkIndex.toString(), + name = "", + currencySymbol = "", + derivationPath = Network.DerivationPath.Card(""), + isTestnet = false, + standardType = Network.StandardType.ERC20, + hasFiatFeeRate = false, + canHandleTokens = false, + ), name = "NETWORK$networkIndex", type = "N$networkIndex", iconResId = R.drawable.ic_eth_16, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/item/CurrencyNetworkUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/item/CurrencyNetworkUM.kt index 84f3aabb87..b666a09c2a 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/item/CurrencyNetworkUM.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/item/CurrencyNetworkUM.kt @@ -3,7 +3,7 @@ package com.tangem.features.managetokens.entity.item import com.tangem.domain.tokens.model.Network internal data class CurrencyNetworkUM( - val networkId: Network.ID, + val network: Network, val name: String, val type: String, val iconResId: Int, @@ -12,5 +12,5 @@ internal data class CurrencyNetworkUM( override val onSelectedStateChange: (Boolean) -> Unit, ) : SelectableItemUM { - override val id: String = networkId.value + override val id: String = network.id.value } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/ManageTokensUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/ManageTokensUM.kt index 4fc9d627c7..33f1524452 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/ManageTokensUM.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/ManageTokensUM.kt @@ -36,6 +36,7 @@ internal sealed class ManageTokensUM { override val loadMore: () -> Boolean, val saveChanges: () -> Unit, val hasChanges: Boolean, + val isSavingInProgress: Boolean, ) : ManageTokensUM() fun copySealed( @@ -44,6 +45,7 @@ internal sealed class ManageTokensUM { hasChanges: Boolean = this is ManageContent && this.hasChanges, isInitialBatchLoading: Boolean = this.isInitialBatchLoading, isNextBatchLoading: Boolean = this.isNextBatchLoading, + isSavingInProgress: Boolean = this is ManageContent && this.isSavingInProgress, ): ManageTokensUM { return when (this) { is ManageContent -> copy( @@ -52,6 +54,7 @@ internal sealed class ManageTokensUM { hasChanges = hasChanges, isInitialBatchLoading = isInitialBatchLoading, isNextBatchLoading = isNextBatchLoading, + isSavingInProgress = isSavingInProgress, ) is ReadContent -> copy( search = search, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt index 941fdad919..87e6636653 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt @@ -12,6 +12,7 @@ import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.domain.managetokens.SaveManagedTokensUseCase import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.entity.item.CurrencyItemUM @@ -21,12 +22,14 @@ import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM import com.tangem.features.managetokens.impl.R import com.tangem.features.managetokens.utils.list.ChangedCurrencies import com.tangem.features.managetokens.utils.list.ManageTokensListManager +import com.tangem.pagination.BatchFetchResult import com.tangem.pagination.PaginationStatus import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @ComponentScoped @@ -35,6 +38,7 @@ internal class ManageTokensModel @Inject constructor( private val router: Router, private val manageTokensListManager: ManageTokensListManager, private val messageSender: UiMessageSender, + private val saveManagedTokensUseCase: SaveManagedTokensUseCase, paramsContainer: ParamsContainer, ) : Model() { @@ -76,7 +80,7 @@ internal class ManageTokensModel @Inject constructor( popBack = router::pop, isInitialBatchLoading = true, isNextBatchLoading = false, - items = getInitialItems(), + items = persistentListOf(), topBar = ManageTokensTopBarUM.ReadContent( title = resourceReference(R.string.common_search_tokens), onBackButtonClick = router::pop, @@ -97,7 +101,7 @@ internal class ManageTokensModel @Inject constructor( popBack = router::pop, isInitialBatchLoading = true, isNextBatchLoading = false, - items = getInitialItems(), + items = persistentListOf(), topBar = ManageTokensTopBarUM.ManageContent( title = resourceReference(id = R.string.main_manage_tokens), onBackButtonClick = router::pop, @@ -116,6 +120,7 @@ internal class ManageTokensModel @Inject constructor( hasChanges = false, saveChanges = ::saveChanges, loadMore = ::loadMoreItems, + isSavingInProgress = false, ) } @@ -157,9 +162,16 @@ internal class ManageTokensModel @Inject constructor( isNextBatchLoading = false, ) } - is PaginationStatus.Paginating, - is PaginationStatus.EndOfPagination, - -> state.copySealed( + is PaginationStatus.Paginating -> { + (status.lastResult as? BatchFetchResult.Error)?.let { fetchError -> + Timber.e(fetchError.throwable) + } + state.copySealed( + isInitialBatchLoading = false, + isNextBatchLoading = false, + ) + } + is PaginationStatus.EndOfPagination -> state.copySealed( isInitialBatchLoading = false, isNextBatchLoading = false, ) @@ -189,10 +201,6 @@ internal class ManageTokensModel @Inject constructor( return true } - private fun getInitialItems(): ImmutableList { - return persistentListOf() - } - private fun navigateToAddCustomToken() { params.userWalletId?.let { bottomSheetNavigation.activate(BottomSheetConfig.AddCustomToken(it)) @@ -200,7 +208,18 @@ internal class ManageTokensModel @Inject constructor( } private fun saveChanges() { - // TODO: [REDACTED_JIRA] + modelScope.launch { + state.update { state -> state.copySealed(isSavingInProgress = true) } + saveManagedTokensUseCase.invoke( + userWalletId = requireNotNull(params.userWalletId), + currenciesToAdd = manageTokensListManager.currenciesToAdd.value, + currenciesToRemove = manageTokensListManager.currenciesToRemove.value, + ).fold( + ifLeft = { Timber.e(it, "Failed to save changes") }, + ifRight = { router.pop() }, + ) + state.update { state -> state.copySealed(isSavingInProgress = false) } + } } private fun searchCurrencies(query: String) { diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt index 547963c3b0..c8771d1fa6 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt @@ -102,6 +102,7 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), isVisible = state.hasChanges, + showProgress = state.isSavingInProgress, onClick = state.saveChanges, ) } @@ -133,7 +134,12 @@ private fun ManageTokensTopBar(topBar: ManageTokensTopBarUM, search: SearchBarUM } @Composable -private fun SaveChangesButton(isVisible: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) { +private fun SaveChangesButton( + isVisible: Boolean, + showProgress: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { AnimatedVisibility( modifier = modifier, visible = isVisible, @@ -144,6 +150,7 @@ private fun SaveChangesButton(isVisible: Boolean, onClick: () -> Unit, modifier: PrimaryButtonIconEnd( text = stringResource(id = R.string.common_save), iconResId = R.drawable.ic_tangem_24, + showProgress = showProgress, onClick = onClick, ) } diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ChangedCurrenciesManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ChangedCurrenciesManager.kt index 3e2705f9e6..32aa63bf2a 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ChangedCurrenciesManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ChangedCurrenciesManager.kt @@ -5,54 +5,54 @@ import com.tangem.domain.tokens.model.Network import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update -internal typealias ChangedCurrencies = Map> +internal typealias ChangedCurrencies = Map> internal class ChangedCurrenciesManager { val currenciesToAdd: MutableStateFlow = MutableStateFlow(emptyMap()) val currenciesToRemove: MutableStateFlow = MutableStateFlow(emptyMap()) - fun addCurrency(currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) { - updateChangedItems(currencyId, networkId, currenciesToRemove, currenciesToAdd) + fun addCurrency(currency: ManagedCryptoCurrency.Token, network: Network) { + updateChangedItems(currency, network, currenciesToRemove, currenciesToAdd) } - fun removeCurrency(currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) { - updateChangedItems(currencyId, networkId, currenciesToAdd, currenciesToRemove) + fun removeCurrency(currency: ManagedCryptoCurrency.Token, network: Network) { + updateChangedItems(currency, network, currenciesToAdd, currenciesToRemove) } - fun containsCurrency(currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID): Boolean { - return networkId in currenciesToAdd.value[currencyId].orEmpty() || - networkId in currenciesToRemove.value[currencyId].orEmpty() + fun containsCurrency(currency: ManagedCryptoCurrency.Token, network: Network): Boolean { + return network in currenciesToAdd.value[currency].orEmpty() || + network in currenciesToRemove.value[currency].orEmpty() } private fun updateChangedItems( - currencyId: ManagedCryptoCurrency.ID, - networkId: Network.ID, + currency: ManagedCryptoCurrency.Token, + network: Network, removeFromIfPresent: MutableStateFlow, addToIfNotPresent: MutableStateFlow, ) { - val present = removeFromIfPresent.value[currencyId].orEmpty() + val present = removeFromIfPresent.value[currency].orEmpty() - if (networkId in present) { + if (network in present) { removeFromIfPresent.update { items -> items.toMutableMap().apply { - val ids = present - networkId + val ids = present - network if (ids.isEmpty()) { - remove(currencyId) + remove(currency) } else { - set(currencyId, ids) + set(currency, ids) } } } } else { addToIfNotPresent.update { items -> - val alreadyAdded = items[currencyId] ?: emptySet() - if (networkId in alreadyAdded) { + val alreadyAdded = items[currency] ?: emptySet() + if (network in alreadyAdded) { return@update items } - items + (currencyId to alreadyAdded + networkId) + items + (currency to alreadyAdded + network) } } } 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 62ab953402..92dc53736c 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 @@ -59,8 +59,8 @@ internal class ManageTokensListManager @Inject constructor( scopeProvider = Provider { scope }, ) - val currenciesToAdd: StateFlow = changedCurrenciesManager.currenciesToAdd - val currenciesToRemove: StateFlow = changedCurrenciesManager.currenciesToRemove + val currenciesToAdd: StateFlow = changedCurrenciesManager.currenciesToAdd.asStateFlow() + val currenciesToRemove: StateFlow = changedCurrenciesManager.currenciesToRemove.asStateFlow() @OptIn(ExperimentalCoroutinesApi::class) val paginationStatus: Flow> = state @@ -148,32 +148,32 @@ internal class ManageTokensListManager @Inject constructor( } } - override fun addCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) { - changedCurrenciesManager.addCurrency(currencyId, networkId) + override fun addCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network) { + changedCurrenciesManager.addCurrency(currency, network) - sendSelectCurrencyAction(batchKey, currencyId, networkId, isSelected = true) + sendSelectCurrencyAction(batchKey, currency.id, network, isSelected = true) } - override fun removeCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) { - changedCurrenciesManager.removeCurrency(currencyId, networkId) + override fun removeCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network) { + changedCurrenciesManager.removeCurrency(currency, network) - sendSelectCurrencyAction(batchKey, currencyId, networkId, isSelected = false) + sendSelectCurrencyAction(batchKey, currency.id, network, isSelected = false) } override fun checkNeedToShowRemoveNetworkWarning( - currencyId: ManagedCryptoCurrency.ID, - networkId: Network.ID, - ): Boolean = !changedCurrenciesManager.containsCurrency(currencyId, networkId) + currency: ManagedCryptoCurrency.Token, + network: Network, + ): Boolean = !changedCurrenciesManager.containsCurrency(currency, network) private fun sendSelectCurrencyAction( batchKey: Int, currencyId: ManagedCryptoCurrency.ID, - networkId: Network.ID, + network: Network, isSelected: Boolean, ) { val request = ManageTokensUpdateAction.AddCurrency( currencyId = currencyId, - networkId = networkId, + network = network, isSelected = isSelected, ) val action = BatchAction.UpdateBatches( diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiActions.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiActions.kt index 9d73ea1e76..7b9ab64f3f 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiActions.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiActions.kt @@ -6,11 +6,11 @@ import com.tangem.domain.wallets.models.UserWalletId internal interface ManageTokensUiActions { - fun addCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) + fun addCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network) - fun removeCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) + fun removeCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network) - fun checkNeedToShowRemoveNetworkWarning(currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID): Boolean + fun checkNeedToShowRemoveNetworkWarning(currency: ManagedCryptoCurrency.Token, network: Network): Boolean suspend fun checkHasLinkedTokens(userWalletId: UserWalletId, network: Network): Boolean } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt index 546761dcfb..3ca2075789 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt @@ -142,19 +142,19 @@ internal class ManageTokensUiManager( if (currency !is ManagedCryptoCurrency.Token) return@launch if (isSelected) { - actions.addCurrency(batchKey, currency.id, source.id) + actions.addCurrency(batchKey, currency, source.network) } else { - if (actions.checkNeedToShowRemoveNetworkWarning(currency.id, source.id)) { + if (actions.checkNeedToShowRemoveNetworkWarning(currency, source.network)) { showRemoveNetworkWarning( currency = currency, network = source.network, isCoin = source is ManagedCryptoCurrency.SourceNetwork.Main, onConfirm = { - actions.removeCurrency(batchKey, currency.id, source.id) + actions.removeCurrency(batchKey, currency, source.network) }, ) } else { - actions.removeCurrency(batchKey, currency.id, source.id) + actions.removeCurrency(batchKey, currency, source.network) } } } diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/CurrencyNetworksMapper.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/CurrencyNetworksMapper.kt index 71d9885d89..6fdacc516a 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/CurrencyNetworksMapper.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/CurrencyNetworksMapper.kt @@ -16,7 +16,7 @@ internal fun ManagedCryptoCurrency.Token.toUiNetworksModel( NetworksUM.Expanded( networks = availableNetworks.map { it.toUiModel( - isSelected = it.id in addedIn, + isSelected = it.network in addedIn, isEditable = isItemsEditable, onSelectedStateChange = onSelectedStateChange, ) @@ -33,7 +33,7 @@ private fun SourceNetwork.toUiModel( onSelectedStateChange: (SourceNetwork, Boolean) -> Unit, ): CurrencyNetworkUM { return CurrencyNetworkUM( - networkId = id, + network = network, name = network.name.uppercase(), iconResId = id.getIconRes(isColored = isSelected || !isEditable), isSelected = isSelected || !isEditable, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CurrencyItemOperations.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CurrencyItemOperations.kt index d8b0a83776..557b49d960 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CurrencyItemOperations.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CurrencyItemOperations.kt @@ -55,10 +55,10 @@ private fun CurrencyItemUM.Basic.updateNetworks(currency: ManagedCryptoCurrency. is NetworksUM.Collapsed -> networks is NetworksUM.Expanded -> networks.copy( networks = networks.networks.map { network -> - val isSelected = network.networkId in currency.addedIn + val isSelected = network.network in currency.addedIn network.copy( - iconResId = network.networkId.getIconRes(isSelected), + iconResId = network.network.id.getIconRes(isSelected), isSelected = isSelected, ) }.toImmutableList(), diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CurrencyNetworkOperations.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CurrencyNetworkOperations.kt index 562f0db8a2..fbc93fce2c 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CurrencyNetworkOperations.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CurrencyNetworkOperations.kt @@ -8,7 +8,7 @@ import com.tangem.features.managetokens.entity.item.CurrencyNetworkUM internal fun CurrencyNetworkUM.select(isSelected: Boolean): CurrencyNetworkUM { return copy( - iconResId = networkId.getIconRes(isSelected), + iconResId = network.id.getIconRes(isSelected), isSelected = isSelected, ) } From a67d482c3aa3e2292701957367fb457377bdc457 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Aug 2024 14:51:47 +0400 Subject: [PATCH 2/2] Updated on 2026-08-14 --- .../converter/UserWalletItemUMConverter.kt | 2 + .../tokens/GetWalletTotalBalanceUseCase.kt | 4 +- .../portfolio/impl/loader/PortfolioData.kt | 26 +++++ .../impl/loader/PortfolioDataLoader.kt | 105 ++++++++++++++++++ .../impl/ui/WalletSelectorBottomSheet.kt | 2 - .../ui/state/WalletSelectorBSContentUM.kt | 1 - 6 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/loader/PortfolioData.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/loader/PortfolioDataLoader.kt diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt index 704315fc82..163168be81 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt @@ -30,6 +30,7 @@ class UserWalletItemUMConverter( private val balance: TotalFiatBalance? = null, private val isLoading: Boolean = true, private val isBalanceHidden: Boolean = false, + private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None, ) : Converter { override fun convert(value: UserWallet): UserWalletItemUM { @@ -45,6 +46,7 @@ class UserWalletItemUMConverter( ), imageUrl = artworkUrl, isEnabled = !isLocked, + endIcon = endIcon, onClick = { onClick(value.walletId) }, ) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt index 0f4d3477da..012dd2847a 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt @@ -29,7 +29,7 @@ class GetWalletTotalBalanceUseCase( private val stakingRepository: StakingRepository, ) { - suspend operator fun invoke( + operator fun invoke( userTallestIds: Collection, ): LceFlow> { val flows = userTallestIds.distinct() @@ -54,7 +54,7 @@ class GetWalletTotalBalanceUseCase( } @OptIn(ExperimentalCoroutinesApi::class) - suspend operator fun invoke(userWalletId: UserWalletId): LceFlow { + operator fun invoke(userWalletId: UserWalletId): LceFlow { val currenciesStatuses = getStatuses(userWalletId) return currenciesStatuses.transformLatest { maybeStatuses -> diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/loader/PortfolioData.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/loader/PortfolioData.kt new file mode 100644 index 0000000000..8ac05a4e90 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/loader/PortfolioData.kt @@ -0,0 +1,26 @@ +package com.tangem.features.markets.portfolio.impl.loader + +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.TotalFiatBalance +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Portfolio data. Combined data from all flows that required to setup portfolio + * + * @property walletsWithCurrencyStatuses wallets with crypto currency statuses + * @property appCurrency app currency + * @property isBalanceHidden flag that indicates if balance should be hidden + * @property walletsWithBalance wallets with total balance + * +[REDACTED_AUTHOR] + */ +internal data class PortfolioData( + val walletsWithCurrencyStatuses: Map>, + val appCurrency: AppCurrency, + val isBalanceHidden: Boolean, + val walletsWithBalance: Map>, +) \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/loader/PortfolioDataLoader.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/loader/PortfolioDataLoader.kt new file mode 100644 index 0000000000..e2e08f4b70 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/loader/PortfolioDataLoader.kt @@ -0,0 +1,105 @@ +package com.tangem.features.markets.portfolio.impl.loader + +import arrow.core.getOrElse +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.tokens.GetAllWalletsCryptoCurrencyStatusesUseCase +import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.TotalFiatBalance +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.* +import timber.log.Timber +import javax.inject.Inject + +/** + * Loader of portfolio data + * + * @property getAllWalletsCryptoCurrencyStatusesUseCase use case for getting all wallets crypto currency statuses + * @property getSelectedAppCurrencyUseCase use case for getting selected app currency + * @property getBalanceHidingSettingsUseCase use case for getting balance hiding settings + * @property getWalletTotalBalanceUseCase use case for getting wallet total balance + * +[REDACTED_AUTHOR] + */ +internal class PortfolioDataLoader @Inject constructor( + private val getAllWalletsCryptoCurrencyStatusesUseCase: GetAllWalletsCryptoCurrencyStatusesUseCase, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, + private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase, +) { + + /** Load data by [currencyRawId] */ + @OptIn(ExperimentalCoroutinesApi::class) + fun load(currencyRawId: String): Flow { + return combine( + flow = getAllWalletsCryptoCurrencyStatusesFlow(currencyRawId = currencyRawId), + flow2 = getSelectedAppCurrencyFlow(), + flow3 = getBalanceHidingSettingsFlow(), + ) { walletsWithCurrencyStatuses, appCurrency, isBalanceHidden -> + PortfolioData( + walletsWithCurrencyStatuses = walletsWithCurrencyStatuses, + appCurrency = appCurrency, + isBalanceHidden = isBalanceHidden, + walletsWithBalance = emptyMap(), + ) + } + // setup balances for wallets from walletsWithCurrencyStatuses + .flatMapLatest { portfolioData -> + getWalletsWithTotalBalanceFlow( + ids = portfolioData.walletsWithCurrencyStatuses.keys.map(UserWallet::walletId), + ) + .map { portfolioData.copy(walletsWithBalance = it) } + } + } + + private fun getAllWalletsCryptoCurrencyStatusesFlow( + currencyRawId: String, + ): Flow>> { + return getAllWalletsCryptoCurrencyStatusesUseCase(currencyRawId) + .distinctUntilChanged() + .map { walletsWithMaybeStatuses -> + walletsWithMaybeStatuses.mapValues { entry -> + entry.value.mapNotNull { it.getOrNull() } + } + } + } + + private fun getSelectedAppCurrencyFlow(): Flow { + return getSelectedAppCurrencyUseCase() + .map { + it.getOrElse { e -> + Timber.e("Failed to load app currency: $e") + AppCurrency.Default + } + } + .distinctUntilChanged() + } + + private fun getBalanceHidingSettingsFlow(): Flow { + return getBalanceHidingSettingsUseCase() + .map { it.isBalanceHidden } + .distinctUntilChanged() + } + + private fun getWalletsWithTotalBalanceFlow( + ids: List, + ): Flow>> { + return combine( + flows = ids + .map { userWalletId -> + getWalletTotalBalanceUseCase(userWalletId) + .map { userWalletId to it } + .distinctUntilChanged() + }, + transform = { it.toMap() }, + ) + .distinctUntilChanged() + .onEmpty { ids.associateWith { Lce.Loading(partialContent = null) } } + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/WalletSelectorBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/WalletSelectorBottomSheet.kt index a9e190928f..22ba89a8f7 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/WalletSelectorBottomSheet.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/WalletSelectorBottomSheet.kt @@ -109,7 +109,6 @@ private fun Preview() { ), ), onBack = {}, - onWalletSelected = {}, ), ), ) @@ -135,7 +134,6 @@ private fun PreviewContent() { ), ), onBack = {}, - onWalletSelected = {}, ), ) } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/WalletSelectorBSContentUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/WalletSelectorBSContentUM.kt index ee727b3608..fddc12c25e 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/WalletSelectorBSContentUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/WalletSelectorBSContentUM.kt @@ -6,6 +6,5 @@ import kotlinx.collections.immutable.ImmutableList internal data class WalletSelectorBSContentUM( val userWallets: ImmutableList, - val onWalletSelected: (UserWalletItemUM) -> Unit, val onBack: () -> Unit, ) : TangemBottomSheetConfigContent \ No newline at end of file