Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-14 19:54:34 +04:00
parent 66efd406cc
commit 90fea21f17
14 changed files with 103 additions and 398 deletions

View file

@ -0,0 +1,51 @@
package com.tangem.data.networks.di
import androidx.datastore.core.DataStore
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.networks.repository.DefaultNetworksRepository
import com.tangem.data.networks.store.DefaultNetworksStatusesStoreV2
import com.tangem.data.networks.store.NetworksStatusesStoreV2
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.datasource.local.network.entity.NetworkStatusDM
import com.tangem.domain.networks.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object NetworkDataModule {
@Provides
@Singleton
fun provideNetworksStatusesStoreV2(
persistenceNetworksStatusesStore: DataStore<Map<String, Set<NetworkStatusDM>>>,
dispatchers: CoroutineDispatcherProvider,
): NetworksStatusesStoreV2 {
return DefaultNetworksStatusesStoreV2(
runtimeStore = RuntimeSharedStore(),
persistenceDataStore = persistenceNetworksStatusesStore,
dispatchers = dispatchers,
)
}
@Provides
@Singleton
fun provideNetworkRepository(
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
walletManagersFacade: WalletManagersFacade,
networksStatusesStoreV2: NetworksStatusesStoreV2,
dispatchers: CoroutineDispatcherProvider,
): NetworksRepository {
return DefaultNetworksRepository(
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
walletManagersFacade = walletManagersFacade,
networksStatusesStore = networksStatusesStoreV2,
dispatchers = dispatchers,
)
}
}

View file

@ -1,15 +1,9 @@
package com.tangem.data.networks.di
import androidx.datastore.core.DataStore
import com.tangem.data.networks.store.DefaultNetworksStatusesStoreV2
import com.tangem.data.networks.store.NetworksStatusesStoreV2
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.datasource.local.network.entity.NetworkStatusDM
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.networks.single.SingleNetworkStatusSupplier
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -20,19 +14,6 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
internal object NetworkStatusSupplierModule {
@Provides
@Singleton
fun provideNetworksStatusesStoreV2(
persistenceNetworksStatusesStore: DataStore<Map<String, Set<NetworkStatusDM>>>,
dispatchers: CoroutineDispatcherProvider,
): NetworksStatusesStoreV2 {
return DefaultNetworksStatusesStoreV2(
runtimeStore = RuntimeSharedStore(),
persistenceDataStore = persistenceNetworksStatusesStore,
dispatchers = dispatchers,
)
}
@Provides
@Singleton
fun provideSingleNetworkStatusSupplier(factory: SingleNetworkStatusProducer.Factory): SingleNetworkStatusSupplier {

View file

@ -3,14 +3,19 @@ package com.tangem.data.tokens.di
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.tokens.repository.*
import com.tangem.data.tokens.repository.DefaultCurrenciesRepository
import com.tangem.data.tokens.repository.DefaultCurrencyChecksRepository
import com.tangem.data.tokens.repository.DefaultPolkadotAccountHealthCheckRepository
import com.tangem.data.tokens.repository.DefaultQuotesRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
import com.tangem.datasource.local.network.NetworksStatusesStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.quote.QuotesStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.tokens.repository.*
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -67,30 +72,6 @@ internal object TokensDataModule {
)
}
@Provides
@Singleton
fun provideNetworksRepository(
networksStatusesStore: NetworksStatusesStore,
walletManagersFacade: WalletManagersFacade,
userWalletsStore: UserWalletsStore,
appPreferencesStore: AppPreferencesStore,
cacheRegistry: CacheRegistry,
dispatchers: CoroutineDispatcherProvider,
excludedBlockchains: ExcludedBlockchains,
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
): NetworksRepository {
return DefaultNetworksRepository(
networksStatusesStore = networksStatusesStore,
walletManagersFacade = walletManagersFacade,
userWalletsStore = userWalletsStore,
appPreferencesStore = appPreferencesStore,
cacheRegistry = cacheRegistry,
dispatchers = dispatchers,
excludedBlockchains = excludedBlockchains,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
)
}
@Provides
@Singleton
fun provideCurrencyChecksRepository(

View file

@ -1,260 +0,0 @@
package com.tangem.data.tokens.repository
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.data.networks.utils.NetworkStatusFactory
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.network.NetworksStatusesStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
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.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.repository.NetworksRepository
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.CoroutineDispatcherProvider
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import timber.log.Timber
@Suppress("LongParameterList")
internal class DefaultNetworksRepository(
private val networksStatusesStore: NetworksStatusesStore,
private val walletManagersFacade: WalletManagersFacade,
private val userWalletsStore: UserWalletsStore,
private val appPreferencesStore: AppPreferencesStore,
private val cacheRegistry: CacheRegistry,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val dispatchers: CoroutineDispatcherProvider,
excludedBlockchains: ExcludedBlockchains,
) : NetworksRepository {
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
override fun getNetworkStatusesUpdates(
userWalletId: UserWalletId,
networks: Set<Network>,
): Flow<Set<NetworkStatus>> {
return networksStatusesStore.get(userWalletId, networks)
.distinctUntilChanged()
.flowOn(dispatchers.io)
}
override suspend fun fetchNetworkStatuses(userWalletId: UserWalletId, networks: Set<Network>, refresh: Boolean) {
withContext(dispatchers.io) {
fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh)
}
}
override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set<Network>) {
val currencies = getCurrencies(userWalletId, networks)
withContext(dispatchers.io) {
fetchNetworksPendingTransactions(userWalletId, networks, currencies)
}
}
override suspend fun getNetworkStatusesSync(
userWalletId: UserWalletId,
networks: Set<Network>,
refresh: Boolean,
): Set<NetworkStatus> = withContext(dispatchers.io) {
fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh)
networksStatusesStore.getSyncOrNull(userWalletId).orEmpty()
}
override suspend fun getNetworkAddresses(
userWalletId: UserWalletId,
network: Network,
): List<CryptoCurrencyAddress> = withContext(dispatchers.io) {
// Get list of currencies matching [network]
val currencies = getCurrencies(userWalletId)
.filter { currency -> network.id == currency.network.id }
// There is no currencies matching given [networks] in [userWalletId]
if (currencies.toList().isEmpty()) return@withContext emptyList()
currencies.toList().map { currency ->
CryptoCurrencyAddress(
cryptoCurrency = currency,
address = walletManagersFacade.getAddresses(userWalletId, currency.network)
.firstOrNull { it.type == AddressType.Default }
?.value.orEmpty(),
)
}
}
private suspend fun fetchNetworksStatusesIfCacheExpired(
userWalletId: UserWalletId,
networks: Set<Network>,
refresh: Boolean,
) = coroutineScope {
if (refresh) {
networksStatusesStore.refresh(key = userWalletId, networks = networks)
}
val currencies = getCurrencies(userWalletId, networks)
val networksDeferred = networks.mapNotNull { network ->
coroutineScope {
val key = getNetworksStatusesCacheKey(userWalletId, network)
if (refresh || cacheRegistry.isExpired(key)) {
async {
cacheRegistry.invokeOnExpire(
key = key,
skipCache = refresh,
block = { fetchNetworkStatus(userWalletId, network, currencies) },
)
}
} else {
null
}
}
}
networksDeferred.awaitAll()
}
private suspend fun fetchNetworksPendingTransactions(
userWalletId: UserWalletId,
networks: Set<Network>,
currencies: Sequence<CryptoCurrency>,
) {
coroutineScope {
networks
.map { network ->
async {
fetchNetworkPendingTransactions(userWalletId, network, currencies)
}
}
.awaitAll()
}
}
private suspend fun fetchNetworkStatus(
userWalletId: UserWalletId,
network: Network,
currencies: Sequence<CryptoCurrency>,
) {
val networkCurrencies = currencies.filter { it.network == network }
val result = walletManagersFacade.update(
userWalletId = userWalletId,
network = network,
extraTokens = networkCurrencies
.filterIsInstance<CryptoCurrency.Token>()
.toSet(),
)
withContext(NonCancellable) {
invalidateCacheKeyIfNeeded(userWalletId, network, result)
}
val networkStatus = NetworkStatusFactory.create(
network = network,
updatingResult = result,
addedCurrencies = networkCurrencies.toSet(),
)
networksStatusesStore.store(userWalletId, networkStatus)
}
private suspend fun fetchNetworkPendingTransactions(
userWalletId: UserWalletId,
network: Network,
currencies: Sequence<CryptoCurrency>,
) {
val result = walletManagersFacade.updatePendingTransactions(
userWalletId = userWalletId,
network = network,
)
withContext(NonCancellable) {
invalidateCacheKeyIfNeeded(userWalletId, network, result)
}
val networkStatus = NetworkStatusFactory.create(
network = network,
updatingResult = result,
addedCurrencies = currencies.toSet(),
)
networksStatusesStore.store(userWalletId, networkStatus)
}
private suspend fun getCurrencies(userWalletId: UserWalletId, networks: Set<Network>): Sequence<CryptoCurrency> {
val currencies = getCurrencies(userWalletId)
return currencies.filter { it.network in networks }
}
private suspend fun getCurrencies(userWalletId: UserWalletId): Sequence<CryptoCurrency> {
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find user wallet with provided ID: $userWalletId"
}
return if (userWallet.isMultiCurrency) {
val response = requireNotNull(
value = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue),
),
lazyMessage = {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
},
)
responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse).asSequence()
} else {
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
scanResponse = userWallet.scanResponse,
)
.asSequence()
} else {
val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(
scanResponse = userWallet.scanResponse,
)
sequenceOf(currency)
}
}
}
private suspend fun invalidateCacheKeyIfNeeded(
userWalletId: UserWalletId,
network: Network,
result: UpdateWalletManagerResult,
) {
when (result) {
is UpdateWalletManagerResult.Verified,
is UpdateWalletManagerResult.NoAccount,
-> Unit
is UpdateWalletManagerResult.Unreachable,
is UpdateWalletManagerResult.MissedDerivation,
-> {
Timber.w(
"""
Invalidate network cache key
|- User wallet ID: $userWalletId
|- Network: ${network.id}
""".trimIndent(),
)
cacheRegistry.invalidate(getNetworksStatusesCacheKey(userWalletId, network))
}
}
}
private fun getNetworksStatusesCacheKey(userWalletId: UserWalletId, network: Network): String {
return "network_status_${userWalletId}_${network.id.value}_${network.derivationPath.value}"
}
}