Updated on 2026-08-14

This commit is contained in:
Tangem 2023-03-28 18:36:22 +08:00
parent fccb0e8347
commit 5af84ab1e1
3 changed files with 49 additions and 220 deletions

View file

@ -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"
}
}

View file

@ -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,
)
}

View file

@ -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 {