Updated on 2026-08-14
This commit is contained in:
parent
dd186d6fbc
commit
3f5f3ef68b
23 changed files with 351 additions and 128 deletions
|
|
@ -1,15 +1,15 @@
|
|||
package com.tangem.data.tokens.di
|
||||
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.tokens.repository.DefaultCurrenciesRepository
|
||||
import com.tangem.data.tokens.repository.DefaultNetworksRepository
|
||||
import com.tangem.data.tokens.repository.DefaultTokensRepository
|
||||
import com.tangem.data.tokens.repository.MockQuotesRepository
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.tokens.repository.TokensRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -24,14 +24,14 @@ internal object TokensDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTokensRepository(
|
||||
fun provideCurrenciesRepository(
|
||||
tangemTechApi: TangemTechApi,
|
||||
userTokensStore: UserTokensStore,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
cacheRegistry: CacheRegistry,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): TokensRepository {
|
||||
return DefaultTokensRepository(tangemTechApi, userTokensStore, userWalletsStore, cacheRegistry, dispatchers)
|
||||
): CurrenciesRepository {
|
||||
return DefaultCurrenciesRepository(tangemTechApi, userTokensStore, userWalletsStore, cacheRegistry, dispatchers)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -8,23 +8,27 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
|
|||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.core.error.DataError
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.repository.TokensRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultTokensRepository(
|
||||
internal class DefaultCurrenciesRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val userTokensStore: UserTokensStore,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : TokensRepository {
|
||||
) : CurrenciesRepository {
|
||||
|
||||
private val demoConfig = DemoConfig()
|
||||
private val responseCurrenciesFactory = ResponseCurrenciesFactory(demoConfig)
|
||||
|
|
@ -37,6 +41,8 @@ internal class DefaultTokensRepository(
|
|||
isGroupedByNetwork: Boolean,
|
||||
isSortedByBalance: Boolean,
|
||||
) = withContext(dispatchers.io) {
|
||||
ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true)
|
||||
|
||||
val response = userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = currencies,
|
||||
isGroupedByNetwork = isGroupedByNetwork,
|
||||
|
|
@ -46,58 +52,72 @@ internal class DefaultTokensRepository(
|
|||
storeAndPushTokens(userWalletId, response)
|
||||
}
|
||||
|
||||
override suspend fun getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency {
|
||||
val userWallet = withContext(dispatchers.io) {
|
||||
requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find a user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
}
|
||||
require(!userWallet.isMultiCurrency) {
|
||||
"Single currency wallet excepted, but multi currency wallet was found: $userWalletId"
|
||||
}
|
||||
override suspend fun getSingleCurrencyWalletPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency {
|
||||
return withContext(dispatchers.io) {
|
||||
val userWallet = getUserWallet(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
|
||||
|
||||
return cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
|
||||
cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMultiCurrencyWalletCurrencies(
|
||||
userWalletId: UserWalletId,
|
||||
refresh: Boolean,
|
||||
): Flow<Set<CryptoCurrency>> {
|
||||
): Flow<Set<CryptoCurrency>> = channelFlow {
|
||||
val userWallet = getUserWallet(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true)
|
||||
|
||||
launch(dispatchers.io) {
|
||||
getMultiCurrencyWalletCurrencies(userWallet).collect(::send)
|
||||
}
|
||||
|
||||
launch(dispatchers.io) {
|
||||
fetchTokensIfCacheExpired(userWallet, refresh)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMultiCurrencyWalletCurrency(
|
||||
userWalletId: UserWalletId,
|
||||
id: CryptoCurrency.ID,
|
||||
): CryptoCurrency = withContext(dispatchers.io) {
|
||||
val userWallet = getUserWallet(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true)
|
||||
|
||||
val response = requireNotNull(userTokensStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
|
||||
responseCurrenciesFactory.createCurrency(id, response, userWallet.scanResponse.card)
|
||||
}
|
||||
|
||||
override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean> {
|
||||
return channelFlow {
|
||||
val userWallet = withContext(dispatchers.io) {
|
||||
requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find a user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
}
|
||||
require(userWallet.isMultiCurrency) {
|
||||
"Multi currency wallet excepted, but single currency wallet was found: $userWalletId"
|
||||
}
|
||||
ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true)
|
||||
|
||||
launch(dispatchers.io) {
|
||||
getMultiCurrencyWalletCurrencies(userWallet).collectLatest(::send)
|
||||
}
|
||||
|
||||
launch(dispatchers.io) {
|
||||
fetchTokensIfCacheExpired(userWallet, refresh)
|
||||
userTokensStore.get(userWalletId)
|
||||
.map { it.group == UserTokensResponse.GroupType.NETWORK }
|
||||
.collect(::send)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean> {
|
||||
return userTokensStore.get(userWalletId)
|
||||
.map { it.group == UserTokensResponse.GroupType.NETWORK }
|
||||
.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Boolean> {
|
||||
return userTokensStore.get(userWalletId)
|
||||
.map { it.sort == UserTokensResponse.SortType.BALANCE }
|
||||
.flowOn(dispatchers.io)
|
||||
return channelFlow {
|
||||
ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true)
|
||||
|
||||
launch(dispatchers.io) {
|
||||
userTokensStore.get(userWalletId)
|
||||
.map { it.sort == UserTokensResponse.SortType.BALANCE }
|
||||
.collect(::send)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow<Set<CryptoCurrency>> {
|
||||
return userTokensStore.get(userWallet.walletId).map { storedTokens ->
|
||||
responseCurrenciesFactory.createTokens(
|
||||
responseCurrenciesFactory.createCurrencies(
|
||||
response = storedTokens,
|
||||
card = userWallet.scanResponse.card,
|
||||
)
|
||||
|
|
@ -146,6 +166,39 @@ internal class DefaultTokensRepository(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
|
||||
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find a user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ensureIsCorrectUserWallet(userWalletId: UserWalletId, isMultiCurrencyWalletExpected: Boolean) {
|
||||
val userWallet = getUserWallet(userWalletId)
|
||||
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected)
|
||||
}
|
||||
|
||||
private fun ensureIsCorrectUserWallet(userWallet: UserWallet, isMultiCurrencyWalletExpected: Boolean) {
|
||||
val userWalletId = userWallet.walletId
|
||||
|
||||
val message = when {
|
||||
!userWallet.isMultiCurrency && isMultiCurrencyWalletExpected -> {
|
||||
"Multi currency wallet expected, but single currency wallet was found: $userWalletId"
|
||||
}
|
||||
userWallet.isMultiCurrency && !isMultiCurrencyWalletExpected -> {
|
||||
"Single currency wallet expected, but multi currency wallet was found: $userWalletId"
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (message != null) {
|
||||
val error = DataError.UserWalletError.WrongUserWallet(message)
|
||||
|
||||
Timber.e(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTokensCacheKey(userWalletId: UserWalletId): String = "tokens_cache_key_${userWalletId.stringValue}"
|
||||
|
||||
private companion object {
|
||||
|
|
@ -18,7 +18,10 @@ import com.tangem.utils.extensions.addOrReplace
|
|||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class DefaultNetworksRepository(
|
||||
|
|
@ -44,7 +47,9 @@ internal class DefaultNetworksRepository(
|
|||
networks: Set<Network.ID>,
|
||||
refresh: Boolean,
|
||||
): Flow<Set<NetworkStatus>> = channelFlow {
|
||||
networksStatuses.collectLatest(::send)
|
||||
launch(dispatchers.io) {
|
||||
networksStatuses.collect(::send)
|
||||
}
|
||||
|
||||
launch(dispatchers.io) {
|
||||
fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh)
|
||||
|
|
@ -99,7 +104,7 @@ internal class DefaultNetworksRepository(
|
|||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
|
||||
return responseCurrenciesFactory.createTokens(response, userWallet.scanResponse.card)
|
||||
return responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse.card)
|
||||
}
|
||||
|
||||
private fun getNetworksStatusesCacheKey(userWalletId: UserWalletId): String = "network_status_$userWalletId"
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ internal class NetworkStatusFactory {
|
|||
is CryptoCurrency.Coin -> amounts.singleOrNull { it is CryptoCurrencyAmount.Coin }
|
||||
is CryptoCurrency.Token -> amounts.singleOrNull {
|
||||
it is CryptoCurrencyAmount.Token &&
|
||||
it.id == getTokenIdString(currency) &&
|
||||
it.id == getTokenIdString(currency.id) &&
|
||||
it.tokenContractAddress == currency.contractAddress
|
||||
}
|
||||
}?.value
|
||||
|
|
|
|||
|
|
@ -12,11 +12,23 @@ import com.tangem.blockchain.common.Token as SdkToken
|
|||
|
||||
internal class ResponseCurrenciesFactory(private val demoConfig: DemoConfig) {
|
||||
|
||||
fun createTokens(response: UserTokensResponse, card: CardDTO): Set<CryptoCurrency> {
|
||||
return response.tokens.mapNotNull { createToken(it, card) }.toSet()
|
||||
fun createCurrency(currencyId: CryptoCurrency.ID, response: UserTokensResponse, card: CardDTO): CryptoCurrency {
|
||||
val responseTokenId = getTokenIdString(currencyId)
|
||||
|
||||
val token = requireNotNull(response.tokens.firstOrNull { it.id == responseTokenId }) {
|
||||
"Unable find a token with provided ID: $responseTokenId"
|
||||
}
|
||||
|
||||
return requireNotNull(createCurrency(token, card)) {
|
||||
"Unable to create a currency with provided ID: $currencyId"
|
||||
}
|
||||
}
|
||||
|
||||
private fun createToken(responseToken: UserTokensResponse.Token, card: CardDTO): CryptoCurrency? {
|
||||
fun createCurrencies(response: UserTokensResponse, card: CardDTO): Set<CryptoCurrency> {
|
||||
return response.tokens.mapNotNull { createCurrency(it, card) }.toSet()
|
||||
}
|
||||
|
||||
private fun createCurrency(responseToken: UserTokensResponse.Token, card: CardDTO): CryptoCurrency? {
|
||||
var blockchain = Blockchain.fromNetworkId(responseToken.networkId)
|
||||
if (blockchain == null || blockchain == Blockchain.Unknown) {
|
||||
Timber.e("Unable to find a blockchain with the network ID: ${responseToken.networkId}")
|
||||
|
|
|
|||
|
|
@ -49,9 +49,14 @@ internal fun getTokenId(blockchain: Blockchain, token: SdkToken): CryptoCurrency
|
|||
return getTokenOrCoinId(blockchain, token)
|
||||
}
|
||||
|
||||
internal fun getTokenIdString(currency: CryptoCurrency): String? {
|
||||
return currency.id.value.substringAfter(TOKEN_ID_DELIMITER)
|
||||
.takeUnless { currency is CryptoCurrency.Token && currency.isCustom }
|
||||
internal fun getTokenIdString(currencyId: CryptoCurrency.ID): String? {
|
||||
val idValue = currencyId.value
|
||||
|
||||
return if (idValue.startsWith(CUSTOM_TOKEN_ID_PREFIX)) {
|
||||
null
|
||||
} else {
|
||||
idValue.substringAfter(TOKEN_ID_DELIMITER)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getTokenIconUrl(blockchain: Blockchain, token: SdkToken): String? {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ internal class UserTokensResponseFactory {
|
|||
val blockchain = getBlockchain(currency.networkId)
|
||||
|
||||
return UserTokensResponse.Token(
|
||||
id = getTokenIdString(currency),
|
||||
id = getTokenIdString(currency.id),
|
||||
networkId = blockchain.toNetworkId(),
|
||||
derivationPath = currency.derivationPath,
|
||||
name = currency.name,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue