Updated on 2026-08-14
This commit is contained in:
parent
fccb0e8347
commit
5af84ab1e1
3 changed files with 49 additions and 220 deletions
|
|
@ -1,138 +0,0 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.tap.common.FileReader
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.TokenDao
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Deprecated("Use this only for migration")
|
||||
class OldUserTokensRepository(
|
||||
private val fileReader: FileReader,
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
private val moshi = MoshiConverter.networkMoshi
|
||||
private val blockchainsAdapter: JsonAdapter<List<Blockchain>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, Blockchain::class.java),
|
||||
)
|
||||
private val tokensAdapter: JsonAdapter<List<TokenDao>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, TokenDao::class.java),
|
||||
)
|
||||
private val blockchainNetworkAdapter: JsonAdapter<List<BlockchainNetwork>> =
|
||||
moshi.adapter(Types.newParameterizedType(List::class.java, BlockchainNetwork::class.java))
|
||||
|
||||
@Deprecated("Use BlockchainNetwork instead")
|
||||
private fun loadSavedTokens(cardId: String): List<TokenDao> {
|
||||
val json = try {
|
||||
fileReader.readFile(getFileNameForTokens(cardId))
|
||||
} catch (exception: Exception) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return try {
|
||||
tokensAdapter.fromJson(json) ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use BlockchainNetwork instead")
|
||||
private fun loadSavedBlockchains(cardId: String): List<Blockchain> {
|
||||
return try {
|
||||
val json = fileReader.readFile(getFileNameForBlockchains(cardId))
|
||||
blockchainsAdapter.fromJson(json)?.distinct() ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use TokensRepository instead")
|
||||
suspend fun loadSavedCurrencies(
|
||||
cardId: String,
|
||||
isHdWalletSupported: Boolean = false,
|
||||
): List<BlockchainNetwork> {
|
||||
return try {
|
||||
val json = fileReader.readFile(getFileNameForBlockchains(cardId))
|
||||
blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
tryToLoadPreviousFormatAndMigrate(cardId, isHdWalletSupported)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun tryToLoadPreviousFormatAndMigrate(
|
||||
cardId: String,
|
||||
isHdWalletSupported: Boolean = false,
|
||||
): List<BlockchainNetwork> {
|
||||
return try {
|
||||
loadSavedCurrenciesOldWay(
|
||||
cardId,
|
||||
isHdWalletSupported,
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadSavedCurrenciesOldWay(
|
||||
cardId: String,
|
||||
isHdWalletSupported: Boolean = false,
|
||||
): List<BlockchainNetwork> {
|
||||
val blockchains = loadSavedBlockchains(cardId)
|
||||
val tokens = loadSavedTokens(cardId)
|
||||
val ids = getTokensIds(tokens)
|
||||
val derivationStyle = if (isHdWalletSupported) DerivationStyle.LEGACY else null
|
||||
val blockchainNetworks = blockchains.map { blockchain ->
|
||||
BlockchainNetwork(
|
||||
blockchain = blockchain,
|
||||
derivationPath = blockchain.derivationPath(derivationStyle)?.rawPath,
|
||||
tokens = tokens
|
||||
.filter { it.blockchainDao.toBlockchain() == blockchain }
|
||||
.map {
|
||||
val token = it.toToken()
|
||||
token.copy(id = ids[token.contractAddress])
|
||||
},
|
||||
)
|
||||
}
|
||||
return blockchainNetworks
|
||||
}
|
||||
|
||||
private suspend fun getTokensIds(tokens: List<TokenDao>): Map<String, String> = withContext(dispatchers.io) {
|
||||
tokens.map {
|
||||
async {
|
||||
tangemTechApi.getCoins(
|
||||
contractAddress = it.contractAddress,
|
||||
networkIds = it.blockchainDao.toBlockchain().toNetworkId(),
|
||||
active = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
.map {
|
||||
runCatching { it.await() }
|
||||
.onSuccess { return@map it.coins.firstOrNull()?.id }
|
||||
.onFailure { return@map null }
|
||||
|
||||
error("Unreachable code because runCatching must return result")
|
||||
}
|
||||
.mapIndexedNotNull { index, id ->
|
||||
if (id == null) null else tokens[index].contractAddress to id
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FILE_NAME_PREFIX_TOKENS = "tokens"
|
||||
private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
|
||||
private fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
|
||||
private fun getFileNameForBlockchains(cardId: String): String =
|
||||
"${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
|
||||
}
|
||||
}
|
||||
|
|
@ -28,31 +28,28 @@ class UserTokensRepository(
|
|||
private val networkConnectionManager: NetworkConnectionManager,
|
||||
) {
|
||||
|
||||
// TODO("After adding DI") replace with CoroutineDispatcherProvider
|
||||
suspend fun getUserTokens(card: CardDTO): List<Currency> = withContext(dispatchers.io) {
|
||||
val userId = getUserWalletId(card) ?: return@withContext emptyList()
|
||||
val userWalletId = getUserWalletId(card) ?: return@withContext emptyList()
|
||||
|
||||
if (DemoHelper.isDemoCardId(card.cardId)) {
|
||||
return@withContext loadTokensOffline(card, userId).ifEmpty(::loadDemoCurrencies)
|
||||
return@withContext loadTokensOffline(userWalletId = userWalletId).ifEmpty(::loadDemoCurrencies)
|
||||
}
|
||||
|
||||
if (!networkConnectionManager.isOnline) {
|
||||
return@withContext loadTokensOffline(card, userId)
|
||||
}
|
||||
if (!networkConnectionManager.isOnline) return@withContext loadTokensOffline(userWalletId = userWalletId)
|
||||
|
||||
return@withContext remoteGetUserTokens(card, userId)
|
||||
return@withContext remoteGetUserTokens(userWalletId = userWalletId)
|
||||
}
|
||||
|
||||
// TODO("After adding DI") replace with CoroutineDispatcherProvider
|
||||
suspend fun saveUserTokens(card: CardDTO, tokens: List<Currency>) = withContext(dispatchers.io) {
|
||||
val userId = getUserWalletId(card) ?: return@withContext
|
||||
val userWalletId = getUserWalletId(card) ?: return@withContext
|
||||
val userTokens = tokens.toUserTokensResponse()
|
||||
remoteSaveUserTokens(userId, userTokens)
|
||||
storageService.saveUserTokens(userId, userTokens)
|
||||
remoteSaveUserTokens(userWalletId = userWalletId, userTokens = userTokens)
|
||||
storageService.saveUserTokens(userWalletId = userWalletId, tokens = userTokens)
|
||||
}
|
||||
|
||||
suspend fun loadBlockchainsToDerive(card: CardDTO): List<BlockchainNetwork> = withContext(dispatchers.io) {
|
||||
val userId = getUserWalletId(card) ?: return@withContext emptyList()
|
||||
val blockchainNetworks = loadTokensOffline(card = card, userId = userId).toBlockchainNetworks()
|
||||
val userWalletId = getUserWalletId(card) ?: return@withContext emptyList()
|
||||
val blockchainNetworks = loadTokensOffline(userWalletId = userWalletId).toBlockchainNetworks()
|
||||
|
||||
if (DemoHelper.isDemoCardId(card.cardId)) {
|
||||
return@withContext blockchainNetworks.ifEmpty(loadDemoCurrencies()::toBlockchainNetworks)
|
||||
|
|
@ -61,8 +58,8 @@ class UserTokensRepository(
|
|||
return@withContext blockchainNetworks
|
||||
}
|
||||
|
||||
private suspend fun loadTokensOffline(card: CardDTO, userId: String): List<Currency> {
|
||||
return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card)
|
||||
private fun loadTokensOffline(userWalletId: String): List<Currency> {
|
||||
return storageService.getUserTokens(userWalletId = userWalletId) ?: emptyList()
|
||||
}
|
||||
|
||||
private fun loadDemoCurrencies(): List<Currency> {
|
||||
|
|
@ -77,54 +74,49 @@ class UserTokensRepository(
|
|||
.flatMap(BlockchainNetwork::toCurrencies)
|
||||
}
|
||||
|
||||
private fun List<Currency>.toUserTokensResponse() = UserTokensResponse(
|
||||
tokens = CurrencyConverter.convertList(this),
|
||||
group = GROUP_DEFAULT_VALUE,
|
||||
sort = SORT_DEFAULT_VALUE,
|
||||
)
|
||||
private fun List<Currency>.toUserTokensResponse(): UserTokensResponse {
|
||||
return UserTokensResponse(
|
||||
tokens = CurrencyConverter.convertList(input = this),
|
||||
group = GROUP_DEFAULT_VALUE,
|
||||
sort = SORT_DEFAULT_VALUE,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun handleGetUserTokensFailure(card: CardDTO, userId: String, error: Throwable): List<Currency> {
|
||||
private suspend fun handleGetUserTokensFailure(userWalletId: String, error: Throwable): List<Currency> {
|
||||
return when {
|
||||
error is TangemSdkError.NetworkError && error.customMessage.contains(NOT_FOUND_HTTP_CODE) ->
|
||||
storageService.getUserTokens(card).also {
|
||||
remoteSaveUserTokens(userId = userId, userTokens = it.toUserTokensResponse())
|
||||
}
|
||||
error is TangemSdkError.NetworkError && error.customMessage.contains(NOT_FOUND_HTTP_CODE) -> {
|
||||
storageService
|
||||
.getUserTokens(userWalletId)
|
||||
?.also { remoteSaveUserTokens(userWalletId = userWalletId, userTokens = it.toUserTokensResponse()) }
|
||||
?: emptyList()
|
||||
}
|
||||
else -> {
|
||||
val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card)
|
||||
tokens.distinct()
|
||||
storageService.getUserTokens(userWalletId)?.distinct() ?: emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun remoteGetUserTokens(card: CardDTO, userId: String): List<Currency> {
|
||||
runCatching {
|
||||
tangemTechApi.getUserTokens(userId)
|
||||
}.onSuccess { response ->
|
||||
return response.tokens
|
||||
.mapNotNull(Currency.Companion::fromTokenResponse).also {
|
||||
storageService.saveUserTokens(userId, it.toUserTokensResponse())
|
||||
}
|
||||
.distinct()
|
||||
}.onFailure {
|
||||
return handleGetUserTokensFailure(card = card, userId = userId, error = it)
|
||||
}
|
||||
error("Unreachable code because runCatching must return result")
|
||||
private suspend fun remoteGetUserTokens(userWalletId: String): List<Currency> {
|
||||
return runCatching { tangemTechApi.getUserTokens(userWalletId) }
|
||||
.fold(
|
||||
onSuccess = { response ->
|
||||
response.tokens
|
||||
.mapNotNull(Currency.Companion::fromTokenResponse)
|
||||
.also { storageService.saveUserTokens(userWalletId, it.toUserTokensResponse()) }
|
||||
.distinct()
|
||||
},
|
||||
onFailure = { handleGetUserTokensFailure(userWalletId = userWalletId, error = it) },
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun remoteSaveUserTokens(userId: String, userTokens: UserTokensResponse) {
|
||||
private suspend fun remoteSaveUserTokens(userWalletId: String, userTokens: UserTokensResponse) {
|
||||
// it can throw okhttp3.internal.http2.StreamResetException: stream was reset: INTERNAL_ERROR
|
||||
// if the /user-tokens endpoint disabled
|
||||
runCatching {
|
||||
tangemTechApi.saveUserTokens(userId, userTokens)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
runCatching { tangemTechApi.saveUserTokens(userWalletId, userTokens) }
|
||||
.onFailure { Timber.e(it) }
|
||||
}
|
||||
|
||||
private fun getUserWalletId(card: CardDTO): String? {
|
||||
return UserWalletIdBuilder.card(card).build()
|
||||
?.stringValue
|
||||
}
|
||||
private fun getUserWalletId(card: CardDTO): String? = UserWalletIdBuilder.card(card).build()?.stringValue
|
||||
|
||||
companion object {
|
||||
private const val GROUP_DEFAULT_VALUE = "none"
|
||||
|
|
@ -137,23 +129,10 @@ class UserTokensRepository(
|
|||
tangemTechService: TangemTechService,
|
||||
networkConnectionManager: NetworkConnectionManager,
|
||||
): UserTokensRepository {
|
||||
val fileReader = AndroidFileReader(context)
|
||||
val dispatchers = AppCoroutineDispatcherProvider()
|
||||
|
||||
val oldUserTokensRepository = OldUserTokensRepository(
|
||||
fileReader = fileReader,
|
||||
tangemTechApi = tangemTechService.api,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
val storageService = UserTokensStorageService(
|
||||
oldUserTokensRepository = oldUserTokensRepository,
|
||||
fileReader = fileReader,
|
||||
)
|
||||
|
||||
return UserTokensRepository(
|
||||
storageService = storageService,
|
||||
storageService = UserTokensStorageService(fileReader = AndroidFileReader(context)),
|
||||
tangemTechApi = tangemTechService.api,
|
||||
dispatchers = dispatchers,
|
||||
dispatchers = AppCoroutineDispatcherProvider(),
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,21 +4,16 @@ import com.squareup.moshi.JsonAdapter
|
|||
import com.tangem.Log
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.tap.common.FileReader
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.toCurrencies
|
||||
|
||||
class UserTokensStorageService(
|
||||
private val oldUserTokensRepository: OldUserTokensRepository,
|
||||
private val fileReader: FileReader,
|
||||
) {
|
||||
class UserTokensStorageService(private val fileReader: FileReader) {
|
||||
private val userTokensAdapter: JsonAdapter<UserTokensResponse> =
|
||||
MoshiConverter.networkMoshi.adapter(UserTokensResponse::class.java)
|
||||
|
||||
fun getUserTokens(userId: String): List<Currency>? {
|
||||
fun getUserTokens(userWalletId: String): List<Currency>? {
|
||||
return try {
|
||||
val json = fileReader.readFile(getFileNameForUserTokens(userId))
|
||||
val json = fileReader.readFile(getFileNameForUserTokens(userWalletId))
|
||||
userTokensAdapter.fromJson(json)?.tokens?.mapNotNull { Currency.fromTokenResponse(it) }
|
||||
} catch (exception: Exception) {
|
||||
Log.error { exception.stackTraceToString() }
|
||||
|
|
@ -26,16 +21,9 @@ class UserTokensStorageService(
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated("")
|
||||
suspend fun getUserTokens(card: CardDTO): List<Currency> {
|
||||
val blockchainNetworks =
|
||||
oldUserTokensRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
|
||||
return blockchainNetworks.flatMap { it.toCurrencies() }
|
||||
}
|
||||
|
||||
fun saveUserTokens(userId: String, tokens: UserTokensResponse) {
|
||||
fun saveUserTokens(userWalletId: String, tokens: UserTokensResponse) {
|
||||
val json = userTokensAdapter.toJson(tokens)
|
||||
fileReader.rewriteFile(json, getFileNameForUserTokens(userId))
|
||||
fileReader.rewriteFile(json, getFileNameForUserTokens(userWalletId))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue