Updated on 2026-08-14
This commit is contained in:
parent
60ff94372b
commit
13a9dba8ab
14 changed files with 206 additions and 24 deletions
|
|
@ -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<Throwable, Unit> = 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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<NetworkStatus>
|
||||
|
|
@ -57,6 +57,7 @@ internal class DefaultMultiNetworkStatusProducer @AssistedInject constructor(
|
|||
}
|
||||
.distinctUntilChanged()
|
||||
.onEmpty { emit(value = hashSetOf()) }
|
||||
.flowOn(dispatchers.default)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -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<Throwable, Unit> = 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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -58,6 +58,29 @@ internal class DefaultNetworksStatusesStoreV2(
|
|||
updateStatusSourceInRuntime(userWalletId = userWalletId, network = network, source = StatusSource.CACHE)
|
||||
}
|
||||
|
||||
override suspend fun refresh(userWalletId: UserWalletId, networks: Set<Network>) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Network>)
|
||||
|
||||
/** Store actual [NetworkStatus] by [userWalletId] */
|
||||
suspend fun storeActual(userWalletId: UserWalletId, value: NetworkStatus)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<NetworksStatusesStoreV2>()
|
||||
private val userWalletsStore = mockk<UserWalletsStore>()
|
||||
private val excludedBlockchains = mockk<ExcludedBlockchains>()
|
||||
private val dispatchers = TestingCoroutineDispatcherProvider()
|
||||
|
||||
private val producer = DefaultMultiNetworkStatusProducer(
|
||||
params = params,
|
||||
networksStatusesStore = networksStatusesStore,
|
||||
userWalletsStore = userWalletsStore,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
||||
@Before
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<MultiNetworkStatusSupplier>()
|
||||
private val dispatchers = TestingCoroutineDispatcherProvider()
|
||||
|
||||
private val producer = DefaultSingleNetworkStatusProducer(
|
||||
params = params,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -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<String, Set<NetworkStatusDM>>())
|
||||
}
|
||||
|
||||
@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<String, Set<NetworkStatusDM>>())
|
||||
}
|
||||
|
||||
@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<String, Set<NetworkStatusDM>>())
|
||||
}
|
||||
|
||||
@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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <reified E : Any, reified T : Any> Either<E, T>.toLce(isStillLoading:
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmName("tryCatchWithDispatch")
|
||||
suspend inline fun <R> Either.Companion.catchOn(
|
||||
dispatcher: CoroutineDispatcher,
|
||||
crossinline function: suspend () -> R,
|
||||
): Either<Throwable, R> {
|
||||
return withContext(
|
||||
context = dispatcher,
|
||||
block = { catch { function() } },
|
||||
)
|
||||
}
|
||||
|
|
@ -11,5 +11,16 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
*/
|
||||
interface SingleNetworkStatusFetcher : FlowFetcher<SingleNetworkStatusFetcher.Params> {
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
|
@ -68,13 +68,12 @@ class CachedCurrenciesStatusesOperations(
|
|||
val prevStatuses = MutableStateFlow(value = emptyList<CryptoCurrencyStatus>())
|
||||
|
||||
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<Network>,
|
||||
|
|
@ -356,11 +356,26 @@ class CachedCurrenciesStatusesOperations(
|
|||
.onEach(::send)
|
||||
.launchIn(scope = this)
|
||||
}
|
||||
.debounce(timeoutMillis = 500)
|
||||
.map<Set<NetworkStatus>, Either<TokenListError, Set<NetworkStatus>>> { 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<UserWalletId, Boolean>())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue