diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index 47c5eb12ef..d7b74b884b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -1,7 +1,7 @@ package com.tangem.tap.domain.walletconnect2.domain import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrenciesRepository diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt index a19f2c9cd2..afb4450d1c 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt @@ -1,11 +1,16 @@ package com.tangem.domain.tokens -import arrow.core.Either import arrow.core.left +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.utils.EitherFlow +import com.tangem.domain.core.utils.lceError +import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.core.utils.toLce import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.error.mapper.mapToTokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.operations.CurrenciesStatusesLceOperations import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.operations.TokenListOperations import com.tangem.domain.tokens.repository.CurrenciesRepository @@ -13,7 +18,6 @@ import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.transformLatest @@ -25,11 +29,18 @@ class GetTokenListUseCase( ) { @OptIn(ExperimentalCoroutinesApi::class) - operator fun invoke(userWalletId: UserWalletId): Flow> { - return getTokensStatuses(userWalletId).transformLatest { maybeTokens -> + fun launch(userWalletId: UserWalletId): EitherFlow { + val operations = CurrenciesStatusesOperations( + userWalletId = userWalletId, + currenciesRepository = currenciesRepository, + quotesRepository = quotesRepository, + networksRepository = networksRepository, + ) + + return operations.getCurrenciesStatusesFlow().transformLatest { maybeTokens -> maybeTokens.fold( ifLeft = { error -> - emit(error.left()) + emit(error.mapToTokenListError().left()) }, ifRight = { tokens -> emitAll(createTokenList(userWalletId, tokens)) @@ -38,26 +49,35 @@ class GetTokenListUseCase( } } - private fun getTokensStatuses( - userWalletId: UserWalletId, - ): Flow>> { - val operations = CurrenciesStatusesOperations( - userWalletId = userWalletId, + @OptIn(ExperimentalCoroutinesApi::class) + fun launchLce(userWalletId: UserWalletId): LceFlow { + val operations = CurrenciesStatusesLceOperations( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, ) - return operations.getCurrenciesStatusesFlow() - .map { maybeCurrenciesStatuses -> - maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError) - } + return operations.getCurrenciesStatuses(userWalletId).transformLatest { maybeCurrencies -> + maybeCurrencies.fold( + ifLoading = { maybeContent -> + if (maybeContent != null) { + emitAll(createTokenListLce(userWalletId, maybeContent, isCurrenciesLoading = true)) + } else { + emit(lceLoading()) + } + }, + ifContent = { content -> + emitAll(createTokenListLce(userWalletId, content, isCurrenciesLoading = false)) + }, + ifError = { error -> emit(error.lceError()) }, + ) + } } private fun createTokenList( userWalletId: UserWalletId, tokens: List, - ): Flow> { + ): EitherFlow { val operations = TokenListOperations( userWalletId = userWalletId, tokens = tokens, @@ -68,4 +88,22 @@ class GetTokenListUseCase( maybeTokenList.mapLeft(TokenListOperations.Error::mapToTokenListError) } } + + private fun createTokenListLce( + userWalletId: UserWalletId, + currencies: List, + isCurrenciesLoading: Boolean, + ): LceFlow { + val operations = TokenListOperations( + userWalletId = userWalletId, + tokens = currencies, + currenciesRepository = currenciesRepository, + ) + + return operations.getTokenListFlow().map { maybeTokenList -> + maybeTokenList + .mapLeft(TokenListOperations.Error::mapToTokenListError) + .toLce(isCurrenciesLoading) + } + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt new file mode 100644 index 0000000000..c358cd158e --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt @@ -0,0 +1,153 @@ +package com.tangem.domain.tokens.operations + +import arrow.core.* +import arrow.core.raise.recover +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.lce.lce +import com.tangem.domain.core.utils.lceError +import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.* +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.* + +internal class CurrenciesStatusesLceOperations( + private val currenciesRepository: CurrenciesRepository, + private val quotesRepository: QuotesRepository, + private val networksRepository: NetworksRepository, +) { + + fun getCurrenciesStatuses(userWalletId: UserWalletId): LceFlow> { + return getMultiCurrencyWalletCurrencies(userWalletId).transform transform@{ maybeCurrencies -> + val nonEmptyCurrencies = maybeCurrencies.fold( + ifLoading = { maybeContent -> + emit(createLoadingCurrenciesStatuses(maybeContent)) + return@transform + }, + ifContent = { content -> + val nonEmptyCurrencies = content.toNonEmptyListOrNull() + + if (nonEmptyCurrencies == null) { + emit(TokenListError.EmptyTokens.lceError()) + return@transform + } else { + nonEmptyCurrencies + } + }, + ifError = { error -> + emit(error.lceError()) + return@transform + }, + ) + + val (networks, currenciesIds) = getIds(nonEmptyCurrencies) + + combine( + getQuotes(currenciesIds), + getNetworksStatuses(userWalletId, networks), + ) { maybeQuotes, maybeNetworksStatuses -> + val statuses = createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses) + emit(statuses) + }.collect() + } + } + + private fun createLoadingCurrenciesStatuses( + maybeCurrencies: List?, + ): Lce> { + val nonEmptyCurrencies = maybeCurrencies?.toNonEmptyListOrNull() + + val statuses = if (nonEmptyCurrencies == null) { + lceLoading() + } else { + createCurrenciesStatuses( + nonEmptyCurrencies, + maybeNetworkStatuses = null, + maybeQuotes = null, + ) + } + + return statuses + } + + private fun getMultiCurrencyWalletCurrencies( + userWalletId: UserWalletId, + ): LceFlow> { + return currenciesRepository.getMultiCurrencyWalletCurrenciesUpdatesLce(userWalletId) + .map { maybeCurrencies -> + maybeCurrencies.mapError { TokenListError.DataError(it) } + } + } + + private fun createCurrenciesStatuses( + currencies: NonEmptyList, + maybeQuotes: Either>?, + maybeNetworkStatuses: Lce>?, + ): Lce> = lce { + isLoading.set(maybeNetworkStatuses == null) + + var quotesRetrievingFailed = false + + val networksStatuses = maybeNetworkStatuses?.bindOrNull()?.toNonEmptySetOrNull() + val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) { + quotesRetrievingFailed = true + null + } + + currencies.map { currency -> + val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId } + val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network } + + createCurrencyStatus(currency, quote, networkStatus, ignoreQuote = quotesRetrievingFailed) + } + } + + private fun createCurrencyStatus( + currency: CryptoCurrency, + quote: Quote?, + networkStatus: NetworkStatus?, + ignoreQuote: Boolean, + ): CryptoCurrencyStatus { + val currencyStatusOperations = CurrencyStatusOperations( + currency = currency, + quote = quote, + networkStatus = networkStatus, + ignoreQuote = ignoreQuote, + ) + + return currencyStatusOperations.createTokenStatus() + } + + private fun getQuotes(tokensIds: NonEmptySet): Flow>> { + return quotesRepository.getQuotesUpdates(tokensIds) + .map, Either>> { it.right() } + .catch { emit(TokenListError.DataError(it).left()) } + } + + private fun getNetworksStatuses( + userWalletId: UserWalletId, + networks: NonEmptySet, + ): LceFlow> { + return networksRepository.getNetworkStatusesUpdatesLce(userWalletId, networks) + .map { maybeStatuses -> + maybeStatuses.mapError { TokenListError.DataError(it) } + } + } + + private fun getIds(currencies: List): Pair, NonEmptySet> { + val currencyIdToNetworkId = currencies.associate { currency -> + currency.id to currency.network + } + val currenciesIds = currencyIdToNetworkId.keys.toNonEmptySetOrNull() + val networks = currencyIdToNetworkId.values.toNonEmptySetOrNull() + + requireNotNull(currenciesIds) { "Currencies IDs cannot be empty" } + requireNotNull(networks) { "Networks IDs cannot be empty" } + + return networks to currenciesIds + } +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt index 48b5575b45..613246a85c 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt @@ -43,7 +43,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -59,7 +59,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(tokens = flowOf(DataError.NetworkError.NoInternetConnection.left())) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) @@ -79,7 +79,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -95,7 +95,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(isGrouped = flowOf(DataError.NetworkError.NoInternetConnection.left())) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) @@ -109,7 +109,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(isSortedByBalance = flowOf(DataError.NetworkError.NoInternetConnection.left())) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) @@ -134,7 +134,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 3) .toList() @@ -153,7 +153,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(isGrouped = flowOf(true.right())) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -175,7 +175,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -197,7 +197,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -212,7 +212,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(tokens = flowOf(emptyList().right())) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) @@ -225,7 +225,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(tokens = flowOf()) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) @@ -241,7 +241,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(statuses = flowOf()) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -256,7 +256,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(statuses = flowOf(emptySet().right())) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) @@ -275,7 +275,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -293,7 +293,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt index 5342e0ece5..d4000aac56 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt @@ -161,7 +161,7 @@ internal class OrganizeTokensViewModel @Inject constructor( private fun bootstrapTokenList() { viewModelScope.launch(dispatchers.default) { - val maybeTokenList = getTokenListUseCase(userWalletId) + val maybeTokenList = getTokenListUseCase.launch(userWalletId) .first { it.getOrNull()?.totalFiatBalance !is TokenList.FiatBalance.Loading } maybeTokenList.fold( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 7432cbb84e..51fbfe6742 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -47,7 +47,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( val promoFlow = flow { emit(promoRepository.getChangellyPromoBanner()) } return combine( - flow = getTokenListUseCase(userWallet.walletId).conflate(), + flow = getTokenListUseCase.launch(userWallet.walletId).conflate(), flow2 = isReadyToShowRateAppUseCase().conflate(), flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(), flow4 = shouldShowSwapPromoWalletUseCase().conflate(), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 9185370945..53114d04ba 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -33,7 +33,7 @@ internal class MultiWalletTokenListSubscriber( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, ) { - override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase(userWallet.walletId) + override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase.launch(userWallet.walletId) override suspend fun onTokenListReceived(maybeTokenList: Either) { // TODO disabled for 5.7.2 because of potential critical