diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index aae3cf8e9a..72d70b4bb1 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -2,7 +2,6 @@ package com.tangem.tap.domain import androidx.annotation.StringRes import com.tangem.common.core.TangemError -import com.tangem.datasource.api.tangemTech.TangemTechError import com.tangem.wallet.R interface TapErrors @@ -78,13 +77,4 @@ fun TapErrors.assembleErrors(): MutableList?>> { is TapError -> idList.add(Pair(this.messageResource, this.args)) } return idList -} - -fun TangemTechError.toTapError(): TapError { - return when (this.code) { - 404 -> NoDataError(this.description) - else -> TapError.CustomError(customMessage = this.description) - } -} - -class NoDataError(message: String) : TapError.CustomError(customMessage = message) \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt deleted file mode 100644 index 821a8046ac..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.tap.domain.tokens - -import com.tangem.common.core.TangemSdkError -import com.tangem.common.services.Result -import com.tangem.datasource.api.tangemTech.TangemTechService -import com.tangem.datasource.api.tangemTech.UserTokensResponse -import com.tangem.tap.domain.NoDataError - -class UserTokensNetworkService(private val tangemTechService: TangemTechService) { - suspend fun getUserTokens(userId: String): Result { - return when (val result = tangemTechService.getUserTokens(userId)) { - is Result.Success -> result - is Result.Failure -> { - val error = result.error - if (error is TangemSdkError.NetworkError && error.customMessage.contains("404")) { - return Result.Failure(NoDataError(error.customMessage)) - } else { - return result - } - } - } - } - - suspend fun saveUserTokens(userId: String, tokens: UserTokensResponse): Result { - return tangemTechService.putUserTokens(userId, tokens) - } -} diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 6fb7de9ad7..efd7dbefb9 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -2,13 +2,14 @@ package com.tangem.tap.domain.tokens import android.content.Context import com.tangem.blockchain.common.DerivationStyle -import com.tangem.common.services.Result +import com.tangem.common.core.TangemSdkError +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechService -import com.tangem.datasource.api.tangemTech.UserTokensResponse +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.common.CardDTO import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.AndroidFileReader -import com.tangem.tap.domain.NoDataError +import com.tangem.tap.domain.tokens.converters.CurrencyConverter import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.wallet.models.Currency @@ -16,93 +17,91 @@ import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.models.toCurrencies import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.store -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext class UserTokensRepository( private val storageService: UserTokensStorageService, - private val networkService: UserTokensNetworkService, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, ) { - suspend fun getUserTokens(card: CardDTO): List { + + // TODO("After adding DI") replace with CoroutineDispatcherProvider + suspend fun getUserTokens(card: CardDTO): List = withContext(dispatchers.io) { val userId = card.userWalletId.stringValue if (DemoHelper.isDemoCardId(card.cardId)) { - return loadTokensOffline(card, userId).ifEmpty { loadDemoCurrencies() } + return@withContext loadTokensOffline(card, userId).ifEmpty(::loadDemoCurrencies) } if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { - return loadTokensOffline(card, userId) + return@withContext loadTokensOffline(card, userId) } - return when (val networkResult = networkService.getUserTokens(userId)) { - is Result.Success -> { - val tokens = networkResult.data.tokens.mapNotNull { Currency.fromTokenResponse(it) } - storageService.saveUserTokens(userId, tokens.toUserTokensResponse()) - tokens.distinct() + runCatching { tangemTechApi.getUserTokens(userId) } + .onSuccess { response -> + return@withContext response.tokens + .mapNotNull(Currency.Companion::fromTokenResponse).also { + storageService.saveUserTokens(userId, it.toUserTokensResponse()) + } + .distinct() + } + .onFailure { + return@withContext handleGetUserTokensFailure(card = card, userId = userId, error = it) } - is Result.Failure -> { - handleGetUserTokensFailure(card = card, userId = userId, error = networkResult.error) - } - } + throw IllegalStateException("Unreachable code because runCatching must return result") } - suspend fun saveUserTokens(card: CardDTO, tokens: List) { + // TODO("After adding DI") replace with CoroutineDispatcherProvider + suspend fun saveUserTokens(card: CardDTO, tokens: List) = withContext(dispatchers.io) { val userId = card.userWalletId.stringValue val userTokens = tokens.toUserTokensResponse() - networkService.saveUserTokens(userId, userTokens) + tangemTechApi.saveUserTokens(userId, userTokens) storageService.saveUserTokens(userId, userTokens) } - suspend fun removeUserTokens(card: CardDTO) { - val userId = card.userWalletId.stringValue - val userTokens = emptyList().toUserTokensResponse() - networkService.saveUserTokens(userId, userTokens) - storageService.saveUserTokens(userId, userTokens) - } - - private fun List.toUserTokensResponse(): UserTokensResponse { - val tokensResponse = this.map { it.toTokenResponse() } - return UserTokensResponse( - tokens = tokensResponse, - group = GROUP_DEFAULT_VALUE, - sort = SORT_DEFAULT_VALUE, - ) - } - - suspend fun loadBlockchainsToDerive(card: CardDTO): List { - val userId = card.userWalletId.stringValue - val blockchainNetworks = loadTokensOffline(card, userId).toBlockchainNetworks() + suspend fun loadBlockchainsToDerive(card: CardDTO): List = withContext(dispatchers.io) { + val blockchainNetworks = loadTokensOffline( + card = card, + userId = card.userWalletId.stringValue, + ).toBlockchainNetworks() if (DemoHelper.isDemoCardId(card.cardId)) { - return blockchainNetworks - .ifEmpty { loadDemoCurrencies().toBlockchainNetworks() } + return@withContext blockchainNetworks.ifEmpty(loadDemoCurrencies()::toBlockchainNetworks) } - return blockchainNetworks + return@withContext blockchainNetworks + } + + private suspend fun loadTokensOffline(card: CardDTO, userId: String): List { + return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) } private fun loadDemoCurrencies(): List { - return DemoHelper.config.demoBlockchains.map { - BlockchainNetwork( - blockchain = it, - derivationPath = it.derivationPath(DerivationStyle.LEGACY)?.rawPath, - tokens = emptyList(), - ) - }.flatMap { it.toCurrencies() } + return DemoHelper.config.demoBlockchains + .map { blockchain -> + BlockchainNetwork( + blockchain = blockchain, + derivationPath = blockchain.derivationPath(DerivationStyle.LEGACY)?.rawPath, + tokens = emptyList(), + ) + } + .flatMap(BlockchainNetwork::toCurrencies) } - private suspend fun handleGetUserTokensFailure( - card: CardDTO, - userId: String, - error: Throwable, - ): List { - return when (error) { - is NoDataError -> { - val tokens = storageService.getUserTokens(card) - val userTokens = tokens.toUserTokensResponse() - coroutineScope { launch { networkService.saveUserTokens(userId = userId, tokens = userTokens) } } - tokens - } + private fun List.toUserTokensResponse() = UserTokensResponse( + tokens = CurrencyConverter.convertList(this), + group = GROUP_DEFAULT_VALUE, + sort = SORT_DEFAULT_VALUE, + ) + + private suspend fun handleGetUserTokensFailure(card: CardDTO, userId: String, error: Throwable): List { + return when { + error is TangemSdkError.NetworkError && error.customMessage.contains(NOT_FOUND_HTTP_CODE) -> + storageService.getUserTokens(card).also { + tangemTechApi.saveUserTokens(userId = userId, userTokens = it.toUserTokensResponse()) + } else -> { val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) tokens.distinct() @@ -110,21 +109,28 @@ class UserTokensRepository( } } - private suspend fun loadTokensOffline(card: CardDTO, userId: String): List { - return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) - } - companion object { - const val SORT_DEFAULT_VALUE = "manual" - const val GROUP_DEFAULT_VALUE = "none" + private const val GROUP_DEFAULT_VALUE = "none" + private const val SORT_DEFAULT_VALUE = "manual" + private const val NOT_FOUND_HTTP_CODE = "404" + + // TODO("After adding DI") get dependencies by DI fun init(context: Context, tangemTechService: TangemTechService): UserTokensRepository { val fileReader = AndroidFileReader(context) val oldUserTokensRepository = OldUserTokensRepository( - fileReader, store.state.domainNetworks.tangemTechService, + fileReader = fileReader, + tangemNetworkService = store.state.domainNetworks.tangemTechService, + ) + val storageService = UserTokensStorageService( + oldUserTokensRepository = oldUserTokensRepository, + fileReader = fileReader, + ) + + return UserTokensRepository( + storageService = storageService, + tangemTechApi = tangemTechService.api, + dispatchers = AppCoroutineDispatcherProvider(), ) - val storageService = UserTokensStorageService(oldUserTokensRepository, fileReader) - val networkService = UserTokensNetworkService(tangemTechService) - return UserTokensRepository(storageService, networkService) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt index 9e5dac0b68..93d4d909ce 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt @@ -2,8 +2,8 @@ package com.tangem.tap.domain.tokens import com.squareup.moshi.JsonAdapter import com.tangem.Log -import com.tangem.datasource.api.tangemTech.UserTokensResponse 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 diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt b/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt new file mode 100644 index 0000000000..48f55ed575 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.domain.tokens.converters + +import com.tangem.datasource.api.tangemTech.models.TokenBody +import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.utils.converter.Converter + +/** Converter from domain model [Currency] to data model [TokenBody] */ +object CurrencyConverter : Converter { + + override fun convert(value: Currency) = TokenBody( + id = value.coinId, + networkId = value.blockchain.toNetworkId(), + derivationPath = value.derivationPath, + name = value.currencyName, + symbol = value.currencySymbol, + decimals = value.decimals, + contractAddress = if (value is Currency.Token) value.token.contractAddress else null + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt index 146d622d16..00ea34457c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt @@ -1,11 +1,10 @@ package com.tangem.tap.features.wallet.models import com.tangem.blockchain.common.DerivationStyle +import com.tangem.datasource.api.tangemTech.models.TokenBody import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toCoinId -import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.features.addCustomToken.CustomCurrency -import com.tangem.datasource.api.tangemTech.TokenResponse import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.tokens.redux.TokenWithBlockchain @@ -57,17 +56,6 @@ sealed interface Currency { fun isBlockchain(): Boolean = this is Blockchain fun isToken(): Boolean = this is Token - fun toTokenResponse(): TokenResponse { - return TokenResponse( - id = coinId, - networkId = blockchain.toNetworkId(), - derivationPath = derivationPath, - name = currencyName, - symbol = currencySymbol, - decimals = decimals, - contractAddress = if (this is Token) token.contractAddress else null, - ) - } companion object { fun fromBlockchainNetwork( @@ -110,24 +98,24 @@ sealed interface Currency { ) } - fun fromTokenResponse(tokenResponse: TokenResponse): Currency? { - val blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId) + fun fromTokenResponse(tokenBody: TokenBody): Currency? { + val blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenBody.networkId) ?: return null return when { - tokenResponse.contractAddress != null -> Token( + tokenBody.contractAddress != null -> Token( token = SdkToken( - name = tokenResponse.name, - symbol = tokenResponse.symbol, - contractAddress = tokenResponse.contractAddress!!, - decimals = tokenResponse.decimals, - id = tokenResponse.id, + name = tokenBody.name, + symbol = tokenBody.symbol, + contractAddress = tokenBody.contractAddress!!, + decimals = tokenBody.decimals, + id = tokenBody.id, ), blockchain = blockchain, - derivationPath = tokenResponse.derivationPath, + derivationPath = tokenBody.derivationPath, ) else -> Blockchain( blockchain = blockchain, - derivationPath = tokenResponse.derivationPath, + derivationPath = tokenBody.derivationPath, ) } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt index adcccdac54..64e625c180 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt @@ -32,26 +32,4 @@ data class CoinsResponse( } //rates.keys = networkId's -data class RatesResponse(val rates: Map) : TangemTechResponse - -data class UserTokensResponse( - val version: Int = 0, - val group: String? = null, - val sort: String? = null, - val tokens: List = emptyList(), -) : TangemTechResponse - -data class TokenResponse( - val id: String? = null, - val networkId: String, - val derivationPath: String? = null, - val name: String, - val symbol: String, - val decimals: Int, - val contractAddress: String?, -) : TangemTechResponse - -data class TangemTechError( - val code: Int, - val description: String, -) \ No newline at end of file +data class RatesResponse(val rates: Map) : TangemTechResponse \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 8a46ed2092..2dfe10dd02 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -2,6 +2,7 @@ package com.tangem.datasource.api.tangemTech import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.datasource.api.tangemTech.models.GeoResponse +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.PUT @@ -40,5 +41,5 @@ interface TangemTechApi { suspend fun getUserTokens(@Path(value = "user-id") userId: String): UserTokensResponse @PUT("user-tokens/{user-id}") - suspend fun putUserTokens(@Path(value = "user-id") userId: String, @Body userTokens: UserTokensResponse) + suspend fun saveUserTokens(@Path(value = "user-id") userId: String, @Body userTokens: UserTokensResponse) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt index 3827446aec..136a9abcf7 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt @@ -50,15 +50,6 @@ class TangemTechService( } } - suspend fun getUserTokens(userId: String): Result = withContext(Dispatchers.IO) { - performRequest { api.getUserTokens(userId) } - } - - suspend fun putUserTokens(userId: String, userTokens: UserTokensResponse): Result = - withContext(Dispatchers.IO) { - performRequest { api.putUserTokens(userId, userTokens) } - } - fun addHeaderInterceptors(interceptors: List) { headerInterceptors.removeAll(interceptors) headerInterceptors.addAll(interceptors) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt new file mode 100644 index 0000000000..22c9d2fd4e --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt @@ -0,0 +1,20 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +data class UserTokensResponse( + @Json(name = "version") val version: Int = 0, + @Json(name = "group") val group: String? = null, + @Json(name = "sort") val sort: String? = null, + @Json(name = "tokens") val tokens: List = emptyList(), +) + +data class TokenBody( + @Json(name = "id") val id: String? = null, + @Json(name = "networkId") val networkId: String, + @Json(name = "derivationPath") val derivationPath: String? = null, + @Json(name = "name") val name: String, + @Json(name = "symbol") val symbol: String, + @Json(name = "decimals") val decimals: Int, + @Json(name = "contractAddress") val contractAddress: String?, +) \ No newline at end of file