Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-09 13:47:13 +04:00
parent 55f731f1c3
commit b89d98aa35
12 changed files with 358 additions and 0 deletions

View file

@ -8,6 +8,7 @@ import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.networks.repository.DefaultNetworksRepository
import com.tangem.data.networks.store.DefaultNetworksStatusesStore
import com.tangem.data.networks.store.NetworksStatusesStore
import com.tangem.data.networks.utils.DefaultNetworksCleaner
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.datasource.local.network.entity.NetworkStatusDM
@ -15,6 +16,7 @@ import com.tangem.datasource.utils.MoshiDataStoreSerializer
import com.tangem.datasource.utils.mapWithStringKeyTypes
import com.tangem.datasource.utils.setTypes
import com.tangem.domain.networks.repository.NetworksRepository
import com.tangem.domain.networks.utils.NetworksCleaner
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -67,4 +69,18 @@ internal object NetworkDataModule {
dispatchers = dispatchers,
)
}
@Provides
@Singleton
fun provideNetworksCleaner(
networksStatusesStore: NetworksStatusesStore,
walletManagersFacade: WalletManagersFacade,
dispatchers: CoroutineDispatcherProvider,
): NetworksCleaner {
return DefaultNetworksCleaner(
networksStatusesStore = networksStatusesStore,
walletManagersFacade = walletManagersFacade,
dispatchers = dispatchers,
)
}
}

View file

@ -96,6 +96,20 @@ internal class DefaultNetworksStatusesStore(
}
}
override suspend fun clear(userWalletId: UserWalletId, networks: Set<Network>) {
persistenceDataStore.updateData { storedStatuses ->
storedStatuses.toMutableMap().apply {
val updatedValues = this[userWalletId.stringValue].orEmpty().filterNot {
networks.any { network ->
it.networkId.value == network.rawId && it.derivationPath.value == network.derivationPath.value
}
}
this[userWalletId.stringValue] = updatedValues.toSet()
}
}
}
private suspend fun updateInRuntime(
userWalletId: UserWalletId,
networks: Set<Network>,

View file

@ -41,4 +41,7 @@ internal interface NetworksStatusesStore {
* See complex methods in `NetworksStatusesStoreExt`.
*/
suspend fun store(userWalletId: UserWalletId, status: NetworkStatus)
/** Clear statuses of [networks] by [userWalletId] */
suspend fun clear(userWalletId: UserWalletId, networks: Set<Network>)
}

View file

@ -0,0 +1,73 @@
package com.tangem.data.networks.utils
import com.tangem.data.networks.store.NetworksStatusesStore
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.networks.utils.NetworksCleaner
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* Default implementation of [NetworksCleaner].
*
* @property networksStatusesStore Store to manage network statuses.
* @property walletManagersFacade Facade to manage wallet managers.
* @property dispatchers Coroutine dispatchers provider.
*
[REDACTED_AUTHOR]
*/
internal class DefaultNetworksCleaner(
private val networksStatusesStore: NetworksStatusesStore,
private val walletManagersFacade: WalletManagersFacade,
private val dispatchers: CoroutineDispatcherProvider,
) : NetworksCleaner {
override suspend fun invoke(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
withContext(dispatchers.default) {
val (networks, tokens) = currencies.partitionByType()
coroutineScope {
launch { cleanStore(userWalletId = userWalletId, networks = networks) }
launch { cleanWalletManager(userWalletId = userWalletId, networks = networks, tokens = tokens) }
}
}
}
private suspend fun cleanStore(userWalletId: UserWalletId, networks: Set<Network>) {
if (networks.isNotEmpty()) {
networksStatusesStore.clear(userWalletId = userWalletId, networks = networks)
}
}
private suspend fun cleanWalletManager(
userWalletId: UserWalletId,
networks: Set<Network>,
tokens: Set<CryptoCurrency.Token>,
) {
if (networks.isNotEmpty()) {
walletManagersFacade.remove(userWalletId = userWalletId, networks = networks)
}
if (tokens.isNotEmpty()) {
walletManagersFacade.removeTokens(userWalletId = userWalletId, tokens = tokens)
}
}
private fun List<CryptoCurrency>.partitionByType(): Pair<Set<Network>, Set<CryptoCurrency.Token>> {
val networks = mutableSetOf<Network>()
val tokens = mutableSetOf<CryptoCurrency.Token>()
for (currency in this) {
when (currency) {
is CryptoCurrency.Coin -> networks.add(currency.network)
is CryptoCurrency.Token -> tokens.add(currency)
}
}
return Pair(networks, tokens)
}
}

View file

@ -0,0 +1,101 @@
package com.tangem.data.networks.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.data.networks.store.NetworksStatusesStore
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.clearMocks
import io.mockk.coVerifyOrder
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DefaultNetworksCleanerTest {
private val networksStatusesStore = mockk<NetworksStatusesStore>(relaxed = true)
private val walletManagersFacade = mockk<WalletManagersFacade>(relaxed = true)
private val cleaner = DefaultNetworksCleaner(
networksStatusesStore = networksStatusesStore,
walletManagersFacade = walletManagersFacade,
dispatchers = TestingCoroutineDispatcherProvider(),
)
private val userWalletId = UserWalletId("011")
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
private val network = cryptoCurrencyFactory.ethereum.network
private val coin = cryptoCurrencyFactory.ethereum
private val token = cryptoCurrencyFactory.createToken(Blockchain.Ethereum)
@BeforeEach
fun setUp() {
clearMocks(networksStatusesStore, walletManagersFacade)
}
@Test
fun `should clear networks and remove managers and tokens when called`() = runTest {
// Arrange
val currencies = listOf(coin, token)
// Act
cleaner(userWalletId = userWalletId, currencies = currencies)
// Assert
coVerifyOrder {
networksStatusesStore.clear(userWalletId, setOf(network))
walletManagersFacade.remove(userWalletId = userWalletId, networks = setOf(network))
walletManagersFacade.removeTokens(userWalletId = userWalletId, tokens = setOf(token))
}
}
@Test
fun `should handle empty currencies`() = runTest {
// Act
cleaner(userWalletId = userWalletId, currencies = emptyList())
// Assert
coVerifyOrder(inverse = true) {
networksStatusesStore.clear(userWalletId = any(), networks = any())
walletManagersFacade.remove(userWalletId = any(), networks = any())
walletManagersFacade.removeTokens(userWalletId = any(), tokens = any())
}
}
@Test
fun `should clear only networks when there are no tokens`() = runTest {
val currencies = listOf(coin)
cleaner(userWalletId = userWalletId, currencies = currencies)
coVerifyOrder {
networksStatusesStore.clear(userWalletId, setOf(network))
walletManagersFacade.remove(userWalletId = userWalletId, networks = setOf(network))
}
coVerifyOrder(inverse = true) {
walletManagersFacade.removeTokens(userWalletId = any(), tokens = any())
}
}
@Test
fun `should clear only tokens when there are no networks`() = runTest {
// Arrange
val currencies = listOf(token)
// Act
cleaner(userWalletId = userWalletId, currencies = currencies)
// Assert
coVerifyOrder {
walletManagersFacade.removeTokens(userWalletId = userWalletId, tokens = setOf(token))
}
coVerifyOrder(inverse = true) {
networksStatusesStore.clear(userWalletId = any(), networks = any())
walletManagersFacade.remove(userWalletId = any(), networks = any())
}
}
}