diff --git a/data/networks/src/main/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusFetcher.kt b/data/networks/src/main/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusFetcher.kt index 71d51a5793..c807f56761 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusFetcher.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusFetcher.kt @@ -3,6 +3,7 @@ package com.tangem.data.networks.multi import arrow.core.Either import arrow.core.raise.either import arrow.core.raise.ensure +import com.tangem.data.networks.store.NetworksStatusesStoreV2 import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import kotlinx.coroutines.async @@ -14,14 +15,21 @@ import javax.inject.Inject * Default implementation of [MultiNetworkStatusFetcher] * * @property singleNetworkStatusFetcher single network status fetcher + * @property networksStatusesStore networks statuses store * [REDACTED_AUTHOR] */ internal class DefaultMultiNetworkStatusFetcher @Inject constructor( private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher, + private val networksStatusesStore: NetworksStatusesStoreV2, ) : MultiNetworkStatusFetcher { override suspend fun invoke(params: MultiNetworkStatusFetcher.Params): Either = either { + // Optimization! + // Every singleNetworkStatusFetcher with applyRefresh as true will refresh every network in the store. + // So if we update all networks at once, it will be more efficient. + networksStatusesStore.refresh(userWalletId = params.userWalletId, networks = params.networks) + val result = coroutineScope { params.networks .map { @@ -30,6 +38,7 @@ internal class DefaultMultiNetworkStatusFetcher @Inject constructor( params = SingleNetworkStatusFetcher.Params( userWalletId = params.userWalletId, network = it, + applyRefresh = false, ), ) } diff --git a/data/networks/src/main/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducer.kt b/data/networks/src/main/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducer.kt index 2e060a8d75..84acf20972 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducer.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducer.kt @@ -6,13 +6,11 @@ import com.tangem.data.networks.store.NetworksStatusesStoreV2 import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.networks.multi.MultiNetworkStatusProducer import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.onEmpty +import kotlinx.coroutines.flow.* import timber.log.Timber /** @@ -22,12 +20,14 @@ import timber.log.Timber * @property networksStatusesStore networks statuses store * @property userWalletsStore user wallets store * @property excludedBlockchains excluded blockchains + * @property dispatchers dispatchers */ internal class DefaultMultiNetworkStatusProducer @AssistedInject constructor( @Assisted val params: MultiNetworkStatusProducer.Params, private val networksStatusesStore: NetworksStatusesStoreV2, private val userWalletsStore: UserWalletsStore, private val excludedBlockchains: ExcludedBlockchains, + private val dispatchers: CoroutineDispatcherProvider, ) : MultiNetworkStatusProducer { override val fallback: Set @@ -57,6 +57,7 @@ internal class DefaultMultiNetworkStatusProducer @AssistedInject constructor( } .distinctUntilChanged() .onEmpty { emit(value = hashSetOf()) } + .flowOn(dispatchers.default) } @AssistedFactory diff --git a/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcher.kt b/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcher.kt index 9da7afbce4..6ded8b6524 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcher.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcher.kt @@ -14,6 +14,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.core.utils.catchOn import com.tangem.domain.demo.DemoConfig import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.tokens.model.CryptoCurrency @@ -51,8 +52,10 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor( private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) private val networkStatusFactory = NetworkStatusFactory() - override suspend fun invoke(params: SingleNetworkStatusFetcher.Params): Either = Either.catch { - networksStatusesStore.refresh(userWalletId = params.userWalletId, network = params.network) + override suspend fun invoke(params: SingleNetworkStatusFetcher.Params) = Either.catchOn(dispatchers.default) { + if (params.applyRefresh) { + networksStatusesStore.refresh(userWalletId = params.userWalletId, network = params.network) + } val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId) val networkCurrencies = createCurrencies(userWallet = userWallet, network = params.network) diff --git a/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducer.kt b/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducer.kt index a91cf178d7..5438949c19 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducer.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducer.kt @@ -4,11 +4,13 @@ import com.tangem.domain.networks.multi.MultiNetworkStatusProducer import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier import com.tangem.domain.networks.single.SingleNetworkStatusProducer import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.mapNotNull /** @@ -16,12 +18,14 @@ import kotlinx.coroutines.flow.mapNotNull * * @property params params * @property multiNetworkStatusSupplier multi network status supplier + * @property dispatchers dispatchers * [REDACTED_AUTHOR] */ internal class DefaultSingleNetworkStatusProducer @AssistedInject constructor( @Assisted val params: SingleNetworkStatusProducer.Params, private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier, + private val dispatchers: CoroutineDispatcherProvider, ) : SingleNetworkStatusProducer { override val fallback: NetworkStatus @@ -35,6 +39,7 @@ internal class DefaultSingleNetworkStatusProducer @AssistedInject constructor( statuses.firstOrNull { it.network == params.network } } .distinctUntilChanged() + .flowOn(dispatchers.default) } @AssistedFactory diff --git a/data/networks/src/main/java/com/tangem/data/networks/store/DefaultNetworksStatusesStoreV2.kt b/data/networks/src/main/java/com/tangem/data/networks/store/DefaultNetworksStatusesStoreV2.kt index 776b446e54..dedbcb34e5 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/store/DefaultNetworksStatusesStoreV2.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/store/DefaultNetworksStatusesStoreV2.kt @@ -58,6 +58,29 @@ internal class DefaultNetworksStatusesStoreV2( updateStatusSourceInRuntime(userWalletId = userWalletId, network = network, source = StatusSource.CACHE) } + override suspend fun refresh(userWalletId: UserWalletId, networks: Set) { + val simpleStatusIds = networks.map(SimpleNetworkStatus::Id) + + runtimeStore.update(default = emptyMap()) { stored -> + stored.toMutableMap().apply { + val storedStatuses = this[userWalletId.stringValue].orEmpty() + + val statuses = simpleStatusIds.mapTo(hashSetOf()) { simpleStatusId -> + val status = storedStatuses.firstOrNull { it.id == simpleStatusId } + ?: createDefaultStatus(id = simpleStatusId) + + status.copy( + value = status.value.copySealed(source = StatusSource.CACHE), + ) + } + + val updatedStatuses = storedStatuses.addOrReplace(statuses) { old, new -> old.id == new.id } + + put(key = userWalletId.stringValue, value = updatedStatuses) + } + } + } + override suspend fun storeActual(userWalletId: UserWalletId, value: NetworkStatus) { if (value.value.source != StatusSource.ACTUAL) { error("Method storeActual can be called only with StatusSource.ACTUAL") @@ -84,10 +107,7 @@ internal class DefaultNetworksStatusesStoreV2( stored.toMutableMap().apply { val status = this[userWalletId.stringValue].orEmpty() .firstOrNull { it.id == simpleStatusId } - ?: SimpleNetworkStatus( - id = simpleStatusId, - value = NetworkStatus.Unreachable(address = null), - ) + ?: createDefaultStatus(id = simpleStatusId) val updatedStatus = status.copy( value = status.value.copySealed(source = source), @@ -130,4 +150,8 @@ internal class DefaultNetworksStatusesStoreV2( } } } + + private fun createDefaultStatus(id: SimpleNetworkStatus.Id): SimpleNetworkStatus { + return SimpleNetworkStatus(id = id, value = NetworkStatus.Unreachable(address = null)) + } } \ No newline at end of file diff --git a/data/networks/src/main/java/com/tangem/data/networks/store/NetworksStatusesStoreV2.kt b/data/networks/src/main/java/com/tangem/data/networks/store/NetworksStatusesStoreV2.kt index 356df1dbf1..3810940ad9 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/store/NetworksStatusesStoreV2.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/store/NetworksStatusesStoreV2.kt @@ -15,6 +15,9 @@ internal interface NetworksStatusesStoreV2 { /** Refresh status of [network] by [userWalletId] */ suspend fun refresh(userWalletId: UserWalletId, network: Network) + /** Refresh statuses of [networks] by [userWalletId] */ + suspend fun refresh(userWalletId: UserWalletId, networks: Set) + /** Store actual [NetworkStatus] by [userWalletId] */ suspend fun storeActual(userWalletId: UserWalletId, value: NetworkStatus) diff --git a/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusFetcherTest.kt b/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusFetcherTest.kt index f74995432d..ab79da0dd1 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusFetcherTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusFetcherTest.kt @@ -4,6 +4,7 @@ import arrow.core.left import arrow.core.right import com.google.common.truth.Truth import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.data.networks.store.NetworksStatusesStoreV2 import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.wallets.models.UserWalletId @@ -19,8 +20,12 @@ import org.junit.Test internal class DefaultMultiNetworkStatusFetcherTest { private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher = mockk() + private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxed = true) - private val fetcher = DefaultMultiNetworkStatusFetcher(singleNetworkStatusFetcher = singleNetworkStatusFetcher) + private val fetcher = DefaultMultiNetworkStatusFetcher( + singleNetworkStatusFetcher = singleNetworkStatusFetcher, + networksStatusesStore = networksStatusesStore, + ) @Test fun `fetch networks statuses successfully`() = runTest { @@ -29,11 +34,13 @@ internal class DefaultMultiNetworkStatusFetcherTest { val ethParams = SingleNetworkStatusFetcher.Params( userWalletId = userWalletId, network = ethereumAndStellar.first(), + applyRefresh = false, ) val stellarParams = SingleNetworkStatusFetcher.Params( userWalletId = userWalletId, network = ethereumAndStellar.last(), + applyRefresh = false, ) coEvery { singleNetworkStatusFetcher(ethParams) } returns Unit.right() @@ -42,6 +49,7 @@ internal class DefaultMultiNetworkStatusFetcherTest { val actual = fetcher(params) coVerify { + networksStatusesStore.refresh(userWalletId = userWalletId, networks = ethereumAndStellar) singleNetworkStatusFetcher(ethParams) singleNetworkStatusFetcher(stellarParams) } @@ -56,11 +64,13 @@ internal class DefaultMultiNetworkStatusFetcherTest { val ethParams = SingleNetworkStatusFetcher.Params( userWalletId = userWalletId, network = ethereumAndStellar.first(), + applyRefresh = false, ) val stellarParams = SingleNetworkStatusFetcher.Params( userWalletId = userWalletId, network = ethereumAndStellar.last(), + applyRefresh = false, ) val ethException = IllegalStateException("eth") @@ -70,6 +80,7 @@ internal class DefaultMultiNetworkStatusFetcherTest { val actual = fetcher(params) coVerify { + networksStatusesStore.refresh(userWalletId = userWalletId, networks = ethereumAndStellar) singleNetworkStatusFetcher(ethParams) singleNetworkStatusFetcher(stellarParams) } @@ -88,11 +99,13 @@ internal class DefaultMultiNetworkStatusFetcherTest { val ethParams = SingleNetworkStatusFetcher.Params( userWalletId = userWalletId, network = ethereumAndStellar.first(), + applyRefresh = false, ) val stellarParams = SingleNetworkStatusFetcher.Params( userWalletId = userWalletId, network = ethereumAndStellar.last(), + applyRefresh = false, ) coEvery { singleNetworkStatusFetcher(ethParams) } returns IllegalStateException("eth").left() @@ -101,6 +114,7 @@ internal class DefaultMultiNetworkStatusFetcherTest { val actual = fetcher(params) coVerify { + networksStatusesStore.refresh(userWalletId = userWalletId, networks = ethereumAndStellar) singleNetworkStatusFetcher(ethParams) singleNetworkStatusFetcher(stellarParams) } diff --git a/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducerTest.kt b/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducerTest.kt index ebed6000a6..274e82eae5 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducerTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducerTest.kt @@ -14,6 +14,7 @@ import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.common.configs.GenericCardConfig import com.tangem.domain.networks.multi.MultiNetworkStatusProducer import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -32,12 +33,14 @@ internal class DefaultMultiNetworkStatusProducerTest { private val networksStatusesStore = mockk() private val userWalletsStore = mockk() private val excludedBlockchains = mockk() + private val dispatchers = TestingCoroutineDispatcherProvider() private val producer = DefaultMultiNetworkStatusProducer( params = params, networksStatusesStore = networksStatusesStore, userWalletsStore = userWalletsStore, excludedBlockchains = excludedBlockchains, + dispatchers = dispatchers, ) @Before diff --git a/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcherTest.kt b/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcherTest.kt index b448cddeec..051cbca91b 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcherTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcherTest.kt @@ -10,9 +10,9 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider -import io.mockk.Ordering import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.coVerifyOrder import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.Test @@ -37,14 +37,18 @@ internal class DefaultSingleNetworkStatusFetcherTest { @Test fun `fetch network status successfully`() = runTest { - val params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = network) + val params = SingleNetworkStatusFetcher.Params( + userWalletId = userWalletId, + network = network, + applyRefresh = true, + ) val result = UpdateWalletManagerResult.MissedDerivation coEvery { walletManagersFacade.update(userWalletId, network, emptySet()) } returns result val actual = fetcher(params) - coVerify(ordering = Ordering.SEQUENCE) { + coVerifyOrder { networksStatusesStore.refresh(userWalletId = userWalletId, network = network) userWalletsStore.getSyncStrict(key = userWalletId) walletManagersFacade.update(userWalletId, network, emptySet()) @@ -59,14 +63,18 @@ internal class DefaultSingleNetworkStatusFetcherTest { @Test fun `fetch network status failure`() = runTest { - val params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = network) + val params = SingleNetworkStatusFetcher.Params( + userWalletId = userWalletId, + network = network, + applyRefresh = true, + ) val exception = IllegalStateException() coEvery { userWalletsStore.getSyncStrict(key = userWalletId) } throws exception val actual = fetcher(params) - coVerify(ordering = Ordering.SEQUENCE) { + coVerifyOrder { networksStatusesStore.refresh(userWalletId = userWalletId, network = network) userWalletsStore.getSyncStrict(key = userWalletId) networksStatusesStore.storeError(userWalletId = userWalletId, network = network) @@ -81,6 +89,35 @@ internal class DefaultSingleNetworkStatusFetcherTest { Truth.assertThat(actual.leftOrNull()).isEqualTo(exception) } + @Test + fun `fetch network status if applyRefresh is false`() = runTest { + val params = SingleNetworkStatusFetcher.Params( + userWalletId = userWalletId, + network = network, + applyRefresh = false, + ) + + val result = UpdateWalletManagerResult.MissedDerivation + coEvery { walletManagersFacade.update(userWalletId, network, emptySet()) } returns result + + val actual = fetcher(params) + + coVerifyOrder { + userWalletsStore.getSyncStrict(key = userWalletId) + walletManagersFacade.update(userWalletId, network, emptySet()) + networksStatusesStore.storeActual( + userWalletId = userWalletId, + value = NetworkStatus(network, NetworkStatus.MissedDerivation), + ) + } + + coVerify(inverse = true) { + networksStatusesStore.refresh(userWalletId = any(), network = any()) + } + + Truth.assertThat(actual.isRight()).isTrue() + } + private companion object { val userWalletId = UserWalletId("011") val network = MockCryptoCurrencyFactory().ethereum.network diff --git a/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducerTest.kt b/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducerTest.kt index 220e8b819f..98894ea109 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducerTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducerTest.kt @@ -9,6 +9,7 @@ import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier import com.tangem.domain.networks.single.SingleNetworkStatusProducer import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -27,10 +28,12 @@ internal class DefaultSingleNetworkStatusProducerTest { ) private val multiNetworkStatusSupplier = mockk() + private val dispatchers = TestingCoroutineDispatcherProvider() private val producer = DefaultSingleNetworkStatusProducer( params = params, multiNetworkStatusSupplier = multiNetworkStatusSupplier, + dispatchers = dispatchers, ) @Test diff --git a/data/networks/src/test/java/com/tangem/data/networks/store/NetworkStatusesStoreUpdateMethodsTest.kt b/data/networks/src/test/java/com/tangem/data/networks/store/NetworkStatusesStoreUpdateMethodsTest.kt index d2e5f3c533..57aea3c1e2 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/store/NetworkStatusesStoreUpdateMethodsTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/store/NetworkStatusesStoreUpdateMethodsTest.kt @@ -32,7 +32,7 @@ internal class NetworkStatusesStoreUpdateMethodsTest { ) @Test - fun `refresh if runtime store is empty`() = runTest { + fun `refresh the single network if runtime store is empty`() = runTest { store.refresh(userWalletId = userWalletId, network = network) val runtimeExpected = mapOf( @@ -49,7 +49,7 @@ internal class NetworkStatusesStoreUpdateMethodsTest { } @Test - fun `refresh if runtime store contains status with this network`() = runTest { + fun `refresh the single network if runtime store contains status with this network`() = runTest { val status = MockNetworkStatusFactory.createVerified().toSimple() runtimeStore.store( @@ -68,6 +68,45 @@ internal class NetworkStatusesStoreUpdateMethodsTest { Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap>()) } + @Test + fun `refresh the multi networks if runtime store is empty`() = runTest { + store.refresh(userWalletId = userWalletId, networks = networks) + + val runtimeExpected = mapOf( + userWalletId.stringValue to networks.mapTo(hashSetOf()) { + SimpleNetworkStatus( + id = SimpleNetworkStatus.Id(it), + value = NetworkStatus.Unreachable(address = null), + ) + }, + ) + + Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected) + Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap>()) + } + + @Test + fun `refresh the multi networks if runtime store contains status with this network`() = runTest { + val firstStatus = MockNetworkStatusFactory.createVerified(network = networks.first()).toSimple() + val secondStatus = MockNetworkStatusFactory.createVerified(network = networks.last()).toSimple() + + runtimeStore.store( + value = mapOf(userWalletId.stringValue to setOf(firstStatus, secondStatus)), + ) + + store.refresh(userWalletId = userWalletId, networks = networks) + + val runtimeExpected = mapOf( + userWalletId.stringValue to setOf( + firstStatus.copy(value = firstStatus.value.copySealed(source = StatusSource.CACHE)), + secondStatus.copy(value = secondStatus.value.copySealed(source = StatusSource.CACHE)), + ), + ) + + Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected) + Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap>()) + } + @Test fun `store actual with any status sources`() = runTest { val expectedErrorMessage = "Method storeActual can be called only with StatusSource.ACTUAL" @@ -210,5 +249,7 @@ internal class NetworkStatusesStoreUpdateMethodsTest { val userWalletId = UserWalletId(stringValue = "011") val network = MockCryptoCurrencyFactory().ethereum.network + + val networks = MockCryptoCurrencyFactory().ethereumAndStellar.mapTo(hashSetOf()) { it.network } } } \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/utils/EitherExt.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/utils/EitherExt.kt index 457f3e4f5b..b961210633 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/utils/EitherExt.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/utils/EitherExt.kt @@ -2,7 +2,9 @@ package com.tangem.domain.core.utils import arrow.core.Either import com.tangem.domain.core.lce.Lce +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext /** * [Flow] of [Either] @@ -34,4 +36,15 @@ inline fun Either.toLce(isStillLoading: } } } +} + +@JvmName("tryCatchWithDispatch") +suspend inline fun Either.Companion.catchOn( + dispatcher: CoroutineDispatcher, + crossinline function: suspend () -> R, +): Either { + return withContext( + context = dispatcher, + block = { catch { function() } }, + ) } \ No newline at end of file diff --git a/domain/networks/src/main/java/com/tangem/domain/networks/single/SingleNetworkStatusFetcher.kt b/domain/networks/src/main/java/com/tangem/domain/networks/single/SingleNetworkStatusFetcher.kt index 791e66ea05..d9ab0fbece 100644 --- a/domain/networks/src/main/java/com/tangem/domain/networks/single/SingleNetworkStatusFetcher.kt +++ b/domain/networks/src/main/java/com/tangem/domain/networks/single/SingleNetworkStatusFetcher.kt @@ -11,5 +11,16 @@ import com.tangem.domain.wallets.models.UserWalletId */ interface SingleNetworkStatusFetcher : FlowFetcher { - data class Params(val userWalletId: UserWalletId, val network: Network) + /** + * Params + * + * @property userWalletId wallet id + * @property network network + * @property applyRefresh flag that determines whether to apply refresh (see DefaultMultiNetworkStatusFetcher) + */ + data class Params( + val userWalletId: UserWalletId, + val network: Network, + val applyRefresh: Boolean = true, + ) } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt index b43e518a08..3334482cf9 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt @@ -68,13 +68,12 @@ class CachedCurrenciesStatusesOperations( val prevStatuses = MutableStateFlow(value = emptyList()) val isUpdating = MutableStateFlow(value = true) - val isFetchingStarted = MutableStateFlow(value = false) val nonEmptyCurrencies = currenciesFlow.mapNotNull { it.getOrNull() }.firstOrNull()?.toNonEmptyListOrNull() - if (nonEmptyCurrencies != null) { + if (!isFetchingStarted(userWalletId) && nonEmptyCurrencies != null) { launch { - isFetchingStarted.value = true + setFetchStarted(userWalletId) val (networks, currenciesIds) = getIds(nonEmptyCurrencies) fetchComponents(userWalletId, networks, currenciesIds, nonEmptyCurrencies) @@ -132,9 +131,9 @@ class CachedCurrenciesStatusesOperations( ) } - if (!isFetchingStarted.value) { + if (!isFetchingStarted(userWalletId)) { launch { - isFetchingStarted.value = true + setFetchStarted(userWalletId) fetchComponents(userWalletId, networks, currenciesIds, currencies) } @@ -331,6 +330,7 @@ class CachedCurrenciesStatusesOperations( } // temporary code because token list is built using networks list + @OptIn(FlowPreview::class) private fun getNetworkStatusesUpdates( userWalletId: UserWalletId, networks: NonEmptySet, @@ -356,11 +356,26 @@ class CachedCurrenciesStatusesOperations( .onEach(::send) .launchIn(scope = this) } + .debounce(timeoutMillis = 500) .map, Either>> { it.right() } .distinctUntilChanged() } + private fun isFetchingStarted(userWalletId: UserWalletId): Boolean { + return fetchingState.value[userWalletId] ?: false + } + + private fun setFetchStarted(userWalletId: UserWalletId) { + fetchingState.update { + it.toMutableMap().apply { + put(key = userWalletId, value = true) + } + } + } + companion object { internal const val RETRY_DELAY = 2000L + + private val fetchingState = MutableStateFlow(value = emptyMap()) } } \ No newline at end of file