Updated on 2026-08-14
This commit is contained in:
parent
ebb562b2a0
commit
ae759af1c7
14 changed files with 422 additions and 140 deletions
|
|
@ -142,22 +142,6 @@ internal object NFTDomainModule {
|
|||
return GetWalletNFTEnabledUseCase(walletsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideClearNFTCacheUseCase(
|
||||
nftCleaner: NFTCleaner,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
singleAccountListSupplier: SingleAccountListSupplier,
|
||||
): ObserveAndClearNFTCacheIfNeedUseCase {
|
||||
return ObserveAndClearNFTCacheIfNeedUseCase(
|
||||
nftCleaner = nftCleaner,
|
||||
currenciesRepository = currenciesRepository,
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
singleAccountListSupplier = singleAccountListSupplier,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetNftCurrencyUseCase(nftRepository: NFTRepository): GetNFTCurrencyUseCase {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ 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 com.tangem.utils.coroutines.runSuspendCatching
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Default implementation of [NetworksCleaner].
|
||||
|
|
@ -27,33 +29,51 @@ internal class DefaultNetworksCleaner(
|
|||
) : NetworksCleaner {
|
||||
|
||||
override suspend fun invoke(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
if (currencies.isEmpty()) {
|
||||
Timber.d("No currencies to clear for wallet: $userWalletId")
|
||||
return
|
||||
}
|
||||
|
||||
withContext(dispatchers.default) {
|
||||
val (networks, tokens) = currencies.partitionByType()
|
||||
|
||||
coroutineScope {
|
||||
launch { cleanStore(userWalletId = userWalletId, networks = networks) }
|
||||
launch { cleanWalletManager(userWalletId = userWalletId, networks = networks, tokens = tokens) }
|
||||
}
|
||||
awaitAll(
|
||||
async { clearStatusesStore(userWalletId = userWalletId, networks = networks) },
|
||||
async { clearBlockchainSDK(userWalletId = userWalletId, networks = networks, tokens = tokens) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun cleanStore(userWalletId: UserWalletId, networks: Set<Network>) {
|
||||
private suspend fun clearStatusesStore(userWalletId: UserWalletId, networks: Set<Network>) {
|
||||
if (networks.isNotEmpty()) {
|
||||
networksStatusesStore.clear(userWalletId = userWalletId, networks = networks)
|
||||
runSuspendCatching {
|
||||
networksStatusesStore.clear(userWalletId = userWalletId, networks = networks)
|
||||
}
|
||||
.onFailure { Timber.e(it, "Failed to clear network statuses for wallet: $userWalletId") }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun cleanWalletManager(
|
||||
private suspend fun clearBlockchainSDK(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network>,
|
||||
tokens: Set<CryptoCurrency.Token>,
|
||||
) {
|
||||
if (networks.isNotEmpty()) {
|
||||
walletManagersFacade.remove(userWalletId = userWalletId, networks = networks)
|
||||
runSuspendCatching {
|
||||
walletManagersFacade.remove(userWalletId = userWalletId, networks = networks)
|
||||
}
|
||||
.onFailure {
|
||||
Timber.e(it, "Failed to remove networks from Blockchain SDK for wallet: $userWalletId")
|
||||
}
|
||||
}
|
||||
|
||||
if (tokens.isNotEmpty()) {
|
||||
walletManagersFacade.removeTokens(userWalletId = userWalletId, tokens = tokens)
|
||||
runSuspendCatching {
|
||||
walletManagersFacade.removeTokens(userWalletId = userWalletId, tokens = tokens)
|
||||
}
|
||||
.onFailure {
|
||||
Timber.e(it, "Failed to remove tokens from Blockchain SDK for wallet: $userWalletId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ 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.coEvery
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -45,7 +46,7 @@ class DefaultNetworksCleanerTest {
|
|||
|
||||
// Assert
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.clear(userWalletId, setOf(network))
|
||||
networksStatusesStore.clear(userWalletId = userWalletId, networks = setOf(network))
|
||||
walletManagersFacade.remove(userWalletId = userWalletId, networks = setOf(network))
|
||||
walletManagersFacade.removeTokens(userWalletId = userWalletId, tokens = setOf(token))
|
||||
}
|
||||
|
|
@ -66,12 +67,15 @@ class DefaultNetworksCleanerTest {
|
|||
|
||||
@Test
|
||||
fun `should clear only networks when there are no tokens`() = runTest {
|
||||
// Arrange
|
||||
val currencies = listOf(coin)
|
||||
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, currencies = currencies)
|
||||
|
||||
// Assert
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.clear(userWalletId, setOf(network))
|
||||
networksStatusesStore.clear(userWalletId = userWalletId, networks = setOf(network))
|
||||
walletManagersFacade.remove(userWalletId = userWalletId, networks = setOf(network))
|
||||
}
|
||||
|
||||
|
|
@ -98,4 +102,27 @@ class DefaultNetworksCleanerTest {
|
|||
walletManagersFacade.remove(userWalletId = any(), networks = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should handle exception during cleaning`() = runTest {
|
||||
// Arrange
|
||||
val currencies = listOf(coin, token)
|
||||
|
||||
val exception = Exception("Test exception")
|
||||
coEvery { networksStatusesStore.clear(userWalletId, setOf(network)) } throws exception
|
||||
coEvery { walletManagersFacade.remove(userWalletId = userWalletId, networks = setOf(network)) } throws exception
|
||||
coEvery {
|
||||
walletManagersFacade.removeTokens(userWalletId = userWalletId, tokens = setOf(token))
|
||||
} throws exception
|
||||
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, currencies = currencies)
|
||||
|
||||
// Assert
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.clear(userWalletId = userWalletId, networks = setOf(network))
|
||||
walletManagersFacade.remove(userWalletId = userWalletId, networks = setOf(network))
|
||||
walletManagersFacade.removeTokens(userWalletId = userWalletId, tokens = setOf(token))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
|||
import com.tangem.datasource.local.token.P2PEthPoolVaultsStore
|
||||
import com.tangem.datasource.local.token.StakingActionsStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.repositories.*
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.staking.utils.StakingCleaner
|
||||
|
|
@ -136,10 +137,12 @@ internal object StakingDataModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingCleaner(
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
stakingBalancesStore: StakingBalancesStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): StakingCleaner {
|
||||
return DefaultStakingCleaner(
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
stakingBalancesStore = stakingBalancesStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
package com.tangem.data.staking.utils
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.data.staking.store.StakingBalancesStore
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.utils.StakingCleaner
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Default implementation of [StakingCleaner].
|
||||
|
|
@ -15,15 +22,46 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultStakingCleaner(
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
private val stakingBalancesStore: StakingBalancesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : StakingCleaner {
|
||||
|
||||
override suspend fun invoke(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
|
||||
if (stakingIds.isEmpty()) return
|
||||
override suspend fun invoke(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
if (currencies.isEmpty()) {
|
||||
Timber.d("No currencies to clear for wallet: $userWalletId")
|
||||
return
|
||||
}
|
||||
|
||||
with(dispatchers.default) {
|
||||
stakingBalancesStore.clear(userWalletId, stakingIds)
|
||||
val stakingIds = currencies.mapNotNullTo(hashSetOf()) {
|
||||
stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = it).getOrNull()
|
||||
}
|
||||
|
||||
if (stakingIds.isEmpty()) {
|
||||
Timber.d("All currencies have no stakingIds to clear for wallet: $userWalletId")
|
||||
return
|
||||
}
|
||||
|
||||
invoke(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||
}
|
||||
|
||||
override suspend fun invoke(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
|
||||
if (stakingIds.isEmpty()) {
|
||||
Timber.d("No stakingIds to clear for wallet: $userWalletId")
|
||||
return
|
||||
}
|
||||
|
||||
withContext(dispatchers.default) {
|
||||
awaitAll(
|
||||
async { clearStatusesStore(userWalletId = userWalletId, stakingIds = stakingIds) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun clearStatusesStore(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
|
||||
runSuspendCatching {
|
||||
stakingBalancesStore.clear(userWalletId, stakingIds)
|
||||
}
|
||||
.onFailure { Timber.e(it, "Failed to clear yield balance statuses for wallet: $userWalletId") }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +1,146 @@
|
|||
package com.tangem.data.staking.utils
|
||||
|
||||
import arrow.core.right
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.data.staking.store.StakingBalancesStore
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultStakingCleanerTest {
|
||||
|
||||
private val stakingIdFactory = mockk<StakingIdFactory>(relaxed = true)
|
||||
private val stakingBalancesStore = mockk<StakingBalancesStore>(relaxed = true)
|
||||
private val cleaner = DefaultStakingCleaner(
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
stakingBalancesStore = stakingBalancesStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val stakingIds = setOf(
|
||||
StakingID(integrationId = StakingIntegrationID.StakeKit.Coin.Cardano.value, address = "0x1"),
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
clearMocks(stakingBalancesStore)
|
||||
clearMocks(stakingIdFactory, stakingBalancesStore)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should clear yields balances when called`() = runTest {
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class ClearByStakingIds {
|
||||
|
||||
// Assert
|
||||
coVerifyOrder {
|
||||
stakingBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||
private val stakingIds = setOf(
|
||||
StakingID(integrationId = StakingIntegrationID.StakeKit.Coin.Cardano.value, address = "0x1"),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `should clear yields balances when called`() = runTest {
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||
|
||||
// Assert
|
||||
coVerifyOrder {
|
||||
stakingBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should handle empty stakingIds`() = runTest {
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, stakingIds = emptySet())
|
||||
|
||||
// Assert
|
||||
coVerifyOrder(inverse = true) {
|
||||
stakingBalancesStore.clear(userWalletId = any(), stakingIds = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should catch exception from stakingBalancesStore and not throw`() = runTest {
|
||||
// Arrange
|
||||
coEvery { stakingBalancesStore.clear(userWalletId, stakingIds) } throws Exception()
|
||||
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||
|
||||
// Assert
|
||||
coVerifyOrder {
|
||||
stakingBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should handle empty stakingIds`() = runTest {
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, stakingIds = emptySet())
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class ClearByCurrencies {
|
||||
|
||||
// Assert
|
||||
coVerifyOrder(inverse = true) {
|
||||
stakingBalancesStore.clear(userWalletId = any(), stakingIds = any())
|
||||
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
private val coin = cryptoCurrencyFactory.ethereum
|
||||
|
||||
@Test
|
||||
fun `should clear yields balances when called with single currency`() = runTest {
|
||||
// Arrange
|
||||
val stakingId = StakingID(
|
||||
integrationId = "stake_kit_coin_eth",
|
||||
address = "0xabc",
|
||||
)
|
||||
|
||||
coEvery { stakingIdFactory.create(userWalletId, coin) } returns stakingId.right()
|
||||
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, currency = coin)
|
||||
|
||||
// Assert
|
||||
coVerifyOrder {
|
||||
stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = coin)
|
||||
stakingBalancesStore.clear(userWalletId = userWalletId, stakingIds = setOf(stakingId))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should handle empty list of currencies`() = runTest {
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, currencies = emptyList())
|
||||
|
||||
// Assert
|
||||
coVerifyOrder(inverse = true) {
|
||||
stakingIdFactory.create(userWalletId = any(), cryptoCurrency = any())
|
||||
stakingBalancesStore.clear(userWalletId = any(), stakingIds = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should catch exception from stakingBalancesStore and not throw`() = runTest {
|
||||
// Arrange
|
||||
val stakingId = StakingID(
|
||||
integrationId = "stake_kit_coin_eth",
|
||||
address = "0xabc",
|
||||
)
|
||||
|
||||
coEvery { stakingIdFactory.create(userWalletId, coin) } returns stakingId.right()
|
||||
coEvery {
|
||||
stakingBalancesStore.clear(userWalletId = userWalletId, stakingIds = setOf(stakingId))
|
||||
} throws Exception()
|
||||
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, currency = coin)
|
||||
|
||||
// Assert
|
||||
coVerifyOrder {
|
||||
stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = coin)
|
||||
stakingBalancesStore.clear(userWalletId = userWalletId, stakingIds = setOf(stakingId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,11 +5,13 @@ import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
|||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.*
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyMetadataCleaner
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
||||
import com.tangem.domain.networks.utils.NetworksCleaner
|
||||
import com.tangem.domain.nft.utils.NFTCleaner
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
||||
|
|
@ -98,9 +100,7 @@ internal object AccountStatusUseCaseModule {
|
|||
derivationsRepository: DerivationsRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
networksCleaner: NetworksCleaner,
|
||||
stakingCleaner: StakingCleaner,
|
||||
cryptoCurrencyMetadataCleaner: CryptoCurrencyMetadataCleaner,
|
||||
expressServiceFetcher: ExpressServiceFetcher,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ManageCryptoCurrenciesUseCase {
|
||||
|
|
@ -111,9 +111,7 @@ internal object AccountStatusUseCaseModule {
|
|||
derivationsRepository = derivationsRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
networksCleaner = networksCleaner,
|
||||
stakingCleaner = stakingCleaner,
|
||||
cryptoCurrencyMetadataCleaner = cryptoCurrencyMetadataCleaner,
|
||||
expressServiceFetcher = expressServiceFetcher,
|
||||
parallelUpdatingScope = CoroutineScope(SupervisorJob() + dispatchers.default),
|
||||
dispatchers = dispatchers,
|
||||
|
|
@ -157,4 +155,20 @@ internal object AccountStatusUseCaseModule {
|
|||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCryptoCurrencyMetadataCleaner(
|
||||
networksCleaner: NetworksCleaner,
|
||||
stakingCleaner: StakingCleaner,
|
||||
nftCleaner: NFTCleaner,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): CryptoCurrencyMetadataCleaner {
|
||||
return CryptoCurrencyMetadataCleaner(
|
||||
networksCleaner = networksCleaner,
|
||||
stakingCleaner = stakingCleaner,
|
||||
nftCleaner = nftCleaner,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.account.repository.AccountsCRUDRepository
|
|||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyMetadataCleaner
|
||||
import com.tangem.domain.core.utils.eitherOn
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.express.models.ExpressAsset
|
||||
|
|
@ -17,9 +18,6 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
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.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.utils.StakingCleaner
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
|
|
@ -36,9 +34,7 @@ import timber.log.Timber
|
|||
* @property currenciesRepository Repository for managing currencies.
|
||||
* @property derivationsRepository Repository for deriving public keys.
|
||||
* @property cryptoCurrencyBalanceFetcher Fetcher for updating crypto currency balances.
|
||||
* @property stakingIdFactory Factory for creating staking IDs.
|
||||
* @property networksCleaner Cleaner for removing obsolete network data.
|
||||
* @property stakingCleaner Cleaner for removing obsolete staking data.
|
||||
* @property cryptoCurrencyMetadataCleaner Cleaner for removing metadata of deleted currencies.
|
||||
* @property expressServiceFetcher Fetcher for updating express service data.
|
||||
* @property parallelUpdatingScope Coroutine scope for parallel updates.
|
||||
* @property dispatchers Coroutine dispatchers for managing threading.
|
||||
|
|
@ -53,9 +49,7 @@ class ManageCryptoCurrenciesUseCase(
|
|||
private val derivationsRepository: DerivationsRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
private val networksCleaner: NetworksCleaner,
|
||||
private val stakingCleaner: StakingCleaner,
|
||||
private val cryptoCurrencyMetadataCleaner: CryptoCurrencyMetadataCleaner,
|
||||
private val expressServiceFetcher: ExpressServiceFetcher,
|
||||
private val parallelUpdatingScope: CoroutineScope,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -306,21 +300,12 @@ class ManageCryptoCurrenciesUseCase(
|
|||
if (currencies.isEmpty()) return
|
||||
|
||||
coroutineScope {
|
||||
listOf(
|
||||
launch { networksCleaner(userWalletId = userWalletId, currencies = currencies) },
|
||||
launch { clearStaking(userWalletId = userWalletId, currencies = currencies) },
|
||||
)
|
||||
launch {
|
||||
cryptoCurrencyMetadataCleaner(userWalletId = userWalletId, currencies = currencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun clearStaking(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
val stakingIds = currencies.mapNotNullTo(hashSetOf()) {
|
||||
stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = it).getOrNull()
|
||||
}
|
||||
|
||||
stakingCleaner(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||
}
|
||||
|
||||
private data class TempID(
|
||||
val networkId: String,
|
||||
val derivationPath: Network.DerivationPath,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
package com.tangem.domain.account.status.utils
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.networks.utils.NetworksCleaner
|
||||
import com.tangem.domain.nft.utils.NFTCleaner
|
||||
import com.tangem.domain.staking.utils.StakingCleaner
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Class to clean up all data related to a specific cryptocurrency in a user's wallet.
|
||||
*
|
||||
* @property networksCleaner Utility to clean network-related data.
|
||||
* @property stakingCleaner Utility to clean staking-related data.
|
||||
* @property nftCleaner Utility to clean NFT-related data.
|
||||
* @property dispatchers Coroutine dispatchers for managing threading.
|
||||
*
|
||||
* @see <a href="https://www.notion.so/tangem/2be5d34eb67880008f95cb779dcafac9">Notion</a>
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CryptoCurrencyMetadataCleaner(
|
||||
private val networksCleaner: NetworksCleaner,
|
||||
private val stakingCleaner: StakingCleaner,
|
||||
private val nftCleaner: NFTCleaner,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Cleans up data for a single cryptocurrency in the specified user wallet.
|
||||
*
|
||||
* @param userWalletId The ID of the user's wallet.
|
||||
* @param currency The cryptocurrency to be cleaned.
|
||||
*/
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, currency: CryptoCurrency) {
|
||||
invoke(userWalletId = userWalletId, currencies = listOf(currency))
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up data for multiple cryptocurrencies in the specified user wallet.
|
||||
*
|
||||
* @param userWalletId The ID of the user's wallet.
|
||||
* @param currencies The list of cryptocurrencies to be cleaned.
|
||||
*/
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
if (currencies.isEmpty()) return
|
||||
|
||||
return withContext(dispatchers.default) {
|
||||
awaitAll(
|
||||
async { networksCleaner(userWalletId = userWalletId, currencies = currencies) },
|
||||
async { clearStaking(userWalletId = userWalletId, currencies = currencies) },
|
||||
async { clearNFTs(userWalletId = userWalletId, currencies = currencies) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun clearStaking(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
stakingCleaner(userWalletId = userWalletId, currencies = currencies)
|
||||
}
|
||||
|
||||
private suspend fun clearNFTs(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
val networks = currencies.mapTo(destination = hashSetOf(), transform = CryptoCurrency::network)
|
||||
|
||||
nftCleaner(userWalletId = userWalletId, networks = networks)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.domain.account.status.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.networks.utils.NetworksCleaner
|
||||
import com.tangem.domain.nft.utils.NFTCleaner
|
||||
import com.tangem.domain.staking.utils.StakingCleaner
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class CryptoCurrencyCleanerTest {
|
||||
|
||||
private val networksCleaner: NetworksCleaner = mockk(relaxUnitFun = true)
|
||||
private val stakingCleaner: StakingCleaner = mockk(relaxUnitFun = true)
|
||||
private val nftCleaner: NFTCleaner = mockk(relaxUnitFun = true)
|
||||
|
||||
private val cleaner = CryptoCurrencyMetadataCleaner(
|
||||
networksCleaner = networksCleaner,
|
||||
stakingCleaner = stakingCleaner,
|
||||
nftCleaner = nftCleaner,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
|
||||
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
private val coin = cryptoCurrencyFactory.ethereum
|
||||
private val token = cryptoCurrencyFactory.createToken(Blockchain.Ethereum)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(networksCleaner, stakingCleaner, nftCleaner)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not call cleaners when currencies list is empty`() = runTest {
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, currencies = emptyList())
|
||||
|
||||
// Assert
|
||||
coVerify(inverse = true) {
|
||||
networksCleaner(userWalletId = any(), currencies = any())
|
||||
stakingCleaner(userWalletId = any(), currencies = any())
|
||||
nftCleaner(userWalletId = any(), networks = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should call both cleaners when currencies list is not empty`() = runTest {
|
||||
// Arrange
|
||||
val currencies = listOf(coin, token)
|
||||
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, currencies = currencies)
|
||||
|
||||
// Assert
|
||||
coVerify {
|
||||
networksCleaner(userWalletId = userWalletId, currencies = currencies)
|
||||
stakingCleaner(userWalletId = userWalletId, currencies = currencies)
|
||||
nftCleaner(userWalletId = userWalletId, networks = setOf(coin.network, token.network))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,16 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
*/
|
||||
interface NetworksCleaner {
|
||||
|
||||
/**
|
||||
* Cleans up network-related data for the given [userWalletId] and [currency].
|
||||
*
|
||||
* @param userWalletId The ID of the user wallet for which to clean up data.
|
||||
* @param currency The cryptocurrency whose associated network data should be cleaned.
|
||||
*/
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, currency: CryptoCurrency) {
|
||||
invoke(userWalletId = userWalletId, currencies = listOf(currency))
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up network-related data for the given [userWalletId] and list of [currencies].
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
package com.tangem.domain.nft
|
||||
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
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.nft.utils.NFTCleaner
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
class ObserveAndClearNFTCacheIfNeedUseCase(
|
||||
private val nftCleaner: NFTCleaner,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
) {
|
||||
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<Set<Network>> = getCryptoCurrencies(userWalletId)
|
||||
.map { it.map(CryptoCurrency::network) }
|
||||
.mapDiff { old, new ->
|
||||
// calculate networks sets difference to determine which networks were removed
|
||||
old.toSet() - new.toSet()
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.onEach { removedNetworks ->
|
||||
if (removedNetworks.isNotEmpty()) {
|
||||
nftCleaner(userWalletId, removedNetworks)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCryptoCurrencies(userWalletId: UserWalletId): Flow<Collection<CryptoCurrency>> {
|
||||
return if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
singleAccountListSupplier(userWalletId).map(AccountList::flattenCurrencies)
|
||||
} else {
|
||||
currenciesRepository.getWalletCurrenciesUpdates(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T, R> Flow<T>.mapDiff(diff: (old: T, new: T) -> R): Flow<R> = flow {
|
||||
var previous: T? = null
|
||||
collect { current ->
|
||||
val prev = previous
|
||||
if (prev != null) {
|
||||
emit(diff(prev, current))
|
||||
}
|
||||
previous = current
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.staking.utils
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
|
|
@ -10,6 +11,34 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
*/
|
||||
interface StakingCleaner {
|
||||
|
||||
/**
|
||||
* Cleans up staking-related data for the given [userWalletId] and single [currency].
|
||||
*
|
||||
* @param userWalletId The ID of the user wallet for which to clean up data.
|
||||
* @param currency The cryptocurrency whose associated staking data should be cleaned.
|
||||
*/
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, currency: CryptoCurrency) {
|
||||
invoke(userWalletId = userWalletId, currencies = listOf(currency))
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up staking-related data for the given [userWalletId] and list of [currencies].
|
||||
*
|
||||
* @param userWalletId The ID of the user wallet for which to clean up data.
|
||||
* @param currencies The list of cryptocurrencies whose associated staking data should be cleaned.
|
||||
*/
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, currencies: List<CryptoCurrency>)
|
||||
|
||||
/**
|
||||
* Cleans up staking-related data for the given [userWalletId] and single [stakingId].
|
||||
*
|
||||
* @param userWalletId The ID of the user wallet for which to clean up data.
|
||||
* @param stakingId The staking ID whose associated data should be cleaned.
|
||||
*/
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, stakingId: StakingID) {
|
||||
invoke(userWalletId = userWalletId, stakingIds = setOf(stakingId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up staking-related data for the given [userWalletId] and set of [stakingIds].
|
||||
*
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import com.tangem.domain.apptheme.model.AppThemeMode
|
|||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.*
|
||||
import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
|
||||
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
|
|
@ -84,7 +83,6 @@ internal class WalletModel @Inject constructor(
|
|||
private val onrampStatusFactory: OnrampStatusFactory,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val walletContentFetcher: WalletContentFetcher,
|
||||
private val observeAndClearNFTCacheIfNeedUseCase: ObserveAndClearNFTCacheIfNeedUseCase,
|
||||
private val walletDeepLinkActionListener: WalletDeepLinkActionListener,
|
||||
private val notificationsRepository: NotificationsRepository,
|
||||
private val getWalletsListForEnablingUseCase: GetWalletsForAutomaticallyPushEnablingUseCase,
|
||||
|
|
@ -114,7 +112,6 @@ internal class WalletModel @Inject constructor(
|
|||
|
||||
private val walletsUpdateJobHolder = JobHolder()
|
||||
private val refreshWalletJobHolder = JobHolder()
|
||||
private val clearNFTCacheJobHolder = JobHolder()
|
||||
private val updateTangemPayJobHolder = JobHolder()
|
||||
|
||||
private var needToRefreshWallet = false
|
||||
|
|
@ -334,7 +331,6 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
|
||||
subscribeOnExpressTransactionsUpdates(selectedWallet)
|
||||
observeAndClearNFTCacheIfNeedUseCase(selectedWallet)
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
|
|
@ -390,13 +386,6 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun observeAndClearNFTCacheIfNeedUseCase(selectedWallet: UserWallet) {
|
||||
observeAndClearNFTCacheIfNeedUseCase
|
||||
.invoke(selectedWallet.walletId)
|
||||
.launchIn(modelScope)
|
||||
.saveIn(clearNFTCacheJobHolder)
|
||||
}
|
||||
|
||||
private fun subscribeTangemPayOnWalletState() {
|
||||
/**
|
||||
* Update state each time a user opens/returns to wallet screen
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue