From d677abc44d2f722122dc09003873c9503408bb48 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Aug 2023 10:46:03 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 33 ++++- .../appcurrency/SelectedAppCurrencyStore.kt | 2 + .../DefaultSelectedAppCurrencyStore.kt | 1 - .../repository/DefaultCurrenciesRepository.kt | 27 ++-- .../repository/DefaultNetworksRepository.kt | 24 ++-- .../repository/DefaultQuotesRepository.kt | 24 +++- .../tokens/ApplyTokenListSortingUseCase.kt | 2 +- .../tokens/FetchCurrencyStatusUseCase.kt | 121 ++++++++++++++++++ .../domain/tokens/FetchTokenListUseCase.kt | 101 +++++++++++++++ ....kt => GetCurrencyStatusUpdatesUseCase.kt} | 19 +-- ...GetPrimaryCurrencyStatusUpdatesUseCase.kt} | 17 +-- .../domain/tokens/GetTokenListUseCase.kt | 11 +- .../domain/tokens/error/CurrencyError.kt | 8 -- .../tokens/error/CurrencyStatusError.kt | 8 ++ ...ppers.kt => CurrencyStatusErrorMappers.kt} | 8 +- .../CurrenciesStatusesOperations.kt | 25 ++-- .../tokens/repository/CurrenciesRepository.kt | 9 +- .../tokens/repository/NetworksRepository.kt | 19 ++- .../tokens/repository/QuotesRepository.kt | 16 ++- ...rimaryCurrencyStatusUpdatesUseCaseTest.kt} | 12 +- .../domain/tokens/GetTokenListUseCaseTest.kt | 35 ----- .../repository/MockCurrenciesRepository.kt | 5 +- .../repository/MockNetworksRepository.kt | 12 +- .../tokens/repository/MockQuotesRepository.kt | 7 +- .../TokenDetailsLoadedBalanceConverter.kt | 6 +- .../state/factory/TokenDetailsStateFactory.kt | 4 +- .../viewmodels/TokenDetailsViewModel.kt | 17 ++- ...letSingleCurrencyLoadedBalanceConverter.kt | 4 +- .../state/factory/WalletStateFactory.kt | 4 +- .../wallet/viewmodels/WalletViewModel.kt | 6 +- 30 files changed, 423 insertions(+), 164 deletions(-) create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt rename domain/tokens/src/main/kotlin/com/tangem/domain/tokens/{GetCurrencyUseCase.kt => GetCurrencyStatusUpdatesUseCase.kt} (74%) rename domain/tokens/src/main/kotlin/com/tangem/domain/tokens/{GetPrimaryCurrencyUseCase.kt => GetPrimaryCurrencyStatusUpdatesUseCase.kt} (79%) delete mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyError.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyStatusError.kt rename domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/{GetWalletTokenErrorMappers.kt => CurrencyStatusErrorMappers.kt} (75%) rename domain/tokens/src/test/kotlin/com/tangem/domain/tokens/{GetPrimaryCurrencyUseCaseTest.kt => GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt} (92%) diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index ce2402c7de..1a77f50c7b 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -15,6 +15,16 @@ import dagger.hilt.android.scopes.ViewModelScoped @InstallIn(ViewModelComponent::class) internal object TokensDomainModule { + @Provides + @ViewModelScoped + fun provideFetchTokenListUseCase( + currenciesRepository: CurrenciesRepository, + quotesRepository: QuotesRepository, + networksRepository: NetworksRepository, + ): FetchTokenListUseCase { + return FetchTokenListUseCase(currenciesRepository, networksRepository, quotesRepository) + } + @Provides @ViewModelScoped fun provideGetTokenListUseCase( @@ -42,8 +52,8 @@ internal object TokensDomainModule { quotesRepository: QuotesRepository, networksRepository: NetworksRepository, dispatchers: CoroutineDispatcherProvider, - ): GetCurrencyUseCase { - return GetCurrencyUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers) + ): GetCurrencyStatusUpdatesUseCase { + return GetCurrencyStatusUpdatesUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers) } @Provides @@ -53,8 +63,23 @@ internal object TokensDomainModule { quotesRepository: QuotesRepository, networksRepository: NetworksRepository, dispatchers: CoroutineDispatcherProvider, - ): GetPrimaryCurrencyUseCase { - return GetPrimaryCurrencyUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers) + ): GetPrimaryCurrencyStatusUpdatesUseCase { + return GetPrimaryCurrencyStatusUpdatesUseCase( + currenciesRepository, + quotesRepository, + networksRepository, + dispatchers, + ) + } + + @Provides + @ViewModelScoped + fun provideFetchCurrencyStatusUseCase( + currenciesRepository: CurrenciesRepository, + quotesRepository: QuotesRepository, + networksRepository: NetworksRepository, + ): FetchCurrencyStatusUseCase { + return FetchCurrencyStatusUseCase(currenciesRepository, networksRepository, quotesRepository) } @Provides diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/SelectedAppCurrencyStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/SelectedAppCurrencyStore.kt index 80b95f3fb2..e732ce8415 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/SelectedAppCurrencyStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/SelectedAppCurrencyStore.kt @@ -7,6 +7,8 @@ interface SelectedAppCurrencyStore { fun get(): Flow + suspend fun getSyncOrNull(): CurrenciesResponse.Currency? + suspend fun store(item: CurrenciesResponse.Currency) suspend fun isEmpty(): Boolean diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/implementation/DefaultSelectedAppCurrencyStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/implementation/DefaultSelectedAppCurrencyStore.kt index 7cdf82b1d7..b6d8f61ade 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/implementation/DefaultSelectedAppCurrencyStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/appcurrency/implementation/DefaultSelectedAppCurrencyStore.kt @@ -8,7 +8,6 @@ import com.tangem.datasource.local.datastore.core.StringKeyDataStore internal class DefaultSelectedAppCurrencyStore( dataStore: StringKeyDataStore, ) : SelectedAppCurrencyStore, KeylessDataStoreDecorator(dataStore) { - override suspend fun isEmpty(): Boolean { return getSyncOrNull() == 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 b431e2b4dd..0df288c0c0 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 @@ -78,19 +78,18 @@ internal class DefaultCurrenciesRepository( } } - override fun getMultiCurrencyWalletCurrencies( - userWalletId: UserWalletId, - refresh: Boolean, - ): Flow> = channelFlow { - val userWallet = getUserWallet(userWalletId) - ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) + override fun getMultiCurrencyWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow> { + return channelFlow { + val userWallet = getUserWallet(userWalletId) + ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) - launch(dispatchers.io) { - getMultiCurrencyWalletCurrencies(userWallet).collect(::send) - } + launch(dispatchers.io) { + getMultiCurrencyWalletCurrencies(userWallet).collect(::send) + } - launch(dispatchers.io) { - fetchTokensIfCacheExpired(userWallet, refresh) + launch(dispatchers.io) { + fetchTokensIfCacheExpired(userWallet, refresh = false) + } } } @@ -102,7 +101,11 @@ internal class DefaultCurrenciesRepository( ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) fetchTokensIfCacheExpired(userWallet, refresh) - val storedTokens = requireNotNull(userTokensStore.getSyncOrNull(userWallet.walletId)) + + val storedTokens = requireNotNull(userTokensStore.getSyncOrNull(userWallet.walletId)) { + "Unable to find tokens response for user wallet with provided ID: $userWalletId" + } + return responseCurrenciesFactory.createCurrencies( response = storedTokens, card = userWallet.scanResponse.card, diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index 606fc9e94d..5ac3840c7f 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -16,14 +16,8 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.addOrReplace -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* internal class DefaultNetworksRepository( private val walletManagersFacade: WalletManagersFacade, @@ -45,10 +39,9 @@ internal class DefaultNetworksRepository( return networkConverter.convertSet(networksIds) } - override fun getNetworkStatuses( + override fun getNetworkStatusesUpdates( userWalletId: UserWalletId, networks: Set, - refresh: Boolean, ): Flow> = channelFlow { launch(dispatchers.io) { networksStatuses.collect { @@ -57,10 +50,19 @@ internal class DefaultNetworksRepository( } launch(dispatchers.io) { - fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh) + fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh = false) } } + override suspend fun getNetworkStatusesSync( + userWalletId: UserWalletId, + networks: Set, + refresh: Boolean, + ): Set = withContext(dispatchers.io) { + fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh) + networksStatuses.first().toSet() + } + private suspend fun fetchNetworksStatusesIfCacheExpired( userWalletId: UserWalletId, networks: Set, diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt index 25fb880a50..a288240656 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt @@ -9,11 +9,9 @@ import com.tangem.domain.tokens.models.CryptoCurrency import com.tangem.domain.tokens.models.Quote import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import timber.log.Timber internal class DefaultQuotesRepository( @@ -28,7 +26,7 @@ internal class DefaultQuotesRepository( private var quotesFetchedForAppCurrency: String? = null - override fun getQuotes(currenciesIds: Set, refresh: Boolean): Flow> { + override fun getQuotesUpdates(currenciesIds: Set): Flow> { return channelFlow { launch(dispatchers.io) { quotesStore.get(currenciesIds) @@ -38,12 +36,26 @@ internal class DefaultQuotesRepository( launch(dispatchers.io) { selectedAppCurrencyStore.get().collectLatest { appCurrency -> - fetchExpiredQuotes(currenciesIds, appCurrency.id, refresh) + fetchExpiredQuotes(currenciesIds, appCurrency.id, refresh = false) } } } } + override suspend fun getQuotesSync(currenciesIds: Set, refresh: Boolean): Set { + return withContext(dispatchers.io) { + val selectedAppCurrency = requireNotNull(selectedAppCurrencyStore.getSyncOrNull()) { + "Unable to get selected application currency to update quotes" + } + + fetchExpiredQuotes(currenciesIds, selectedAppCurrency.id, refresh) + + val quotes = quotesStore.get(currenciesIds).first() + + quotesConverter.convertSet(quotes) + } + } + private suspend fun fetchExpiredQuotes( currenciesIds: Set, appCurrencyId: String, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ApplyTokenListSortingUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ApplyTokenListSortingUseCase.kt index e57b268e68..39e2ea82db 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ApplyTokenListSortingUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ApplyTokenListSortingUseCase.kt @@ -68,7 +68,7 @@ class ApplyTokenListSortingUseCase( private suspend fun Raise.getCurrencies(userWalletId: UserWalletId): List { val tokens = catch( block = { - currenciesRepository.getMultiCurrencyWalletCurrencies(userWalletId, refresh = false).firstOrNull() + currenciesRepository.getMultiCurrencyWalletCurrenciesUpdates(userWalletId).firstOrNull() }, catch = { raise(TokenListSortingError.DataError(it)) }, ) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt new file mode 100644 index 0000000000..b10e3633c9 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt @@ -0,0 +1,121 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import arrow.core.raise.Raise +import arrow.core.raise.catch +import arrow.core.raise.either +import com.tangem.domain.tokens.error.CurrencyStatusError +import com.tangem.domain.tokens.models.CryptoCurrency +import com.tangem.domain.tokens.models.Network +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.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope + +/** + * Use case responsible for fetching currency status information, including network status + * and quotes for a given cryptocurrency. It provides methods to fetch currency status either + * by providing a specific currency ID or fetching the status of the primary currency. + * + * @param currenciesRepository The repository for retrieving currency-related data. + * @param networksRepository The repository for retrieving network-related data. + * @param quotesRepository The repository for retrieving cryptocurrency quotes. + */ +// TODO: Add tests +class FetchCurrencyStatusUseCase( + private val currenciesRepository: CurrenciesRepository, + private val networksRepository: NetworksRepository, + private val quotesRepository: QuotesRepository, +) { + + /** + * Fetches the status of a specific cryptocurrency for a given user wallet. + * + * @param userWalletId The ID of the user's wallet. + * @param id The ID of the cryptocurrency. + * @param refresh Indicates whether to force a refresh of the status data. + * @return An [Either] representing success (Right) or an error (Left) in fetching the status. + */ + suspend operator fun invoke( + userWalletId: UserWalletId, + id: CryptoCurrency.ID, + refresh: Boolean = false, + ): Either { + return either { + val currency = getCurrency(userWalletId, id) + + fetchCurrencyStatus(userWalletId, currency, refresh) + } + } + + /** + * Fetches the status of the primary cryptocurrency for a given user wallet. + * + * @param userWalletId The ID of the user's wallet. + * @param refresh Indicates whether to force a refresh of the status data. + * @return An [Either] representing success (Right) or an error (Left) in fetching the status. + */ + suspend operator fun invoke( + userWalletId: UserWalletId, + refresh: Boolean = false, + ): Either { + return either { + val currency = getPrimaryCurrency(userWalletId) + + fetchCurrencyStatus(userWalletId, currency, refresh) + } + } + + private suspend fun Raise.fetchCurrencyStatus( + userWalletId: UserWalletId, + currency: CryptoCurrency, + refresh: Boolean, + ) = coroutineScope { + val fetchStatus = async { + fetchNetworkStatus(userWalletId, currency.network.id, refresh) + } + val fetchQuote = async { + fetchQuote(currency.id, refresh) + } + + awaitAll(fetchStatus, fetchQuote) + } + + private suspend fun Raise.getCurrency( + userWalletId: UserWalletId, + id: CryptoCurrency.ID, + ): CryptoCurrency { + return catch({ currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, id) }) { + raise(CurrencyStatusError.DataError(it)) + } + } + + private suspend fun Raise.getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency { + return catch({ currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) }) { + raise(CurrencyStatusError.DataError(it)) + } + } + + private suspend fun Raise.fetchNetworkStatus( + userWalletId: UserWalletId, + networkId: Network.ID, + refresh: Boolean, + ) { + catch( + block = { networksRepository.getNetworkStatusesSync(userWalletId, setOf(networkId), refresh) }, + ) { + raise(CurrencyStatusError.DataError(it)) + } + } + + private suspend fun Raise.fetchQuote(currencyId: CryptoCurrency.ID, refresh: Boolean) { + catch( + block = { quotesRepository.getQuotesSync(setOf(currencyId), refresh) }, + ) { + raise(CurrencyStatusError.DataError(it)) + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt new file mode 100644 index 0000000000..7e379288b9 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt @@ -0,0 +1,101 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import arrow.core.raise.Raise +import arrow.core.raise.catch +import arrow.core.raise.either +import arrow.core.raise.ensureNotNull +import arrow.core.toNonEmptyListOrNull +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.models.CryptoCurrency +import com.tangem.domain.tokens.models.Network +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.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope + +/** + * Use case responsible for fetching token list information, including currency data, + * network statuses, and quotes for tokens associated with a user's wallet. + * + * @param currenciesRepository The repository for retrieving currency-related data. + * @param networksRepository The repository for retrieving network-related data. + * @param quotesRepository The repository for retrieving cryptocurrency quotes. + */ +// TODO: Add tests +class FetchTokenListUseCase( + private val currenciesRepository: CurrenciesRepository, + private val networksRepository: NetworksRepository, + private val quotesRepository: QuotesRepository, +) { + + /** + * Fetches the token list information for a user's wallet, including currency data, + * network statuses, and quotes for associated tokens. + * + * @param userWalletId The ID of the user's wallet. + * @param refresh Indicates whether to force a refresh of the token list data. + * @return An [Either] representing success (Right) or an error (Left) in fetching the token list. + */ + suspend operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = false): Either { + return either { + val currencies = fetchCurrencies(userWalletId, refresh) + + coroutineScope { + val fetchStatuses = async { + fetchNetworksStatuses( + userWalletId, + currencies.mapTo(hashSetOf()) { it.network.id }, + refresh, + ) + } + val fetchQuotes = async { + fetchQuotes( + currencies.mapTo(hashSetOf()) { it.id }, + refresh, + ) + } + + awaitAll(fetchStatuses, fetchQuotes) + } + } + } + + private suspend fun Raise.fetchCurrencies( + userWalletId: UserWalletId, + refresh: Boolean, + ): List { + val currencies = catch( + block = { currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId, refresh) }, + ) { + raise(TokenListError.DataError(it)) + } + + return ensureNotNull(currencies.toNonEmptyListOrNull()) { + TokenListError.EmptyTokens + } + } + + private suspend fun Raise.fetchNetworksStatuses( + userWalletId: UserWalletId, + networksIds: Set, + refresh: Boolean, + ) { + catch( + block = { networksRepository.getNetworkStatusesSync(userWalletId, networksIds, refresh) }, + ) { + raise(TokenListError.DataError(it)) + } + } + + private suspend fun Raise.fetchQuotes(currenciesIds: Set, refresh: Boolean) { + catch( + block = { quotesRepository.getQuotesSync(currenciesIds, refresh) }, + ) { + raise(TokenListError.DataError(it)) + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyStatusUpdatesUseCase.kt similarity index 74% rename from domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyUseCase.kt rename to domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyStatusUpdatesUseCase.kt index 650abfd651..068b7b1572 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyStatusUpdatesUseCase.kt @@ -1,7 +1,7 @@ package com.tangem.domain.tokens import arrow.core.Either -import com.tangem.domain.tokens.error.CurrencyError +import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.error.mapper.mapToCurrencyError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.models.CryptoCurrency @@ -14,14 +14,13 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* /** - * Use case for fetching the status of a specific cryptocurrency associated with a user wallet. + * Use case for fetching the status of a cryptocurrency associated with a user wallet. * * @property currenciesRepository Repository for managing and fetching cryptocurrencies. * @property quotesRepository Repository for managing and fetching cryptocurrency quotes. * @property networksRepository Repository for managing and fetching information related to blockchain networks. - * @property dispatchers Provides coroutine dispatchers. */ -class GetCurrencyUseCase( +class GetCurrencyStatusUpdatesUseCase( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, @@ -33,30 +32,26 @@ class GetCurrencyUseCase( * * @param userWalletId The unique identifier of the user's wallet. * @param currencyId The unique identifier of the cryptocurrency. - * @param refresh A boolean flag indicating whether the data should be refreshed. - * @return A [Flow] emitting either a [CurrencyError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation. + * @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation. */ operator fun invoke( userWalletId: UserWalletId, currencyId: CryptoCurrency.ID, - refresh: Boolean = false, - ): Flow> { + ): Flow> { return flow { - emitAll(getCurrency(userWalletId, currencyId, refresh)) + emitAll(getCurrency(userWalletId, currencyId)) }.flowOn(dispatchers.io) } private suspend fun getCurrency( userWalletId: UserWalletId, currencyId: CryptoCurrency.ID, - refresh: Boolean, - ): Flow> { + ): Flow> { val operations = CurrenciesStatusesOperations( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, userWalletId = userWalletId, - refresh = refresh, ) return operations.getCurrencyStatusFlow(currencyId).map { maybeCurrency -> diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCase.kt similarity index 79% rename from domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCase.kt rename to domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCase.kt index 58a8bba978..7ae1e238aa 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCase.kt @@ -1,7 +1,7 @@ package com.tangem.domain.tokens import arrow.core.Either -import com.tangem.domain.tokens.error.CurrencyError +import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.error.mapper.mapToCurrencyError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations @@ -20,7 +20,7 @@ import kotlinx.coroutines.flow.* * @property networksRepository Repository for managing and fetching information related to blockchain networks. * @property dispatchers Provides coroutine dispatchers. */ -class GetPrimaryCurrencyUseCase( +class GetPrimaryCurrencyStatusUpdatesUseCase( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, @@ -32,27 +32,22 @@ class GetPrimaryCurrencyUseCase( * * @param userWalletId The unique identifier of the user's wallet. * @param refresh A boolean flag indicating whether the data should be refreshed. - * @return A [Flow] emitting either a [CurrencyError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation. + * @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation. */ - operator fun invoke( - userWalletId: UserWalletId, - refresh: Boolean = false, - ): Flow> { + operator fun invoke(userWalletId: UserWalletId): Flow> { return flow { - emitAll(getPrimaryCurrency(userWalletId, refresh)) + emitAll(getPrimaryCurrency(userWalletId)) }.flowOn(dispatchers.io) } private suspend fun getPrimaryCurrency( userWalletId: UserWalletId, - refresh: Boolean, - ): Flow> { + ): Flow> { val operations = CurrenciesStatusesOperations( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, userWalletId = userWalletId, - refresh = refresh, ) return operations.getPrimaryCurrencyStatusFlow().map { maybeCurrency -> 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 59125a081c..99c107632a 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 @@ -14,7 +14,10 @@ import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapMerge +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map class GetTokenListUseCase( internal val currenciesRepository: CurrenciesRepository, @@ -24,8 +27,8 @@ class GetTokenListUseCase( ) { @OptIn(ExperimentalCoroutinesApi::class) - operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = false): Flow> { - return getTokensStatuses(userWalletId, refresh).flatMapMerge { maybeTokens -> + operator fun invoke(userWalletId: UserWalletId): Flow> { + return getTokensStatuses(userWalletId).flatMapMerge { maybeTokens -> maybeTokens.fold( ifLeft = { error -> flowOf(error.left()) @@ -39,11 +42,9 @@ class GetTokenListUseCase( private fun getTokensStatuses( userWalletId: UserWalletId, - refresh: Boolean, ): Flow>> { val operations = CurrenciesStatusesOperations( userWalletId = userWalletId, - refresh = refresh, useCase = this@GetTokenListUseCase, ) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyError.kt deleted file mode 100644 index 5d1ab7e2d8..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyError.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.domain.tokens.error - -sealed class CurrencyError { - - object UnableToCreateCurrency : CurrencyError() - - data class DataError(val cause: Throwable) : CurrencyError() -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyStatusError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyStatusError.kt new file mode 100644 index 0000000000..c1f5dfaac8 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyStatusError.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.tokens.error + +sealed class CurrencyStatusError { + + object UnableToCreateCurrency : CurrencyStatusError() + + data class DataError(val cause: Throwable) : CurrencyStatusError() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/GetWalletTokenErrorMappers.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/CurrencyStatusErrorMappers.kt similarity index 75% rename from domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/GetWalletTokenErrorMappers.kt rename to domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/CurrencyStatusErrorMappers.kt index 51fc33c463..6b5e9429d8 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/GetWalletTokenErrorMappers.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/CurrencyStatusErrorMappers.kt @@ -1,15 +1,15 @@ package com.tangem.domain.tokens.error.mapper -import com.tangem.domain.tokens.error.CurrencyError +import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations -internal fun CurrenciesStatusesOperations.Error.mapToCurrencyError(): CurrencyError { +internal fun CurrenciesStatusesOperations.Error.mapToCurrencyError(): CurrencyStatusError { return when (this) { - is CurrenciesStatusesOperations.Error.DataError -> CurrencyError.DataError(this.cause) + is CurrenciesStatusesOperations.Error.DataError -> CurrencyStatusError.DataError(this.cause) is CurrenciesStatusesOperations.Error.EmptyNetworksStatuses, is CurrenciesStatusesOperations.Error.EmptyQuotes, is CurrenciesStatusesOperations.Error.EmptyCurrencies, is CurrenciesStatusesOperations.Error.UnableToCreateCurrencyStatus, - -> CurrencyError.UnableToCreateCurrency + -> CurrencyStatusError.UnableToCreateCurrency } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index 48ea420c61..cb8885e6b5 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -20,19 +20,16 @@ internal class CurrenciesStatusesOperations( private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, private val userWalletId: UserWalletId, - private val refresh: Boolean, ) { constructor( userWalletId: UserWalletId, - refresh: Boolean, useCase: GetTokenListUseCase, ) : this( currenciesRepository = useCase.currenciesRepository, quotesRepository = useCase.quotesRepository, networksRepository = useCase.networksRepository, userWalletId = userWalletId, - refresh = refresh, ) @OptIn(ExperimentalCoroutinesApi::class) @@ -51,16 +48,16 @@ internal class CurrenciesStatusesOperations( emit(emptyCurrenciesStatuses.right()) return@transformLatest - } else if (!refresh) { - val maybeLoadingCurrenciesStatuses = createCurrenciesStatuses( - currencies = nonEmptyCurrencies, - maybeNetworkStatuses = null, - maybeQuotes = null, - ) - - emit(maybeLoadingCurrenciesStatuses) } + val maybeLoadingCurrenciesStatuses = createCurrenciesStatuses( + currencies = nonEmptyCurrencies, + maybeNetworkStatuses = null, + maybeQuotes = null, + ) + + emit(maybeLoadingCurrenciesStatuses) + val (networksIds, currenciesIds) = getIds(nonEmptyCurrencies) val currenciesFlow = combine( @@ -168,7 +165,7 @@ internal class CurrenciesStatusesOperations( } private fun getMultiCurrencyWalletCurrencies(): Flow>> { - return currenciesRepository.getMultiCurrencyWalletCurrencies(userWalletId, refresh) + return currenciesRepository.getMultiCurrencyWalletCurrenciesUpdates(userWalletId) .map, Either>> { it.right() } .catch { emit(Error.DataError(it).left()) } .onEmpty { emit(Error.EmptyCurrencies.left()) } @@ -188,14 +185,14 @@ internal class CurrenciesStatusesOperations( } private fun getQuotes(tokensIds: NonEmptySet): Flow>> { - return quotesRepository.getQuotes(tokensIds, refresh) + return quotesRepository.getQuotesUpdates(tokensIds) .map, Either>> { it.right() } .catch { emit(Error.DataError(it).left()) } .onEmpty { emit(Error.EmptyQuotes.left()) } } private fun getNetworksStatuses(networks: NonEmptySet): Flow>> { - return networksRepository.getNetworkStatuses(userWalletId, networks, refresh) + return networksRepository.getNetworkStatusesUpdates(userWalletId, networks) .map, Either>> { it.right() } .catch { emit(Error.DataError(it).left()) } .onEmpty { emit(Error.EmptyNetworksStatuses.left()) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt index 9da5ff82bf..97cbf78ede 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt @@ -48,19 +48,22 @@ interface CurrenciesRepository { suspend fun getSingleCurrencyWalletPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency /** - * Retrieves the list of cryptocurrencies within a multi-currency wallet. + * Retrieves updates of the list of cryptocurrencies within a multi-currency wallet. + * + * Loads remote cryptocurrencies if they have expired. * * @param userWalletId The unique identifier of the user wallet. - * @param refresh A boolean flag indicating whether the data should be refreshed. * @return A [Flow] emitting the set of cryptocurrencies associated with the user wallet. * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet * ID provided. */ - fun getMultiCurrencyWalletCurrencies(userWalletId: UserWalletId, refresh: Boolean): Flow> + fun getMultiCurrencyWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow> /** * Retrieves the list of cryptocurrencies within a multi-currency wallet. * + * Loads cryptocurrencies if they have expired or if [refresh] is `true`. + * * @param userWalletId The unique identifier of the user wallet. * @param refresh A boolean flag indicating whether the data should be refreshed. * @return A list of [CryptoCurrency]. diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt index 6dcec4843b..b019c7f3f5 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt @@ -19,16 +19,29 @@ interface NetworksRepository { fun getNetworks(networksIds: Set): Set /** - * Retrieves the statuses of specified blockchain networks for a specific user wallet. + * Retrieves updates of network statuses of specified blockchain networks for a specific user wallet. + * + * Loads remote network statuses if they have expired. + * + * @param userWalletId The unique identifier of the user wallet. + * @param networks A set of network IDs which statuses are to be retrieved. + * @return A [Flow] emitting a set of [NetworkStatus] objects corresponding to the specified networks. + */ + fun getNetworkStatusesUpdates(userWalletId: UserWalletId, networks: Set): Flow> + + /** + * Retrieves network statuses of specified blockchain networks for a specific user wallet. + * + * Loads remote network statuses if they have expired or if [refresh] is `true`. * * @param userWalletId The unique identifier of the user wallet. * @param networks A set of network IDs which statuses are to be retrieved. * @param refresh A boolean flag indicating whether the data should be refreshed. * @return A [Flow] emitting a set of [NetworkStatus] objects corresponding to the specified networks. */ - fun getNetworkStatuses( + suspend fun getNetworkStatusesSync( userWalletId: UserWalletId, networks: Set, refresh: Boolean, - ): Flow> + ): Set } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt index 39d6d8489d..7e82d77d52 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt @@ -10,11 +10,23 @@ import kotlinx.coroutines.flow.Flow interface QuotesRepository { /** - * Retrieves the quotes for a set of specified cryptocurrencies, identified by their unique IDs. + * Retrieves updates of quotes for a set of specified cryptocurrencies, identified by their unique IDs. + * + * Loads remote quotes if they have expired. + * + * @param currenciesIds The unique identifiers of the cryptocurrencies for which quotes are to be retrieved. + * @return A [Flow] emitting a set of quotes corresponding to the specified cryptocurrencies. + */ + fun getQuotesUpdates(currenciesIds: Set): Flow> + + /** + * Retrieves quotes for a set of specified cryptocurrencies, identified by their unique IDs. + * + * Loads remote quotes if they have expired or if [refresh] is `true`. * * @param currenciesIds The unique identifiers of the cryptocurrencies for which quotes are to be retrieved. * @param refresh A boolean flag indicating whether the data should be refreshed. * @return A [Flow] emitting a set of quotes corresponding to the specified cryptocurrencies. */ - fun getQuotes(currenciesIds: Set, refresh: Boolean): Flow> + suspend fun getQuotesSync(currenciesIds: Set, refresh: Boolean): Set } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt similarity index 92% rename from domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCaseTest.kt rename to domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt index b063abbd49..41b1dda67b 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt @@ -4,7 +4,7 @@ import arrow.core.Either import arrow.core.left import arrow.core.right import com.tangem.domain.core.error.DataError -import com.tangem.domain.tokens.error.CurrencyError +import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.mock.MockNetworks import com.tangem.domain.tokens.mock.MockQuotes import com.tangem.domain.tokens.mock.MockTokens @@ -25,7 +25,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Test -internal class GetPrimaryCurrencyUseCaseTest { +internal class GetPrimaryCurrencyStatusUpdatesUseCaseTest { private val dispatchers = TestingCoroutineDispatcherProvider() private val userWalletId = UserWalletId(value = null) @@ -47,7 +47,7 @@ internal class GetPrimaryCurrencyUseCaseTest { @Test fun `when token getting failed then error should be received`() = runTest { // Given - val expectedResult = CurrencyError.DataError(DataError.NetworkError.NoInternetConnection).left() + val expectedResult = CurrencyStatusError.DataError(DataError.NetworkError.NoInternetConnection).left() val useCase = getUseCase(token = DataError.NetworkError.NoInternetConnection.left()) @@ -75,7 +75,7 @@ internal class GetPrimaryCurrencyUseCaseTest { @Test fun `when networks statuses getting failed then error should be received`() = runTest { // Given - val expectedResult = CurrencyError.DataError(DataError.NetworkError.NoInternetConnection).left() + val expectedResult = CurrencyStatusError.DataError(DataError.NetworkError.NoInternetConnection).left() val useCase = getUseCase(statuses = flowOf(DataError.NetworkError.NoInternetConnection.left())) @@ -88,7 +88,7 @@ internal class GetPrimaryCurrencyUseCaseTest { @Test fun `when networks statuses flow is empty then error should be received`() = runTest { - val expectedResult = CurrencyError.UnableToCreateCurrency.left() + val expectedResult = CurrencyStatusError.UnableToCreateCurrency.left() val useCase = getUseCase(statuses = flowOf()) @@ -153,7 +153,7 @@ internal class GetPrimaryCurrencyUseCaseTest { removeCurrencyResult: Either = Unit.right(), quotes: Flow>> = flowOf(MockQuotes.quotes.right()), statuses: Flow>> = flowOf(MockNetworks.verifiedNetworksStatuses.right()), - ) = GetPrimaryCurrencyUseCase( + ) = GetPrimaryCurrencyStatusUpdatesUseCase( dispatchers = dispatchers, currenciesRepository = MockCurrenciesRepository( sortTokensResult = Unit.right(), 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 124273c991..b3a5e61a2e 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 @@ -51,27 +51,6 @@ internal class GetTokenListUseCaseTest { assertEquals(expectedResult, result) } - @Test - fun `when list refreshed then correct token list should be returned`() = runTest { - // Given - val expectedResult = listOf( - MockTokenLists.failedUngroupedTokenList.right(), - ) - - val useCase = getUseCase( - isGrouped = flowOf(false.right()), - isSortedByBalance = flowOf(false.right()), - ) - - // When - val result = useCase(userWalletId, refresh = true) - .take(count = 1) - .toList() - - // Then - assertEquals(expectedResult, result) - } - @Test fun `when tokens getting failed then error should be received`() = runTest { // Given @@ -108,20 +87,6 @@ internal class GetTokenListUseCaseTest { assertEquals(expectedResult, result) } - @Test - fun `when networks statuses getting failed then error should be received`() = runTest { - // Given - val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left() - - val useCase = getUseCase(statuses = flowOf(DataError.NetworkError.NoInternetConnection.left())) - - // When - val result = useCase(userWalletId, refresh = true).first() - - // Then - assertEquals(expectedResult, result) - } - @Test fun `when grouping type getting failed then error should be received`() = runTest { // Given diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt index 957095493e..73e096b201 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt @@ -55,10 +55,7 @@ internal class MockCurrenciesRepository( return token.getOrElse { e -> throw e } } - override fun getMultiCurrencyWalletCurrencies( - userWalletId: UserWalletId, - refresh: Boolean, - ): Flow> { + override fun getMultiCurrencyWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow> { return tokens.map { it.getOrElse { e -> throw e } } } diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt index 5ac5c7c8fa..0209a5e953 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt @@ -7,6 +7,7 @@ import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.models.Network import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map internal class MockNetworksRepository( @@ -18,11 +19,18 @@ internal class MockNetworksRepository( return networks.getOrElse { throw it } } - override fun getNetworkStatuses( + override fun getNetworkStatusesUpdates( userWalletId: UserWalletId, networks: Set, - refresh: Boolean, ): Flow> { return statuses.map { it.getOrElse { e -> throw e } } } + + override suspend fun getNetworkStatusesSync( + userWalletId: UserWalletId, + networks: Set, + refresh: Boolean, + ): Set { + return getNetworkStatusesUpdates(userWalletId, networks).first() + } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt index baa2ef1599..3a1470ba1a 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt @@ -6,13 +6,18 @@ import com.tangem.domain.core.error.DataError import com.tangem.domain.tokens.models.CryptoCurrency import com.tangem.domain.tokens.models.Quote import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map internal class MockQuotesRepository( private val quotes: Flow>>, ) : QuotesRepository { - override fun getQuotes(currenciesIds: Set, refresh: Boolean): Flow> { + override fun getQuotesUpdates(currenciesIds: Set): Flow> { return quotes.map { it.getOrElse { e -> throw e } } } + + override suspend fun getQuotesSync(currenciesIds: Set, refresh: Boolean): Set { + return getQuotesUpdates(currenciesIds).first() + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 85c51d80b2..19c2d0c8b4 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -6,7 +6,7 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.marketprice.PriceChangeConfig import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.tokens.error.CurrencyError +import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState @@ -16,9 +16,9 @@ import java.math.BigDecimal internal class TokenDetailsLoadedBalanceConverter( private val currentStateProvider: Provider, private val appCurrencyProvider: Provider, -) : Converter, TokenDetailsState> { +) : Converter, TokenDetailsState> { - override fun convert(value: Either): TokenDetailsState { + override fun convert(value: Either): TokenDetailsState { return value.fold(ifLeft = { convertError() }, ifRight = ::convert) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 74cda4c7d8..e0ad501d38 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -4,7 +4,7 @@ import androidx.paging.PagingData import arrow.core.Either import com.tangem.common.Provider import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.tokens.error.CurrencyError +import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.tokens.models.CryptoCurrency @@ -63,7 +63,7 @@ internal class TokenDetailsStateFactory( } fun getCurrencyLoadedBalanceState( - cryptoCurrencyEither: Either, + cryptoCurrencyEither: Either, ): TokenDetailsState { return tokenDetailsLoadedBalanceConverter.convert(cryptoCurrencyEither) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 758750faca..f4defde6c4 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -11,7 +11,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase -import com.tangem.domain.tokens.GetCurrencyUseCase +import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.models.CryptoCurrency @@ -39,7 +39,7 @@ import kotlin.properties.Delegates internal class TokenDetailsViewModel @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, private val getSelectedWalletUseCase: GetSelectedWalletUseCase, - private val getCurrencyUseCase: GetCurrencyUseCase, + private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase, @@ -71,7 +71,7 @@ internal class TokenDetailsViewModel @Inject constructor( override fun onCreate(owner: LifecycleOwner) { getWallet() - updateContent(selectedWallet = wallet, refresh = false) + updateContent(selectedWallet = wallet) } private fun getWallet() { @@ -82,8 +82,8 @@ internal class TokenDetailsViewModel @Inject constructor( ) } - private fun updateContent(selectedWallet: UserWallet, refresh: Boolean) { - updateMarketPrice(selectedWallet = selectedWallet, refresh = refresh) + private fun updateContent(selectedWallet: UserWallet) { + updateMarketPrice(selectedWallet = selectedWallet) updateButtons(userWalletId = selectedWallet.walletId, currencyId = cryptoCurrency.id.value) updateTxHistory() } @@ -96,8 +96,11 @@ internal class TokenDetailsViewModel @Inject constructor( .launchIn(viewModelScope) } - private fun updateMarketPrice(selectedWallet: UserWallet, refresh: Boolean) { - getCurrencyUseCase(userWalletId = selectedWallet.walletId, currencyId = cryptoCurrency.id, refresh = refresh) + private fun updateMarketPrice(selectedWallet: UserWallet) { + getCurrencyStatusUpdatesUseCase( + userWalletId = selectedWallet.walletId, + currencyId = cryptoCurrency.id, + ) .distinctUntilChanged() .onEach { either -> uiState = stateFactory.getCurrencyLoadedBalanceState(either) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt index 6e1cc58e52..6acdd08c82 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt @@ -7,7 +7,7 @@ import com.tangem.core.ui.components.marketprice.PriceChangeConfig import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.tokens.error.CurrencyError +import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory @@ -170,7 +170,7 @@ internal class WalletSingleCurrencyLoadedBalanceConverter( } data class SingleCurrencyLoadedBalanceModel( - val cryptoCurrencyEither: Either, + val cryptoCurrencyEither: Either, val isRefreshing: Boolean, ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt index af79773b6c..512d0d0cc0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt @@ -5,7 +5,7 @@ import arrow.core.Either import com.tangem.common.Provider import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.tokens.error.CurrencyError +import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenActionsState @@ -199,7 +199,7 @@ internal class WalletStateFactory( fun getLockedState(): WalletState = lockedConverter.convert(Unit) fun getSingleCurrencyLoadedBalanceState( - cryptoCurrencyEither: Either, + cryptoCurrencyEither: Either, isRefreshing: Boolean, ): WalletState { return singleCurrencyLoadedBalanceConverter.convert( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index cbeb3bc707..509377d260 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -22,7 +22,7 @@ import com.tangem.domain.settings.CanUseBiometryUseCase import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase -import com.tangem.domain.tokens.GetPrimaryCurrencyUseCase +import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -73,7 +73,7 @@ internal class WalletViewModel @Inject constructor( private val setAccessCodeRequestPolicyUseCase: SetAccessCodeRequestPolicyUseCase, private val getAccessCodeSavingStatusUseCase: GetAccessCodeSavingStatusUseCase, private val getTokenListUseCase: GetTokenListUseCase, - private val getPrimaryCurrencyUseCase: GetPrimaryCurrencyUseCase, + private val getPrimaryCurrencyUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val getCardWasScannedUseCase: GetCardWasScannedUseCase, private val isUserAlreadyRateAppUseCase: IsUserAlreadyRateAppUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, @@ -180,7 +180,7 @@ internal class WalletViewModel @Inject constructor( "Impossible to update tokens list if state isn't WalletMultiCurrencyState" } - getTokenListUseCase(userWalletId = state.walletsListConfig.wallets[index].id, refresh = isRefreshing) + getTokenListUseCase(userWalletId = state.walletsListConfig.wallets[index].id) .distinctUntilChanged() .onEach { tokenListEither -> uiState = stateFactory.getStateByTokensList(