Updated on 2026-08-14
This commit is contained in:
parent
c870f8386d
commit
93dd76610b
3 changed files with 148 additions and 60 deletions
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.data.managetokens
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.compatibility.l2BlockchainsCoinIds
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.isSupportedInApp
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.data.common.currency.getBlockchain
|
||||
|
|
@ -15,7 +15,9 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
|||
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.testnet.TestnetTokensStorage
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.extensions.canHandleBlockchain
|
||||
import com.tangem.domain.common.extensions.canHandleToken
|
||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
|
|
@ -31,6 +33,7 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import com.tangem.pagination.BatchFetchResult
|
||||
import com.tangem.pagination.BatchListSource
|
||||
import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher
|
||||
import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher.Request
|
||||
import com.tangem.pagination.toBatchFlow
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
||||
|
|
@ -40,6 +43,7 @@ internal class DefaultManageTokensRepository(
|
|||
private val userWalletsStore: UserWalletsStore,
|
||||
private val manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val testnetTokensStorage: TestnetTokensStorage,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : ManageTokensRepository {
|
||||
|
||||
|
|
@ -59,7 +63,6 @@ internal class DefaultManageTokensRepository(
|
|||
).toBatchFlow()
|
||||
}
|
||||
|
||||
@Suppress("ComplexCondition")
|
||||
private fun createFetcher(
|
||||
batchSize: Int,
|
||||
): LimitOffsetBatchFetcher<ManageTokensListConfig, List<ManagedCryptoCurrency>> = LimitOffsetBatchFetcher(
|
||||
|
|
@ -67,59 +70,101 @@ internal class DefaultManageTokensRepository(
|
|||
batchSize = batchSize,
|
||||
subFetcher = { request, _, isFirstBatchFetching ->
|
||||
val userWallet = request.params.userWalletId?.let { getUserWallet(it) }
|
||||
val supportedBlockchains = getSupportedBlockchains(userWallet)
|
||||
val searchText = request.params.searchText?.takeIf { it.isNotBlank() }
|
||||
|
||||
val call = suspend {
|
||||
tangemTechApi.getCoins(
|
||||
networkIds = supportedBlockchains.joinToString(
|
||||
separator = ",",
|
||||
transform = Blockchain::toNetworkId,
|
||||
),
|
||||
active = true,
|
||||
searchText = searchText,
|
||||
offset = request.offset * request.limit,
|
||||
limit = request.limit,
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
val coinsResponse = if (isFirstBatchFetching) {
|
||||
call()
|
||||
if (userWallet?.scanResponse?.card?.isTestCard == true) {
|
||||
fetchTestnetCurrencies(userWallet, request)
|
||||
} else {
|
||||
retryOnError(call = call)
|
||||
fetchCurrencies(userWallet, request, isFirstBatchFetching)
|
||||
}
|
||||
// filter l2 coins
|
||||
val updatedCoinsResponse = coinsResponse.copy(
|
||||
coins = coinsResponse.coins.filterNot { l2BlockchainsCoinIds.contains(it.id) },
|
||||
)
|
||||
|
||||
val tokensResponse = request.params.userWalletId?.let { getSavedUserTokensResponseSync(it) }
|
||||
val items = if (isFirstBatchFetching &&
|
||||
tokensResponse != null &&
|
||||
userWallet != null &&
|
||||
request.params.searchText.isNullOrBlank()
|
||||
) {
|
||||
managedCryptoCurrencyFactory.createWithCustomTokens(
|
||||
coinsResponse = updatedCoinsResponse,
|
||||
tokensResponse = tokensResponse,
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
)
|
||||
} else {
|
||||
managedCryptoCurrencyFactory.create(
|
||||
coinsResponse = updatedCoinsResponse,
|
||||
tokensResponse = tokensResponse,
|
||||
derivationStyleProvider = userWallet?.scanResponse?.derivationStyleProvider,
|
||||
)
|
||||
}
|
||||
|
||||
BatchFetchResult.Success(
|
||||
data = items,
|
||||
empty = items.isEmpty(),
|
||||
last = items.size < request.limit,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@Suppress("ComplexCondition")
|
||||
private suspend fun fetchCurrencies(
|
||||
userWallet: UserWallet?,
|
||||
request: Request<ManageTokensListConfig>,
|
||||
isFirstBatchFetching: Boolean,
|
||||
): BatchFetchResult.Success<List<ManagedCryptoCurrency>> {
|
||||
val supportedBlockchains = getSupportedBlockchains(userWallet)
|
||||
|
||||
val call = suspend {
|
||||
tangemTechApi.getCoins(
|
||||
networkIds = supportedBlockchains.joinToString(
|
||||
separator = ",",
|
||||
transform = Blockchain::toNetworkId,
|
||||
),
|
||||
active = true,
|
||||
searchText = request.params.searchText,
|
||||
offset = request.offset * request.limit,
|
||||
limit = request.limit,
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
val coinsResponse = if (isFirstBatchFetching) {
|
||||
call()
|
||||
} else {
|
||||
retryOnError(call = call)
|
||||
}
|
||||
// filter l2 coins
|
||||
val updatedCoinsResponse = coinsResponse.copy(
|
||||
coins = coinsResponse.coins.filterNot { l2BlockchainsCoinIds.contains(it.id) },
|
||||
)
|
||||
|
||||
val tokensResponse = request.params.userWalletId?.let { getSavedUserTokensResponseSync(it) }
|
||||
val items = if (isFirstBatchFetching &&
|
||||
tokensResponse != null &&
|
||||
userWallet != null &&
|
||||
request.params.searchText.isNullOrBlank()
|
||||
) {
|
||||
managedCryptoCurrencyFactory.createWithCustomTokens(
|
||||
coinsResponse = updatedCoinsResponse,
|
||||
tokensResponse = tokensResponse,
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
)
|
||||
} else {
|
||||
managedCryptoCurrencyFactory.create(
|
||||
coinsResponse = updatedCoinsResponse,
|
||||
tokensResponse = tokensResponse,
|
||||
derivationStyleProvider = userWallet?.scanResponse?.derivationStyleProvider,
|
||||
)
|
||||
}
|
||||
|
||||
return BatchFetchResult.Success(
|
||||
data = items,
|
||||
empty = items.isEmpty(),
|
||||
last = items.size < request.limit,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun fetchTestnetCurrencies(
|
||||
userWallet: UserWallet,
|
||||
request: Request<ManageTokensListConfig>,
|
||||
): BatchFetchResult.Success<List<ManagedCryptoCurrency>> {
|
||||
val searchText = request.params.searchText
|
||||
val testnetTokensConfig = testnetTokensStorage.getConfig()
|
||||
|
||||
val items = managedCryptoCurrencyFactory.createTestnetWithCustomTokens(
|
||||
testnetTokensConfig = if (!searchText.isNullOrBlank()) {
|
||||
testnetTokensConfig.copy(
|
||||
tokens = testnetTokensConfig.tokens.filter { token ->
|
||||
token.symbol.contains(other = searchText, ignoreCase = true) ||
|
||||
token.name.contains(other = searchText, ignoreCase = true)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
testnetTokensConfig
|
||||
},
|
||||
tokensResponse = getSavedUserTokensResponseSync(userWallet.walletId),
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
)
|
||||
|
||||
return BatchFetchResult.Success(
|
||||
data = items,
|
||||
empty = items.isEmpty(),
|
||||
last = true,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
|
||||
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find a user wallet with provided ID: $userWalletId"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.data.managetokens.DefaultManageTokensRepository
|
|||
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.testnet.TestnetTokensStorage
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||
|
|
@ -27,6 +28,7 @@ internal object ManageTokensDataModule {
|
|||
userWalletsStore: UserWalletsStore,
|
||||
manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
testnetTokensStorage: TestnetTokensStorage,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ManageTokensRepository {
|
||||
return DefaultManageTokensRepository(
|
||||
|
|
@ -34,6 +36,7 @@ internal object ManageTokensDataModule {
|
|||
userWalletsStore,
|
||||
manageTokensUpdateFetcher,
|
||||
appPreferencesStore,
|
||||
testnetTokensStorage,
|
||||
dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.data.common.currency.getNetwork
|
|||
import com.tangem.data.common.currency.getTokenId
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.testnet.models.TestnetTokensConfig
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
||||
|
|
@ -34,16 +35,49 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
tokensResponse: UserTokensResponse,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
): List<ManagedCryptoCurrency> {
|
||||
val customTokens = tokensResponse.tokens
|
||||
.mapNotNull { token ->
|
||||
maybeCreateCustomToken(token, coinsResponse.imageHost, derivationStyleProvider)
|
||||
}
|
||||
|
||||
val customTokens = createCustomTokens(tokensResponse, derivationStyleProvider)
|
||||
val tokens = create(coinsResponse, tokensResponse, derivationStyleProvider)
|
||||
|
||||
return customTokens + tokens
|
||||
}
|
||||
|
||||
fun createTestnetWithCustomTokens(
|
||||
testnetTokensConfig: TestnetTokensConfig,
|
||||
tokensResponse: UserTokensResponse?,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
): List<ManagedCryptoCurrency> {
|
||||
val customTokens = tokensResponse
|
||||
?.let { createCustomTokens(it, derivationStyleProvider) }
|
||||
?: emptyList()
|
||||
val testnetTokens = testnetTokensConfig.tokens.map { testnetToken ->
|
||||
ManagedCryptoCurrency.Token(
|
||||
id = ManagedCryptoCurrency.ID(testnetToken.id),
|
||||
name = testnetToken.name,
|
||||
symbol = testnetToken.symbol,
|
||||
iconUrl = getIconUrl(testnetToken.id),
|
||||
availableNetworks = testnetToken.networks?.mapNotNull { network ->
|
||||
createSource(
|
||||
networkId = network.id,
|
||||
contractAddress = network.address,
|
||||
decimals = network.decimalCount ?: return@mapNotNull null,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
)
|
||||
} ?: emptyList(),
|
||||
addedIn = findAddedInNetworks(testnetToken.id, tokensResponse, derivationStyleProvider),
|
||||
)
|
||||
}
|
||||
|
||||
return customTokens + testnetTokens
|
||||
}
|
||||
|
||||
private fun createCustomTokens(
|
||||
tokensResponse: UserTokensResponse,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
): List<ManagedCryptoCurrency> = tokensResponse.tokens
|
||||
.mapNotNull { token ->
|
||||
maybeCreateCustomToken(token, null, derivationStyleProvider)
|
||||
}
|
||||
|
||||
private fun maybeCreateCustomToken(
|
||||
token: UserTokensResponse.Token,
|
||||
imageHost: String?,
|
||||
|
|
@ -100,23 +134,29 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
symbol = coinResponse.symbol,
|
||||
iconUrl = getIconUrl(coinResponse.id, imageHost),
|
||||
availableNetworks = updatedNetworks.mapNotNull { network ->
|
||||
createSource(network, derivationStyleProvider)
|
||||
createSource(
|
||||
networkId = network.networkId,
|
||||
contractAddress = network.contractAddress,
|
||||
decimals = network.decimalCount?.toInt() ?: return@mapNotNull null,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
)
|
||||
},
|
||||
addedIn = findAddedInNetworks(coinResponse.id, tokensResponse, derivationStyleProvider),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSource(
|
||||
networkResponse: CoinsResponse.Coin.Network,
|
||||
networkId: String,
|
||||
contractAddress: String?,
|
||||
decimals: Int,
|
||||
derivationStyleProvider: DerivationStyleProvider?,
|
||||
extraDerivationPath: String? = null,
|
||||
): SourceNetwork? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkResponse.networkId)
|
||||
val blockchain = Blockchain.fromNetworkId(networkId)
|
||||
?.takeIf { it.isSupportedInApp() }
|
||||
?: return null
|
||||
|
||||
val network = getNetwork(blockchain, extraDerivationPath, derivationStyleProvider) ?: return null
|
||||
val contractAddress = networkResponse.contractAddress
|
||||
|
||||
return if (contractAddress.isNullOrBlank()) {
|
||||
SourceNetwork.Main(
|
||||
|
|
@ -127,7 +167,7 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
} else {
|
||||
SourceNetwork.Default(
|
||||
network = network,
|
||||
decimals = requireNotNull(networkResponse.decimalCount?.toInt()),
|
||||
decimals = decimals,
|
||||
contractAddress = contractAddress,
|
||||
)
|
||||
}
|
||||
|
|
@ -157,7 +197,7 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getIconUrl(id: String, imageHost: String?): String {
|
||||
private fun getIconUrl(id: String, imageHost: String? = DEFAULT_IMAGE_HOST): String {
|
||||
return "${imageHost ?: DEFAULT_IMAGE_HOST}large/$id.png"
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue