From 0d185108da0cf016a68be7c5794ab7066868e64b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 3 Sep 2024 13:25:28 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/MarketsDomainModule.kt | 12 ++ .../card/DefaultDerivationsRepository.kt | 15 ++ data/markets/build.gradle.kts | 11 +- .../markets/DefaultMarketsTokenRepository.kt | 41 ++++ .../data/markets/di/MarketsDataModule.kt | 3 + .../card/repository/DerivationsRepository.kt | 2 + domain/markets/build.gradle.kts | 6 +- .../domain/markets/SaveMarketTokensUseCase.kt | 60 ++++++ .../repositories/MarketsTokenRepository.kt | 8 + .../model/AddToPortfolioBSContentUMFactory.kt | 24 ++- .../impl/model/AddToPortfolioManager.kt | 188 ++++++++++++++++++ .../impl/model/MarketsPortfolioModel.kt | 85 ++++---- .../impl/model/MyPortfolioUMFactory.kt | 62 ++---- .../portfolio/impl/model/PortfolioUIData.kt | 4 +- 14 files changed, 431 insertions(+), 90 deletions(-) create mode 100644 domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioManager.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt index f32af0c735..293ec473c3 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt @@ -1,7 +1,9 @@ package com.tangem.tap.di.domain +import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.domain.markets.* import com.tangem.domain.markets.repositories.MarketsTokenRepository +import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.QuotesRepository import dagger.Module import dagger.Provides @@ -44,4 +46,14 @@ object MarketsDomainModule { fun provideGetTokenQuotesUseCase(quotesRepository: QuotesRepository): GetCurrencyQuotesUseCase { return GetCurrencyQuotesUseCase(quotesRepository = quotesRepository) } + + @Provides + @Singleton + fun provideSaveMarketTokensUseCase( + derivationsRepository: DerivationsRepository, + marketsTokenRepository: MarketsTokenRepository, + currenciesRepository: CurrenciesRepository, + ): SaveMarketTokensUseCase { + return SaveMarketTokensUseCase(derivationsRepository, marketsTokenRepository, currenciesRepository) + } } \ 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 4390c3ca9f..72b36175ef 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 @@ -38,6 +38,21 @@ internal class DefaultDerivationsRepository( derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network)) } + override suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List) { + val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found") + + derivePublicKeysByNetworks( + userWalletId = userWalletId, + networks = networkIds.mapNotNull { + getNetwork( + blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, + extraDerivationPath = null, + derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider, + ) + }, + ) + } + override suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List) { val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found") diff --git a/data/markets/build.gradle.kts b/data/markets/build.gradle.kts index 98b32e7c97..7e23136300 100644 --- a/data/markets/build.gradle.kts +++ b/data/markets/build.gradle.kts @@ -14,10 +14,16 @@ dependencies { implementation(projects.core.datasource) implementation(projects.core.utils) implementation(projects.core.pagination) - implementation(projects.domain.tokens.models) + + implementation(projects.domain.legacy) implementation(projects.domain.markets) + implementation(projects.domain.models) + implementation(projects.domain.tokens.models) + implementation(projects.data.common) + implementation(projects.libs.blockchainSdk) + // region DI implementation(deps.hilt.android) kapt(deps.hilt.kapt) @@ -28,7 +34,6 @@ dependencies { implementation(deps.moshi) implementation(deps.moshi.kotlin) implementation(deps.timber) - - implementation(projects.libs.blockchainSdk) + implementation(deps.tangem.blockchain) // endregion } diff --git a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt index fa6e9a355e..8f9fa44f1b 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt @@ -1,5 +1,9 @@ package com.tangem.data.markets +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.data.common.currency.CryptoCurrencyFactory +import com.tangem.data.common.currency.getNetwork import com.tangem.data.common.utils.retryOnError import com.tangem.data.markets.converters.TokenChartConverter import com.tangem.data.markets.converters.TokenMarketInfoConverter @@ -8,8 +12,12 @@ import com.tangem.data.markets.converters.toRequestParam import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.markets.TangemTechMarketsApi import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.markets.* import com.tangem.domain.markets.repositories.MarketsTokenRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.pagination.* import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -19,6 +27,7 @@ import java.util.concurrent.atomic.AtomicLong internal class DefaultMarketsTokenRepository( private val marketsApi: TangemTechMarketsApi, private val tangemTechApi: TangemTechApi, + private val userWalletsStore: UserWalletsStore, private val dispatcherProvider: CoroutineDispatcherProvider, ) : MarketsTokenRepository { @@ -148,4 +157,36 @@ internal class DefaultMarketsTokenRepository( return TokenMarketInfoConverter.convert(response.getOrThrow()).quotes } + + override suspend fun createCryptoCurrency( + userWalletId: UserWalletId, + token: TokenMarketParams, + network: TokenMarketInfo.Network, + ): CryptoCurrency? { + val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("UserWalletId [$userWalletId] not found") + val blockchain = Blockchain.fromNetworkId(network.networkId) ?: error("Unknown network [${network.networkId}]") + + return if (network.contractAddress == null) { + CryptoCurrencyFactory().createCoin( + blockchain = blockchain, + extraDerivationPath = null, + derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider, + ) + } else { + val currencyNetwork = getNetwork( + blockchain = blockchain, + extraDerivationPath = null, + derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider, + ) ?: return null + + CryptoCurrencyFactory().createToken( + network = currencyNetwork, + rawId = token.id, + name = token.name, + symbol = token.symbol, + decimals = network.decimalCount ?: error("Unknown decimal"), + contractAddress = network.contractAddress!!, + ) + } + } } \ No newline at end of file diff --git a/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt b/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt index 22356c0dfd..03cb704bd6 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt @@ -4,6 +4,7 @@ import com.tangem.data.markets.DefaultMarketsTokenRepository import com.tangem.datasource.api.markets.TangemTechMarketsApi import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.di.DevTangemApi +import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.markets.repositories.MarketsTokenRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -21,12 +22,14 @@ internal object MarketsDataModule { fun provideMarketsRepository( @DevTangemApi marketsApi: TangemTechMarketsApi, @DevTangemApi tangemTechApi: TangemTechApi, + userWalletsStore: UserWalletsStore, dispatchers: CoroutineDispatcherProvider, ): MarketsTokenRepository { return DefaultMarketsTokenRepository( marketsApi = marketsApi, tangemTechApi = tangemTechApi, dispatcherProvider = dispatchers, + userWalletsStore = userWalletsStore, ) } } \ No newline at end of file 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 c4abbb8284..83ea2e4b48 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 @@ -12,6 +12,8 @@ interface DerivationsRepository { @Throws suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List) + suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List) + @Throws suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List) diff --git a/domain/markets/build.gradle.kts b/domain/markets/build.gradle.kts index c0461ee9ab..430f975fa5 100644 --- a/domain/markets/build.gradle.kts +++ b/domain/markets/build.gradle.kts @@ -13,12 +13,16 @@ android { dependencies { /* Domain */ api(projects.domain.appCurrency.models) + api(projects.domain.card) api(projects.domain.core) - api(projects.core.pagination) api(projects.domain.markets.models) + api(projects.domain.wallets.models) + implementation(projects.domain.tokens.models) implementation(projects.domain.tokens) + api(projects.core.pagination) + /* Utils */ implementation(deps.kotlin.serialization) implementation(projects.core.utils) diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt b/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt new file mode 100644 index 0000000000..bc04fa27ec --- /dev/null +++ b/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt @@ -0,0 +1,60 @@ +package com.tangem.domain.markets + +import arrow.core.Either +import com.tangem.domain.card.repository.DerivationsRepository +import com.tangem.domain.markets.repositories.MarketsTokenRepository +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Use case for saving tokens from Markets + * + * @property derivationsRepository derivations repository + * @property marketsTokenRepository markets token repository + * @property currenciesRepository currencies repository + * +[REDACTED_AUTHOR] + */ +class SaveMarketTokensUseCase( + private val derivationsRepository: DerivationsRepository, + private val marketsTokenRepository: MarketsTokenRepository, + private val currenciesRepository: CurrenciesRepository, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + tokenMarketParams: TokenMarketParams, + addedNetworks: Set, + removedNetworks: Set, + ): Either = Either.catch { + currenciesRepository.removeCurrencies( + userWalletId = userWalletId, + currencies = removedNetworks.mapNotNull { + marketsTokenRepository.createCryptoCurrency( + userWalletId = userWalletId, + token = tokenMarketParams, + network = it, + ) + }, + ) + + derivationsRepository.derivePublicKeysByNetworkIds( + userWalletId = userWalletId, + networkIds = addedNetworks.map { Network.ID(it.networkId) }, + ) + + currenciesRepository.addCurrencies( + userWalletId = userWalletId, + currencies = addedNetworks.mapNotNull { + marketsTokenRepository.createCryptoCurrency( + userWalletId = userWalletId, + token = tokenMarketParams, + network = it, + ) + }, + ) + + // TODO: [REDACTED_JIRA] + } +} \ No newline at end of file diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt b/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt index e96aa4525c..1e40c55a13 100644 --- a/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt +++ b/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt @@ -1,6 +1,8 @@ package com.tangem.domain.markets.repositories import com.tangem.domain.markets.* +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId interface MarketsTokenRepository { @@ -17,4 +19,10 @@ interface MarketsTokenRepository { suspend fun getTokenInfo(fiatCurrencyCode: String, tokenId: String, languageCode: String): TokenMarketInfo suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String): TokenQuotes + + suspend fun createCryptoCurrency( + userWalletId: UserWalletId, + token: TokenMarketParams, + network: TokenMarketInfo.Network, + ): CryptoCurrency? } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt index 763a431844..9e95fe3af0 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt @@ -30,7 +30,11 @@ internal class AddToPortfolioBSContentUMFactory( private val onWalletSelectorVisibilityChange: (Boolean) -> Unit, private val onNetworkSwitchClick: (String, Boolean) -> Unit, private val onWalletSelect: (UserWalletId) -> Unit, - private val onContinueClick: () -> Unit, + private val onContinueClick: ( + selectedWalletId: UserWalletId, + addedNetworks: Set, + removedNetworks: Set, + ) -> Unit, ) { /** @@ -60,7 +64,23 @@ internal class AddToPortfolioBSContentUMFactory( ).convert(value = token), isScanCardNotificationVisible = portfolioUIData.hasMissedDerivations, continueButtonEnabled = isUserChangedNetworks, - onContinueButtonClick = onContinueClick, + onContinueButtonClick = { + val alreadyAddedNetworkIds = portfolioData.walletsWithCurrencies[selectedWallet].orEmpty() + .map { it.status.currency.network.backendId } + .toSet() + + onContinueClick( + selectedWallet.walletId, + portfolioUIData.addToPortfolioData.getAddedNetworks( + userWalletId = selectedWallet.walletId, + alreadyAddedNetworkIds = alreadyAddedNetworkIds, + ), + portfolioUIData.addToPortfolioData.getRemovedNetworks( + userWalletId = selectedWallet.walletId, + alreadyAddedNetworkIds = alreadyAddedNetworkIds, + ), + ) + }, walletSelectorConfig = crateWalletSelectorBSConfig( isShow = portfolioUIData.portfolioBSVisibilityModel.walletSelectorBSVisibility, portfolioData = portfolioData, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioManager.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioManager.kt new file mode 100644 index 0000000000..bf366c445c --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioManager.kt @@ -0,0 +1,188 @@ +package com.tangem.features.markets.portfolio.impl.model + +import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.update +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +internal typealias WalletsWithNetworks = Map> + +/** + * Manager for tracking changing networks in AddToPortfolio + * +[REDACTED_AUTHOR] + */ +@Singleton +internal class AddToPortfolioManager @Inject constructor() { + + private val availableNetworks = MutableStateFlow?>(value = null) + private val addedNetworks = MutableStateFlow(value = emptyMap()) + private val removedNetworks = MutableStateFlow(value = emptyMap()) + + /** Get [AddToPortfolioData] as flow */ + fun getAddToPortfolioData(): Flow { + return combine( + flow = availableNetworks, + flow2 = addedNetworks, + flow3 = removedNetworks, + transform = ::AddToPortfolioData, + ) + } + + /** Set available networks [networks] */ + fun setAvailableNetworks(networks: List) { + availableNetworks.value = networks.toSet() + } + + /** Add network [networkId] to [userWalletId] */ + fun addNetwork(userWalletId: UserWalletId, networkId: String) { + addedNetworks.add(userWalletId, networkId) + + removedNetworks.cancelPrevChangeIfExist(userWalletId = userWalletId, networkId = networkId) + } + + /** Remove network [networkId] from [userWalletId] */ + fun removeNetwork(userWalletId: UserWalletId, networkId: String) { + removedNetworks.add(userWalletId, networkId) + + addedNetworks.cancelPrevChangeIfExist( + userWalletId = userWalletId, + networkId = networkId, + ) + } + + /** Remove all networks by [userWalletId] */ + fun removeAllChanges(userWalletId: UserWalletId) { + addedNetworks.update { + it.toMutableMap().apply { remove(userWalletId) } + } + + removedNetworks.update { + it.toMutableMap().apply { remove(userWalletId) } + } + } + + private fun MutableStateFlow.cancelPrevChangeIfExist( + userWalletId: UserWalletId, + networkId: String, + ) { + if (value[userWalletId].orEmpty().any { it.networkId == networkId }) remove(userWalletId, networkId) + } + + private fun MutableStateFlow.add(userWalletId: UserWalletId, networkId: String) { + change(userWalletId = userWalletId, networkId = networkId, isAddAction = true) + } + + private fun MutableStateFlow.remove(userWalletId: UserWalletId, networkId: String) { + change(userWalletId = userWalletId, networkId = networkId, isAddAction = false) + } + + private fun MutableStateFlow.change( + userWalletId: UserWalletId, + networkId: String, + isAddAction: Boolean, + ) { + val network = availableNetworks.value.orEmpty().firstOrNull { it.networkId == networkId } + + if (network == null) { + Timber.d( + "Network [$networkId] doesn't contain in available networks [%s]", + availableNetworks.value?.joinToString { it.networkId }, + ) + + return + } + + update { + it.toMutableMap().apply { + this[userWalletId] = if (isAddAction) { + this[userWalletId].orEmpty() + network + } else { + this[userWalletId].orEmpty() - network + } + } + } + } + + /** + * Add to portfolio data + * + * @property availableNetworks available networks that user can add to portfolio + * @property addedNetworks networks that user toggled on, but it might have already been added to the wallet + * @property removedNetworks networks that user toggled off, but it might haven't been added to the wallet + * + * Example for [addedNetworks] and [removedNetworks]. This lists will include new networks when user just + * toggle it. But when we will save user changes, we will check what tokens have already been added or + * haven't been added to the wallet. See [getAddedNetworks] and [getRemovedNetworks] + */ + data class AddToPortfolioData( + val availableNetworks: Set?, + val addedNetworks: WalletsWithNetworks, + private val removedNetworks: WalletsWithNetworks, + ) { + + /** + * Associate network with toggle. If user changed toggle state then use it, otherwise check state by already + * added networks. + * + * @param userWalletId user wallet id + * @param alreadyAddedNetworkIds already added network ids + */ + fun associateWithToggle( + userWalletId: UserWalletId, + alreadyAddedNetworkIds: Set, + ): Map { + // Use user choice or check already added networks + return availableNetworks?.associateWith { availableNetwork -> + val isAddedByUser = addedNetworks[userWalletId]?.contains(availableNetwork) + + if (isAddedByUser == true) return@associateWith true + + val isRemovedByUser = removedNetworks[userWalletId]?.contains(availableNetwork) + + if (isRemovedByUser == true) return@associateWith false + + val isAddedBefore = alreadyAddedNetworkIds.any { it == availableNetwork.networkId } + + isAddedBefore + } + .orEmpty() + } + + fun isUserChangedNetworks(userWalletId: UserWalletId): Boolean { + return addedNetworks[userWalletId].orEmpty().isNotEmpty() || + removedNetworks[userWalletId].orEmpty().isNotEmpty() + } + + /** Get new networks that user [userWalletId] added using [alreadyAddedNetworkIds] */ + fun getAddedNetworks( + userWalletId: UserWalletId, + alreadyAddedNetworkIds: Set, + ): Set { + val addedNetworksByUser = addedNetworks[userWalletId].orEmpty() + + return addedNetworksByUser.map { it.networkId } + .minus(alreadyAddedNetworkIds) + .mapNotNull { networkId -> addedNetworksByUser.firstOrNull { it.networkId == networkId } } + .toSet() + } + + /** Get networks that user [userWalletId] removed using [alreadyAddedNetworkIds] */ + fun getRemovedNetworks( + userWalletId: UserWalletId, + alreadyAddedNetworkIds: Set, + ): Set { + val removedNetworksByUser = removedNetworks[userWalletId].orEmpty() + + return alreadyAddedNetworkIds + .minus(removedNetworksByUser.map { it.networkId }.toSet()) + .mapNotNull { networkId -> removedNetworksByUser.firstOrNull { it.networkId == networkId } } + .toSet() + } + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt index 891fc3b8aa..6c6f15a0dd 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt @@ -5,9 +5,10 @@ import arrow.core.getOrElse import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer -import com.tangem.domain.card.HasMissedDerivationsUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.card.HasMissedDerivationsUseCase +import com.tangem.domain.markets.SaveMarketTokensUseCase import com.tangem.domain.markets.TokenMarketInfo import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network @@ -20,6 +21,7 @@ import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -28,12 +30,14 @@ import javax.inject.Inject @ComponentScoped internal class MarketsPortfolioModel @Inject constructor( paramsContainer: ParamsContainer, + getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + tokenActionsIntentsFactory: TokenActionsHandler.Factory, override val dispatchers: CoroutineDispatcherProvider, private val getSelectedWalletUseCase: GetSelectedWalletUseCase, - private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val portfolioDataLoader: PortfolioDataLoader, private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase, - private val tokenActionsIntentsFactory: TokenActionsHandler.Factory, + private val saveMarketTokensUseCase: SaveMarketTokensUseCase, + private val addToPortfolioManager: AddToPortfolioManager, ) : Model() { val state: StateFlow get() = _state @@ -41,14 +45,9 @@ internal class MarketsPortfolioModel @Inject constructor( private val params = paramsContainer.require() - private val availableNetworksFlow = MutableStateFlow?>(value = null) - /** Multi-wallet [UserWalletId] that user uses to add new tokens in AddToPortfolio bottom sheet */ private val selectedMultiWalletIdFlow = MutableStateFlow(value = null) - /** Map of [UserWalletId] and network ids that user is changed */ - private val walletsWithChangedNetworksFlow = MutableStateFlow>>(value = emptyMap()) - private val portfolioBSVisibilityModelFlow = MutableStateFlow(value = PortfolioBSVisibilityModel()) private val currentAppCurrency = getSelectedAppCurrencyUseCase() @@ -91,11 +90,11 @@ internal class MarketsPortfolioModel @Inject constructor( } fun setTokenNetworks(networks: List) { - availableNetworksFlow.value = networks + addToPortfolioManager.setAvailableNetworks(networks) } fun setNoNetworksAvailable() { - availableNetworksFlow.value = emptyList() + addToPortfolioManager.setAvailableNetworks(emptyList()) } private fun subscribeOnSelectedMultiWalletUpdates() { @@ -114,7 +113,6 @@ internal class MarketsPortfolioModel @Inject constructor( combine( flow = portfolioDataLoader.load(params.token.id), flow2 = getPortfolioUIDataFlow(), - flow3 = availableNetworksFlow, transform = factory::create, ) .onEach { _state.value = it } @@ -125,13 +123,13 @@ internal class MarketsPortfolioModel @Inject constructor( return combine( flow = portfolioBSVisibilityModelFlow, flow2 = selectedMultiWalletIdFlow, - flow3 = walletsWithChangedNetworksFlow, - transform = { portfolioBSVisibilityModel, selectedWalletId, walletsWithChangedNetworks -> + flow3 = addToPortfolioManager.getAddToPortfolioData(), + transform = { portfolioBSVisibilityModel, selectedWalletId, addToPortfolioData -> PortfolioUIData( portfolioBSVisibilityModel = portfolioBSVisibilityModel, selectedWalletId = selectedWalletId, - walletsWithChangedNetworks = walletsWithChangedNetworks, - hasMissedDerivations = hasMissedDerivations(selectedWalletId, walletsWithChangedNetworks), + addToPortfolioData = addToPortfolioData, + hasMissedDerivations = hasMissedDerivations(selectedWalletId, addToPortfolioData), ) }, ) @@ -139,13 +137,13 @@ internal class MarketsPortfolioModel @Inject constructor( private suspend fun hasMissedDerivations( selectedWalletId: UserWalletId?, - walletsWithChangedNetworks: Map>, + addToPortfolioData: AddToPortfolioManager.AddToPortfolioData, ): Boolean { return if (selectedWalletId != null) { hasMissedDerivationsUseCase.invoke( userWalletId = selectedWalletId, - networksWithDerivationPath = walletsWithChangedNetworks[selectedWalletId].orEmpty() - .associate { Network.ID(it) to null }, + networksWithDerivationPath = addToPortfolioData.addedNetworks[selectedWalletId].orEmpty() + .associate { Network.ID(it.networkId) to null }, ) } else { false @@ -169,41 +167,46 @@ internal class MarketsPortfolioModel @Inject constructor( } } - private fun onNetworkSwitchClick(id: String, isChecked: Boolean) { - walletsWithChangedNetworksFlow.update { - val selectedWalletId = selectedMultiWalletIdFlow.value + private fun onNetworkSwitchClick(networkId: String, isChecked: Boolean) { + val selectedWalletId = selectedMultiWalletIdFlow.value - if (selectedWalletId == null) { - Timber.e("Impossible ti switch network when selected wallet is null") - return@update it - } + if (selectedWalletId == null) { + Timber.e("Impossible to switch network when selected wallet is null") + return + } - it.toMutableMap().apply { - val networkIds = this[selectedWalletId] ?: emptyList() - - if (isChecked) { - this[selectedWalletId] = networkIds + id - } else { - this[selectedWalletId] = networkIds - id - } - } + if (isChecked) { + addToPortfolioManager.addNetwork(userWalletId = selectedWalletId, networkId = networkId) + } else { + addToPortfolioManager.removeNetwork(userWalletId = selectedWalletId, networkId = networkId) } } private fun onWalletSelect(userWalletId: UserWalletId) { selectedMultiWalletIdFlow.update { prevUserWalletId -> - - // Clear user changes if user select another wallet - walletsWithChangedNetworksFlow.update { - it.toMutableMap().apply { remove(prevUserWalletId) } - } + prevUserWalletId?.let(addToPortfolioManager::removeAllChanges) userWalletId } } - private fun onContinueClick() { - // TODO [REDACTED_JIRA] + private fun onContinueClick( + userWalletId: UserWalletId, + addedNetworks: Set, + removedNetworks: Set, + ) { + modelScope.launch { + saveMarketTokensUseCase( + userWalletId = userWalletId, + tokenMarketParams = params.token, + addedNetworks = addedNetworks, + removedNetworks = removedNetworks, + ) + + onAddToPortfolioBSVisibilityChange(isShow = false) + + addToPortfolioManager.removeAllChanges(userWalletId) + } } private fun onAddToPortfolioBSVisibilityChange(isShow: Boolean) { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt index fb632b9acc..c7e25a4287 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt @@ -23,17 +23,15 @@ internal class MyPortfolioUMFactory( private val tokenActionsHandler: TokenActionsHandler, ) { - fun create( - portfolioData: PortfolioData, - portfolioUIData: PortfolioUIData, - availableNetworks: List?, - ): MyPortfolioUM { - if (availableNetworks == null) return MyPortfolioUM.Loading + fun create(portfolioData: PortfolioData, portfolioUIData: PortfolioUIData): MyPortfolioUM { + val addToPortfolioData = portfolioUIData.addToPortfolioData - if (availableNetworks.isEmpty()) return MyPortfolioUM.Unavailable + if (addToPortfolioData.availableNetworks == null) return MyPortfolioUM.Loading + + if (addToPortfolioData.availableNetworks.isEmpty()) return MyPortfolioUM.Unavailable val walletsWithCurrencies = portfolioData.walletsWithCurrencies - .filterAvailableNetworks(networks = availableNetworks) + .filterAvailableNetworks(networks = addToPortfolioData.availableNetworks) val isPortfolioEmpty = walletsWithCurrencies.flatMap { it.value }.isEmpty() if (isPortfolioEmpty) { @@ -44,7 +42,6 @@ internal class MyPortfolioUMFactory( addToPortfolioBSConfig = createAddToPortfolioBSConfig( portfolioData = portfolioData, portfolioUIData = portfolioUIData, - availableNetworks = availableNetworks, ), onAddClick = onAddClick, ) @@ -56,12 +53,10 @@ internal class MyPortfolioUMFactory( return TokensPortfolioUMConverter( appCurrency = portfolioData.appCurrency, isBalanceHidden = portfolioData.isBalanceHidden, - isAllAvailableNetworksAdded = walletsWithCurrencies.isAllAvailableNetworksAdded(availableNetworks), - bsConfig = createAddToPortfolioBSConfig( - portfolioData = portfolioData, - portfolioUIData = portfolioUIData, - availableNetworks = availableNetworks, + isAllAvailableNetworksAdded = walletsWithCurrencies.isAllAvailableNetworksAdded( + availableNetworks = addToPortfolioData.availableNetworks, ), + bsConfig = createAddToPortfolioBSConfig(portfolioData = portfolioData, portfolioUIData = portfolioUIData), onAddClick = onAddClick, onTokenItemClick = onTokenItemClick, quickActionsIntents = tokenActionsHandler, @@ -72,7 +67,6 @@ internal class MyPortfolioUMFactory( private fun createAddToPortfolioBSConfig( portfolioData: PortfolioData, portfolioUIData: PortfolioUIData, - availableNetworks: List, ): TangemBottomSheetConfig { val walletId = portfolioUIData.selectedWalletId ?: portfolioData.walletsWithCurrencies.keys.firstOrNull { it.isMultiCurrency }?.walletId @@ -81,46 +75,32 @@ internal class MyPortfolioUMFactory( .firstOrNull { it.walletId == walletId } ?: error("portfolioModel.walletsWithCurrencyStatuses doesn't contain selected wallet: $walletId") - val changedNetworks = portfolioUIData.walletsWithChangedNetworks[portfolioUIData.selectedWalletId] + val availableNetworks = portfolioUIData.addToPortfolioData.availableNetworks.orEmpty() + val alreadyAddedNetworks = requireNotNull( - value = portfolioData.walletsWithCurrencies[selectedWallet], + value = portfolioData.walletsWithCurrencies + .filterAvailableNetworks(availableNetworks)[selectedWallet], lazyMessage = { "portfolioModel.walletsWithCurrencyStatuses doesn't contain selected wallet: $walletId" }, ) - .filterAvailableNetworks(availableNetworks) .map { it.status.currency.network.backendId } + .toSet() return addToPortfolioBSContentUMFactory.create( portfolioData = portfolioData, portfolioUIData = portfolioUIData, selectedWallet = selectedWallet, - networksWithToggle = availableNetworks.associateWithToggle( - changedNetworks = changedNetworks, - alreadyAddedNetworks = alreadyAddedNetworks, + networksWithToggle = portfolioUIData.addToPortfolioData.associateWithToggle( + userWalletId = selectedWallet.walletId, + alreadyAddedNetworkIds = alreadyAddedNetworks, ), - isUserChangedNetworks = changedNetworks != null && alreadyAddedNetworks != changedNetworks, + isUserChangedNetworks = portfolioUIData.addToPortfolioData.isUserChangedNetworks(selectedWallet.walletId), ) } - private fun List.associateWithToggle( - changedNetworks: List?, - alreadyAddedNetworks: List, - ): Map { - // Use user choice or check already added networks - return associateWith { network -> - val isSelectedByUser = changedNetworks?.contains(network.networkId) - - if (isSelectedByUser != null) return@associateWith isSelectedByUser - - val isAlreadyAdded = alreadyAddedNetworks.any { it == network.networkId } - - isAlreadyAdded - } - } - private fun Map>.isAllAvailableNetworksAdded( - availableNetworks: List, + availableNetworks: Set, ): Boolean { val networkIds = availableNetworks.map { it.networkId } @@ -134,14 +114,14 @@ internal class MyPortfolioUMFactory( /** Filter map values by available networks [networks] */ private fun Map>.filterAvailableNetworks( - networks: List, + networks: Set, ): Map> { return mapValues { entry -> entry.value.filterAvailableNetworks(networks) } } /** Filter list of [CryptoCurrencyStatus] by available networks [networks] */ private fun List.filterAvailableNetworks( - networks: List, + networks: Set, ): List { val networkIds = networks.map(TokenMarketInfo.Network::networkId) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/PortfolioUIData.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/PortfolioUIData.kt index fcb0dd86c0..e9c972b068 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/PortfolioUIData.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/PortfolioUIData.kt @@ -7,7 +7,7 @@ import com.tangem.domain.wallets.models.UserWalletId * * @property portfolioBSVisibilityModel portfolio bottom sheet visibility model * @property selectedWalletId selected wallet id - * @property walletsWithChangedNetworks wallets with changed networks + * @property addToPortfolioData add to portfolio data * @property hasMissedDerivations flag that indicates if user has missed derivations * [REDACTED_AUTHOR] @@ -15,6 +15,6 @@ import com.tangem.domain.wallets.models.UserWalletId internal data class PortfolioUIData( val portfolioBSVisibilityModel: PortfolioBSVisibilityModel, val selectedWalletId: UserWalletId?, - val walletsWithChangedNetworks: Map>, + val addToPortfolioData: AddToPortfolioManager.AddToPortfolioData, val hasMissedDerivations: Boolean, ) \ No newline at end of file