From 75075b53c8b5c7026be3643aec946f31be022531 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Nov 2023 14:04:21 +0200 Subject: [PATCH] Updated on 2026-08-14 --- .../tokens/GetCryptoCurrencyStatusUseCase.kt | 52 +++++ .../tangem/feature/swap/SwapRepositoryImpl.kt | 22 +- .../swap/domain/BlockchainInteractor.kt | 4 +- ...Impl.kt => DefaultBlockchainInteractor.kt} | 12 +- .../feature/swap/domain/SwapInteractor.kt | 26 +-- .../feature/swap/domain/SwapInteractorImpl.kt | 218 +++++++++++------- .../feature/swap/domain/SwapRepository.kt | 6 +- .../swap/domain/cache/SwapDataCache.kt | 10 +- .../swap/domain/cache/SwapDataCacheImpl.kt | 14 +- .../converters/SwapCurrencyConverter.kt | 49 +--- .../swap/domain/di/SwapDomainModule.kt | 46 +++- .../domain/models/domain/PermissionOptions.kt | 3 +- .../models/ui/TokensDataStateExpress.kt | 31 +++ .../swap/converters/TokensDataConverter.kt | 16 +- .../feature/swap/models/SwapStateHolder.kt | 2 +- .../tangem/feature/swap/ui/StateBuilder.kt | 25 +- .../feature/swap/ui/SwapScreenContent.kt | 4 +- .../feature/swap/viewmodels/SwapViewModel.kt | 101 ++++---- 18 files changed, 412 insertions(+), 229 deletions(-) create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusUseCase.kt rename features/swap/domain/src/main/java/com/tangem/feature/swap/domain/{BlockchainInteractorImpl.kt => DefaultBlockchainInteractor.kt} (76%) create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TokensDataStateExpress.kt diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusUseCase.kt new file mode 100644 index 0000000000..51029ebe6e --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusUseCase.kt @@ -0,0 +1,52 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.error.mapper.mapToTokenListError +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations +import com.tangem.domain.tokens.operations.TokenListOperations +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.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.* + +class GetCryptoCurrencyStatusUseCase( + internal val currenciesRepository: CurrenciesRepository, + internal val quotesRepository: QuotesRepository, + internal val networksRepository: NetworksRepository, + internal val dispatchers: CoroutineDispatcherProvider, +) { + + operator fun invoke(userWalletId: UserWalletId): Flow>> { + val operations = CurrenciesStatusesOperations( + userWalletId = userWalletId, + currenciesRepository = currenciesRepository, + quotesRepository = quotesRepository, + networksRepository = networksRepository, + ) + + return operations.getCurrenciesStatusesFlow() + .map { maybeCurrenciesStatuses -> + maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError) + } + } + + private fun createTokenList( + userWalletId: UserWalletId, + tokens: List, + ): Flow> { + val operations = TokenListOperations( + userWalletId = userWalletId, + tokens = tokens, + currenciesRepository = currenciesRepository, + ) + + return operations.getTokenListFlow().map { maybeTokenList -> + maybeTokenList.mapLeft(TokenListOperations.Error::mapToTokenListError) + } + } +} \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index 53055fe8d4..514ea79360 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -193,26 +193,26 @@ internal class SwapRepositoryImpl @Inject constructor( override suspend fun getCryptoCurrency( userWallet: UserWallet, - currency: Currency, + currency: CryptoCurrency, network: Network, ): CryptoCurrency? { - val blockchain = Blockchain.fromNetworkId(currency.networkId) ?: return null + val blockchain = Blockchain.fromNetworkId(currency.network.id.value) ?: return null val cryptoCurrencyFactory = CryptoCurrencyFactory() return when (currency) { - is Currency.NativeToken -> { + is CryptoCurrency.Coin -> { cryptoCurrencyFactory.createCoin( blockchain = blockchain, extraDerivationPath = network.derivationPath.value, derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider, ) } - is Currency.NonNativeToken -> { + is CryptoCurrency.Token -> { val sdkToken = SdkToken( name = currency.name, symbol = currency.symbol, contractAddress = currency.contractAddress, - decimals = currency.decimalCount, - id = currency.id, + decimals = currency.decimals, + id = currency.id.value, ) cryptoCurrencyFactory.createToken( sdkToken = sdkToken, @@ -258,7 +258,7 @@ internal class SwapRepositoryImpl @Inject constructor( userWalletId: UserWalletId, networkId: String, derivationPath: String?, - currency: Currency, + currency: CryptoCurrency, amount: BigDecimal?, ): String { val blockchain = @@ -276,16 +276,16 @@ internal class SwapRepositoryImpl @Inject constructor( ) ?: error("Cannot cast to Approver") } - private fun convertToAmount(amount: BigDecimal, currency: Currency, blockchain: Blockchain): Amount { + private fun convertToAmount(amount: BigDecimal, currency: CryptoCurrency, blockchain: Blockchain): Amount { return when (currency) { - is Currency.NativeToken -> { + is CryptoCurrency.Token -> { Amount(value = amount, blockchain = blockchain) } - is Currency.NonNativeToken -> { + is CryptoCurrency.Coin -> { Amount( currencySymbol = currency.symbol, value = amount, - decimals = currency.decimalCount, + decimals = currency.decimals, ) } } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/BlockchainInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/BlockchainInteractor.kt index 9830aea157..229911c654 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/BlockchainInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/BlockchainInteractor.kt @@ -1,11 +1,11 @@ package com.tangem.feature.swap.domain -import com.tangem.feature.swap.domain.models.domain.Currency +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.feature.swap.domain.models.domain.NetworkInfo interface BlockchainInteractor { - fun getTokenDecimals(token: Currency): Int + fun getTokenDecimals(token: CryptoCurrency): Int /** * In app blockchain id, actual in blockchain sdk, not the same as networkId diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/BlockchainInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/DefaultBlockchainInteractor.kt similarity index 76% rename from features/swap/domain/src/main/java/com/tangem/feature/swap/domain/BlockchainInteractorImpl.kt rename to features/swap/domain/src/main/java/com/tangem/feature/swap/domain/DefaultBlockchainInteractor.kt index 969b20c5b3..0b9605485b 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/BlockchainInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/DefaultBlockchainInteractor.kt @@ -1,11 +1,11 @@ package com.tangem.feature.swap.domain -import com.tangem.feature.swap.domain.models.domain.Currency +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.feature.swap.domain.models.domain.NetworkInfo import com.tangem.lib.crypto.TransactionManager import javax.inject.Inject -internal class BlockchainInteractorImpl @Inject constructor( +internal class DefaultBlockchainInteractor @Inject constructor( private val transactionManager: TransactionManager, ) : BlockchainInteractor { @@ -23,11 +23,11 @@ internal class BlockchainInteractorImpl @Inject constructor( return transactionManager.getExplorerTransactionLink(networkId, txAddress) } - override fun getTokenDecimals(token: Currency): Int { - return if (token is Currency.NonNativeToken) { - token.decimalCount + override fun getTokenDecimals(token: CryptoCurrency): Int { + return if (token is CryptoCurrency.Token) { + token.decimals } else { - transactionManager.getNativeTokenDecimals(token.networkId) + transactionManager.getNativeTokenDecimals(token.network.id.value) } } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index 53c62fc7ea..13d3c2d3f4 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -9,9 +9,7 @@ import java.math.BigDecimal interface SwapInteractor { - suspend fun getPairs(currency: Currency): List - - suspend fun getPairs(initialCurrency: LeastTokenInfo, currenciesList: List): List + suspend fun getTokensDataState(currency: Currency): TokensDataStateExpress fun initDerivationPathAndNetwork(derivationPath: String?, network: Network?) @@ -31,17 +29,17 @@ interface SwapInteractor { * * @param networkId networkId for tokens * @param searchQuery string query for search - * @return [FoundTokensState] that contains list of tokens matching condition query + * @return [FoundTokensStateExpress] that contains list of tokens matching condition query */ - suspend fun searchTokens(networkId: String, searchQuery: String): FoundTokensState + suspend fun searchTokens(networkId: String, searchQuery: String): FoundTokensStateExpress /** * Find specific token by id, null if not found * * @param id token id - * @return [Currency] or null + * @return [CryptoCurrency] or null */ - fun findTokenById(id: String): Currency? + fun findTokenById(id: String): CryptoCurrency? /** * Gives permission to swap, this starts scan card process @@ -66,8 +64,8 @@ interface SwapInteractor { @Throws(IllegalStateException::class) suspend fun findBestQuote( networkId: String, - fromToken: Currency, - toToken: Currency, + fromToken: CryptoCurrency, + toToken: CryptoCurrency, amountToSwap: String, selectedFee: FeeType = FeeType.NORMAL, ): SwapState @@ -88,8 +86,8 @@ interface SwapInteractor { suspend fun onSwap( networkId: String, swapStateData: SwapStateData, - currencyToSend: Currency, - currencyToGet: Currency, + currencyToSend: CryptoCurrency, + currencyToGet: CryptoCurrency, amountToSwap: String, fee: TxFee, ): TxState @@ -100,16 +98,16 @@ interface SwapInteractor { * @param networkId * @param token */ - fun getTokenBalance(networkId: String, token: Currency): SwapAmount + fun getTokenBalance(networkId: String, token: CryptoCurrency): SwapAmount fun isAvailableToSwap(networkId: String): Boolean - fun getSwapAmountForToken(amount: String, token: Currency): SwapAmount + fun getSwapAmountForToken(amount: String, token: CryptoCurrency): SwapAmount suspend fun checkFeeIsEnough( fee: BigDecimal?, spendAmount: SwapAmount, networkId: String, - fromToken: Currency, + fromToken: CryptoCurrency, ): Boolean } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 9f1ef22479..1381d354c9 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -1,12 +1,13 @@ package com.tangem.feature.swap.domain import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase -import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase +import arrow.core.getOrElse +import com.tangem.domain.tokens.GetCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository -import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.feature.swap.domain.cache.SwapDataCache import com.tangem.feature.swap.domain.converters.SwapCurrencyConverter @@ -21,6 +22,7 @@ import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.models.* import com.tangem.lib.crypto.models.transactions.SendTxResult import com.tangem.utils.toFiatString +import kotlinx.coroutines.flow.first import timber.log.Timber import java.math.BigDecimal import java.math.RoundingMode @@ -37,7 +39,7 @@ internal class SwapInteractorImpl @Inject constructor( private val networksRepository: NetworksRepository, private val walletFeatureToggles: WalletFeatureToggles, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, - private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase, + private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusUseCase, ) : SwapInteractor { // TODO: Move to DI @@ -50,26 +52,65 @@ internal class SwapInteractorImpl @Inject constructor( private var derivationPath: String? = null private var network: Network? = null - override suspend fun getPairs(currency: Currency): List { - val currencies = getSelectedWalletSyncUseCase().fold( - ifLeft = { emptyList() }, - ifRight = { selectedWallet -> - getCryptoCurrenciesUseCase(selectedWallet.walletId).fold( - ifLeft = { emptyList() }, - ifRight = { it }, - ) - }, + override suspend fun getTokensDataState(currency: Currency): TokensDataStateExpress { + val selectedWallet = getSelectedWalletSyncUseCase().fold( + ifLeft = { null }, + ifRight = { it }, ) - val pairs = getPairs( + requireNotNull(selectedWallet) + + val currencyStatuses = getMultiCryptoCurrencyStatusUseCase(selectedWallet.walletId) + .first() + .getOrElse { emptyList() } + // .filter { it.currency.network.backendId != currency.networkId } + val currencies = currencyStatuses.map { it.currency } + + val pairsLeast = getPairs( initialCurrency = LeastTokenInfo( contractAddress = (currency as? Currency.NonNativeToken)?.contractAddress ?: "0", network = currency.networkId, ), - currenciesList = currencies, + currenciesList = currencyStatuses.map { it.currency }, ) - return createCryptoCurrencyPairs(pairs, currencies) + val pairs = createCryptoCurrencyPairs(pairsLeast, currencies) + + val initialCryptoCurrency = mapLegacyCurrencyToCryptoCurrency(currency, currencyStatuses) + ?: error("Initial crypto currency must not be null") + + return TokensDataStateExpress( + initialCryptoCurrency = initialCryptoCurrency, + preselectTokens = getPreselectTokens(currency, currencyStatuses), + foundTokensState = FoundTokensStateExpress(emptyList(), emptyList()), + pairs = pairs, + ) + } + + private fun getPreselectTokens(currency: Currency, currencies: List): PreselectTokensExpress { + val from = mapLegacyCurrencyToCryptoCurrency(currency, currencies) + + val to = currencies.firstOrNull()?.currency // TODO choose of 3 variants + + if (from != null && to != null) { + return PreselectTokensExpress( + fromToken = from, + toToken = to, + ) + } else { + error("From and to currencies must not be null") + } + } + + private fun mapLegacyCurrencyToCryptoCurrency( + currency: Currency, + currencies: List, + ): CryptoCurrency? { + return currencies.map { it.currency } + .find { + it.network.backendId == currency.networkId && + it.getContractAddress() == currency.getContractAddress() + } } private fun createCryptoCurrencyPairs( @@ -97,22 +138,35 @@ internal class SwapInteractorImpl @Inject constructor( ): CryptoCurrency? { return cryptoCurrenciesList.find { it.network.backendId == leastTokenInfo.network && - (it as? CryptoCurrency.Token)?.contractAddress ?: "0" == leastTokenInfo.contractAddress + it.getContractAddress() == leastTokenInfo.contractAddress } } - override suspend fun getPairs( - initialCurrency: LeastTokenInfo, - currenciesList: List, - ): List { + private fun CryptoCurrency.getContractAddress(): String { + return when (this) { + is CryptoCurrency.Token -> this.contractAddress + is CryptoCurrency.Coin -> "0" + } + } + + private fun Currency.getContractAddress(): String { + return when (this) { + is Currency.NativeToken -> "0" + is Currency.NonNativeToken -> this.contractAddress + } + } + + suspend fun getPairs(initialCurrency: LeastTokenInfo, currenciesList: List): List { return repository.getPairs(initialCurrency, currenciesList) } + @Deprecated("used in old swap mechanism") override fun initDerivationPathAndNetwork(derivationPath: String?, network: Network?) { this.derivationPath = derivationPath this.network = network } + @Deprecated("used in old swap mechanism") override suspend fun initTokensToSwap(initialCurrency: Currency): TokensDataState { // TODO: refactor this function val networkId = initialCurrency.networkId @@ -134,7 +188,7 @@ internal class SwapInteractorImpl @Inject constructor( allLoadedTokens.firstOrNull { it.symbol == token.symbol }?.let { loadedOnWalletsMap.add(it.symbol) it - } ?: swapCurrencyConverter.convertBack(token) + } ?: TODO() } .filter { it.symbol != initialCurrency.symbol && allLoadedTokens.contains(it) } val loadedTokens = allLoadedTokens @@ -146,21 +200,22 @@ internal class SwapInteractorImpl @Inject constructor( val appCurrency = userWalletManager.getUserAppCurrency() val rates = repository.getRates(appCurrency.code, tokensInWallet.map { it.id }) cache.cacheBalances(networkId, derivationPath, tokensBalance) - cache.cacheLoadedTokens(loadedTokens.map { TokenWithBalance(it) }) - cache.cacheInWalletTokens(getTokensWithBalance(tokensInWallet, tokensBalance, rates, appCurrency)) + // cache.cacheLoadedTokens(loadedTokens.map { TokenWithBalance(it) }) + // cache.cacheInWalletTokens(getTokensWithBalance(tokensInWallet, tokensBalance, rates, appCurrency)) return TokensDataState( preselectTokens = PreselectTokens( fromToken = initialCurrency, toToken = selectToToken(initialCurrency, tokensInWallet, loadedTokens), ), foundTokensState = FoundTokensState( - tokensInWallet = cache.getInWalletTokens(), - loadedTokens = cache.getLoadedTokens(), + tokensInWallet = emptyList(), // cache.getInWalletTokens(), + loadedTokens = emptyList(), // cache.getLoadedTokens(), ), ) } - override suspend fun searchTokens(networkId: String, searchQuery: String): FoundTokensState { + @Deprecated("used in old swap mechanism") + override suspend fun searchTokens(networkId: String, searchQuery: String): FoundTokensStateExpress { val searchQueryLowerCase = searchQuery.lowercase() val tokensInWallet = cache.getInWalletTokens() .filter { @@ -172,19 +227,21 @@ internal class SwapInteractorImpl @Inject constructor( it.token.name.lowercase().contains(searchQueryLowerCase) || it.token.symbol.lowercase().contains(searchQueryLowerCase) } - return FoundTokensState( + return FoundTokensStateExpress( tokensInWallet = tokensInWallet, loadedTokens = loadedTokens, ) } - override fun findTokenById(id: String): Currency? { + @Deprecated("used in old swap mechanism") + override fun findTokenById(id: String): CryptoCurrency? { val tokensInWallet = cache.getInWalletTokens() val loadedTokens = cache.getLoadedTokens() - return tokensInWallet.firstOrNull { it.token.id == id }?.token - ?: loadedTokens.firstOrNull { it.token.id == id }?.token + return tokensInWallet.firstOrNull { it.token.id.value == id }?.token + ?: loadedTokens.firstOrNull { it.token.id.value == id }?.token } + @Deprecated("used in old swap mechanism") override suspend fun givePermissionToSwap(networkId: String, permissionOptions: PermissionOptions): TxState { val dataToSign = if (permissionOptions.approveType == SwapApproveType.UNLIMITED) { getApproveData( @@ -223,10 +280,11 @@ internal class SwapInteractorImpl @Inject constructor( } } + @Deprecated("used in old swap mechanism") override suspend fun findBestQuote( networkId: String, - fromToken: Currency, - toToken: Currency, + fromToken: CryptoCurrency, + toToken: CryptoCurrency, amountToSwap: String, selectedFee: FeeType, ): SwapState { @@ -268,11 +326,12 @@ internal class SwapInteractorImpl @Inject constructor( } } + @Deprecated("used in old swap mechanism") override suspend fun onSwap( networkId: String, swapStateData: SwapStateData, - currencyToSend: Currency, - currencyToGet: Currency, + currencyToSend: CryptoCurrency, + currencyToGet: CryptoCurrency, amountToSwap: String, fee: TxFee, ): TxState { @@ -322,7 +381,8 @@ internal class SwapInteractorImpl @Inject constructor( } } - override fun getTokenBalance(networkId: String, token: Currency): SwapAmount { + @Deprecated("used in old swap mechanism") + override fun getTokenBalance(networkId: String, token: CryptoCurrency): SwapAmount { return cache.getBalanceForToken( networkId = networkId, derivationPath = derivationPath, @@ -330,25 +390,28 @@ internal class SwapInteractorImpl @Inject constructor( ) ?: SwapAmount(BigDecimal.ZERO, getTokenDecimals(token)) } + @Deprecated("used in old swap mechanism") override fun isAvailableToSwap(networkId: String): Boolean { return ONE_INCH_SUPPORTED_NETWORKS.contains(networkId) } - override fun getSwapAmountForToken(amount: String, token: Currency): SwapAmount { + @Deprecated("used in old swap mechanism") + override fun getSwapAmountForToken(amount: String, token: CryptoCurrency): SwapAmount { val amountDecimal = requireNotNull(toBigDecimalOrNull(amount)) { "wrong amount format" } return SwapAmount(amountDecimal, getTokenDecimals(token)) } - private suspend fun onSuccessLegacyFlow(currency: Currency) { + @Deprecated("used in old swap mechanism") + private suspend fun onSuccessLegacyFlow(currency: CryptoCurrency) { userWalletManager.addToken(swapCurrencyConverter.convert(currency), derivationPath) userWalletManager.refreshWallet() } - private suspend fun onSuccessNewFlow(currency: Currency) { - val network = network ?: return + @Deprecated("used in old swap mechanism") + private suspend fun onSuccessNewFlow(currency: CryptoCurrency) { getSelectedWalletSyncUseCase().fold( ifRight = { userWallet -> - getAndAddCryptoCurrency(userWallet, currency, network) + addCryptoCurrenciesUseCase(userWallet.walletId, currency) }, ifLeft = { Timber.e("Swap Error on getSelectedWalletUseCase") @@ -356,24 +419,20 @@ internal class SwapInteractorImpl @Inject constructor( ) } - private suspend fun getAndAddCryptoCurrency(userWallet: UserWallet, currency: Currency, network: Network) { - repository.getCryptoCurrency(userWallet, currency, network)?.let { cryptoCurrency -> - addCryptoCurrenciesUseCase(userWallet.walletId, cryptoCurrency) - } - } - + @Deprecated("used in old swap mechanism") private fun getTangemFee(): Double { return repository.getTangemFee() } - private fun getTokenDecimals(token: Currency): Int { - return if (token is Currency.NonNativeToken) { - token.decimalCount + private fun getTokenDecimals(token: CryptoCurrency): Int { + return if (token is CryptoCurrency.Token) { + token.decimals } else { - transactionManager.getNativeTokenDecimals(token.networkId) + transactionManager.getNativeTokenDecimals(token.network.id.value) } } + @Deprecated("used in old swap mechanism") private fun selectToToken( initialToken: Currency, tokensInWallet: List, @@ -394,6 +453,7 @@ internal class SwapInteractorImpl @Inject constructor( return toToken } + @Deprecated("used in old swap mechanism") private fun getTokensWithBalance( tokens: List, balances: Map, @@ -418,8 +478,8 @@ internal class SwapInteractorImpl @Inject constructor( } } - private suspend fun isAllowedToSpend(networkId: String, fromToken: Currency, amount: SwapAmount): Boolean { - if (fromToken is Currency.NativeToken) return true + private suspend fun isAllowedToSpend(networkId: String, fromToken: CryptoCurrency, amount: SwapAmount): Boolean { + if (fromToken is CryptoCurrency.Coin) return true return getSelectedWalletSyncUseCase().fold( ifRight = { userWallet -> val allowance = repository.getAllowance( @@ -438,7 +498,11 @@ internal class SwapInteractorImpl @Inject constructor( ) } - private fun createEmptyAmountState(networkId: String, fromToken: Currency, toToken: Currency): SwapState { + private fun createEmptyAmountState( + networkId: String, + fromToken: CryptoCurrency, + toToken: CryptoCurrency, + ): SwapState { val appCurrency = userWalletManager.getUserAppCurrency() val fromTokenBalance = cache.getBalanceForToken(networkId, derivationPath, fromToken.symbol) val toTokenBalance = cache.getBalanceForToken(networkId, derivationPath, toToken.symbol) @@ -462,8 +526,8 @@ internal class SwapInteractorImpl @Inject constructor( fromTokenAddress: String, toTokenAddress: String, amount: SwapAmount, - fromToken: Currency, - toToken: Currency, + fromToken: CryptoCurrency, + toToken: CryptoCurrency, isAllowedToSpend: Boolean, isBalanceWithoutFeeEnough: Boolean, ): SwapState { @@ -520,8 +584,8 @@ internal class SwapInteractorImpl @Inject constructor( networkId: String, fromTokenAddress: String, toTokenAddress: String, - fromToken: Currency, - toToken: Currency, + fromToken: CryptoCurrency, + toToken: CryptoCurrency, amount: SwapAmount, selectedFee: FeeType, ): SwapState { @@ -585,45 +649,45 @@ internal class SwapInteractorImpl @Inject constructor( @Suppress("LongParameterList") private suspend fun updateBalances( networkId: String, - fromToken: Currency, - toToken: Currency, + fromToken: CryptoCurrency, + toToken: CryptoCurrency, fromTokenAmount: SwapAmount, toTokenAmount: SwapAmount, swapStateData: SwapStateData?, ): SwapState.QuotesLoadedState { val appCurrency = userWalletManager.getUserAppCurrency() val nativeToken = userWalletManager.getNativeTokenForNetwork(networkId) - val rates = repository.getRates(appCurrency.code, listOf(fromToken.id, toToken.id, nativeToken.id)) + val rates = repository.getRates(appCurrency.code, listOf(fromToken.id.value, toToken.id.value, nativeToken.id)) val fromTokenBalance = cache.getBalanceForToken(networkId, derivationPath, fromToken.symbol) val toTokenBalance = cache.getBalanceForToken(networkId, derivationPath, toToken.symbol) return SwapState.QuotesLoadedState( fromTokenInfo = TokenSwapInfo( tokenAmount = fromTokenAmount, - coinId = fromToken.id, + coinId = fromToken.id.value, tokenWalletBalance = fromTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") } ?: ZERO_BALANCE, tokenFiatBalance = fromTokenAmount.value.toFiatString( - rateValue = rates[fromToken.id]?.toBigDecimal() ?: BigDecimal.ZERO, + rateValue = rates[fromToken.id.value]?.toBigDecimal() ?: BigDecimal.ZERO, fiatCurrencyName = appCurrency.symbol, formatWithSpaces = true, ), ), toTokenInfo = TokenSwapInfo( tokenAmount = toTokenAmount, - coinId = toToken.id, + coinId = toToken.id.value, tokenWalletBalance = toTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") } ?: ZERO_BALANCE, tokenFiatBalance = toTokenAmount.value.toFiatString( - rateValue = rates[toToken.id]?.toBigDecimal() ?: BigDecimal.ZERO, + rateValue = rates[toToken.id.value]?.toBigDecimal() ?: BigDecimal.ZERO, fiatCurrencyName = appCurrency.symbol, formatWithSpaces = true, ), ), priceImpact = calculatePriceImpact( fromTokenAmount = fromTokenAmount.value, - fromRate = rates[fromToken.id] ?: 0.0, + fromRate = rates[fromToken.id.value] ?: 0.0, toTokenAmount = toTokenAmount.value, - toRate = rates[toToken.id] ?: 0.0, + toRate = rates[toToken.id.value] ?: 0.0, ), networkCurrency = userWalletManager.getNetworkCurrency(networkId), swapDataModel = swapStateData, @@ -634,7 +698,7 @@ internal class SwapInteractorImpl @Inject constructor( @Suppress("LongParameterList") private suspend fun updatePermissionState( networkId: String, - fromToken: Currency, + fromToken: CryptoCurrency, swapAmount: SwapAmount, quotesLoadedState: SwapState.QuotesLoadedState, ): SwapState.QuotesLoadedState { @@ -691,7 +755,7 @@ internal class SwapInteractorImpl @Inject constructor( ) } - private suspend fun syncWalletBalanceForTokens(networkId: String, tokens: List) { + private suspend fun syncWalletBalanceForTokens(networkId: String, tokens: List) { val tokensToSync = tokens.filter { cache.getBalanceForToken(networkId, derivationPath, it.symbol) == null } if (tokensToSync.isNotEmpty()) { val tokensBalance = @@ -746,12 +810,12 @@ internal class SwapInteractorImpl @Inject constructor( private fun isBalanceEnough( networkId: String, - fromToken: Currency, + fromToken: CryptoCurrency, amount: SwapAmount, fee: BigDecimal?, ): Boolean { val tokenBalance = getTokenBalance(networkId, fromToken).value - return if (fromToken is Currency.NonNativeToken) { + return if (fromToken is CryptoCurrency.Token) { tokenBalance >= amount.value } else { tokenBalance > amount.value.plus(fee ?: BigDecimal.ZERO) @@ -762,12 +826,12 @@ internal class SwapInteractorImpl @Inject constructor( return userWalletManager.getWalletAddress(networkId, derivationPath) } - private fun getTokenAddress(currency: Currency): String { + private fun getTokenAddress(currency: CryptoCurrency): String { return when (currency) { - is Currency.NativeToken -> { + is CryptoCurrency.Coin -> { DEFAULT_BLOCKCHAIN_INCH_ADDRESS } - is Currency.NonNativeToken -> { + is CryptoCurrency.Token -> { currency.contractAddress } } @@ -777,7 +841,7 @@ internal class SwapInteractorImpl @Inject constructor( fee: BigDecimal?, spendAmount: SwapAmount, networkId: String, - fromToken: Currency, + fromToken: CryptoCurrency, ): Boolean { if (fee == null) { return false @@ -785,12 +849,12 @@ internal class SwapInteractorImpl @Inject constructor( val nativeTokenBalance = userWalletManager.getNativeTokenBalance(networkId, derivationPath) val percentsToFeeIncrease = BigDecimal.ONE return when (fromToken) { - is Currency.NativeToken -> { + is CryptoCurrency.Coin -> { nativeTokenBalance?.let { balance -> return balance.value.minus(spendAmount.value) > fee.multiply(percentsToFeeIncrease) } ?: false } - is Currency.NonNativeToken -> { + is CryptoCurrency.Token -> { nativeTokenBalance?.let { balance -> return balance.value > fee.multiply(percentsToFeeIncrease) } ?: false @@ -816,7 +880,7 @@ internal class SwapInteractorImpl @Inject constructor( private suspend fun getApproveData( networkId: String, derivationPath: String?, - fromToken: Currency, + fromToken: CryptoCurrency, swapAmount: SwapAmount? = null, ): String { return getSelectedWalletSyncUseCase().fold( diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index 3b64564a3b..ab0e4095c8 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -1,8 +1,6 @@ package com.tangem.feature.swap.domain import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.Network -import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.models.data.AggregatedSwapDataModel import com.tangem.feature.swap.domain.models.domain.* @@ -46,8 +44,6 @@ interface SwapRepository { */ fun getTangemFee(): Double - suspend fun getCryptoCurrency(userWallet: UserWallet, currency: Currency, network: Network): CryptoCurrency? - @Throws(IllegalStateException::class) suspend fun getAllowance( userWalletId: UserWalletId, @@ -62,7 +58,7 @@ interface SwapRepository { userWalletId: UserWalletId, networkId: String, derivationPath: String?, - currency: Currency, + currency: CryptoCurrency, amount: BigDecimal?, ): String } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt index 7f4bf919cc..4890b5ca49 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt @@ -2,19 +2,19 @@ package com.tangem.feature.swap.domain.cache import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.Currency -import com.tangem.feature.swap.domain.models.ui.TokenWithBalance +import com.tangem.feature.swap.domain.models.ui.TokenWithBalanceExpress import java.math.BigDecimal interface SwapDataCache { fun cacheAvailableToSwapTokens(networkId: String, tokens: List) - fun cacheInWalletTokens(tokens: List) - fun cacheLoadedTokens(tokens: List) + fun cacheInWalletTokens(tokens: List) + fun cacheLoadedTokens(tokens: List) fun cacheBalances(networkId: String, derivationPath: String?, balances: Map) fun cacheLastFeeForNetwork(fee: BigDecimal, networkId: String) fun getAvailableTokens(networkId: String): List - fun getInWalletTokens(): List - fun getLoadedTokens(): List + fun getInWalletTokens(): List + fun getLoadedTokens(): List fun getBalanceForToken(networkId: String, derivationPath: String?, symbol: String): SwapAmount? fun getLastFeeForNetwork(networkId: String): BigDecimal? } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt index 4379d70d9f..2a5d7535ca 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt @@ -2,7 +2,7 @@ package com.tangem.feature.swap.domain.cache import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.Currency -import com.tangem.feature.swap.domain.models.ui.TokenWithBalance +import com.tangem.feature.swap.domain.models.ui.TokenWithBalanceExpress import java.math.BigDecimal class SwapDataCacheImpl : SwapDataCache { @@ -10,28 +10,28 @@ class SwapDataCacheImpl : SwapDataCache { private val availableTokensForNetwork: MutableMap> = mutableMapOf() private val feesForNetworks: MutableMap = mutableMapOf() private val tokensBalances: MutableMap> = mutableMapOf() - private val lastInWalletTokens = mutableListOf() - private val lastLoadedTokens = mutableListOf() + private val lastInWalletTokens = mutableListOf() + private val lastLoadedTokens = mutableListOf() override fun cacheLastFeeForNetwork(fee: BigDecimal, networkId: String) { feesForNetworks[networkId] = fee } - override fun cacheInWalletTokens(tokens: List) { + override fun cacheInWalletTokens(tokens: List) { lastInWalletTokens.clear() lastInWalletTokens.addAll(tokens) } - override fun cacheLoadedTokens(tokens: List) { + override fun cacheLoadedTokens(tokens: List) { lastLoadedTokens.clear() lastLoadedTokens.addAll(tokens) } - override fun getInWalletTokens(): List { + override fun getInWalletTokens(): List { return lastInWalletTokens } - override fun getLoadedTokens(): List { + override fun getLoadedTokens(): List { return lastLoadedTokens } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/SwapCurrencyConverter.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/SwapCurrencyConverter.kt index d766f5029a..2cf345a0bb 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/SwapCurrencyConverter.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/converters/SwapCurrencyConverter.kt @@ -1,54 +1,29 @@ package com.tangem.feature.swap.domain.converters -import com.tangem.feature.swap.domain.models.domain.Currency -import com.tangem.utils.converter.TwoWayConverter -import com.tangem.lib.crypto.models.Currency as CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.lib.crypto.models.Currency +import com.tangem.utils.converter.Converter -class SwapCurrencyConverter : TwoWayConverter { +class SwapCurrencyConverter : Converter { - override fun convert(value: Currency): CryptoCurrency { + override fun convert(value: CryptoCurrency): Currency { return when (value) { - is Currency.NonNativeToken -> { - CryptoCurrency.NonNativeToken( - id = value.id, - name = value.name, - symbol = value.symbol, - networkId = value.networkId, - contractAddress = value.contractAddress, - decimalCount = value.decimalCount, - ) - } - is Currency.NativeToken -> { - CryptoCurrency.NativeToken( - id = value.id, - name = value.name, - symbol = value.symbol, - networkId = value.networkId, - ) - } - } - } - - override fun convertBack(value: CryptoCurrency): Currency { - return when (value) { - is CryptoCurrency.NonNativeToken -> { + is CryptoCurrency.Token -> { Currency.NonNativeToken( - id = value.id, + id = value.id.value, name = value.name, symbol = value.symbol, - networkId = value.networkId, + networkId = value.network.id.value, contractAddress = value.contractAddress, - decimalCount = value.decimalCount, - logoUrl = "", + decimalCount = value.decimals, ) } - is CryptoCurrency.NativeToken -> { + is CryptoCurrency.Coin -> { Currency.NativeToken( - id = value.id, + id = value.id.value, name = value.name, symbol = value.symbol, - networkId = value.networkId, - logoUrl = "", + networkId = value.network.id.value, ) } } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index bba56b76e7..73e8bf1a5d 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -1,8 +1,11 @@ package com.tangem.feature.swap.domain.di +import com.tangem.domain.tokens.GetCardTokensListUseCase import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase +import com.tangem.domain.tokens.GetCryptoCurrencyStatusUseCase 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.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.feature.swap.domain.* @@ -10,6 +13,7 @@ import com.tangem.feature.swap.domain.cache.SwapDataCacheImpl import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -31,7 +35,8 @@ class SwapDomainModule { networksRepository: NetworksRepository, walletFeatureToggles: WalletFeatureToggles, @SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, - @SwapScope getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase, + @SwapScope getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusUseCase, + @SwapScope getCardTokensListUseCase: GetCardTokensListUseCase, ): SwapInteractor { return SwapInteractorImpl( transactionManager = transactionManager, @@ -43,14 +48,15 @@ class SwapDomainModule { networksRepository = networksRepository, walletFeatureToggles = walletFeatureToggles, getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - getCryptoCurrenciesUseCase = getCryptoCurrenciesUseCase, + getMultiCryptoCurrencyStatusUseCase = getCryptoCurrencyStatusUseCase, + getCardTokensListUseCase = getCardTokensListUseCase, ) } @Provides @Singleton fun provideBlockchainInteractor(transactionManager: TransactionManager): BlockchainInteractor { - return BlockchainInteractorImpl( + return DefaultBlockchainInteractor( transactionManager = transactionManager, ) } @@ -68,6 +74,40 @@ class SwapDomainModule { fun providesGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase { return GetCryptoCurrenciesUseCase(currenciesRepository = currenciesRepository) } + + @SwapScope + @Provides + @Singleton + fun providesGetCryptoCurrencyStatusUseCase( + currenciesRepository: CurrenciesRepository, + quotesRepository: QuotesRepository, + networksRepository: NetworksRepository, + dispatchers: CoroutineDispatcherProvider, + ): GetCryptoCurrencyStatusUseCase { + return GetCryptoCurrencyStatusUseCase( + currenciesRepository = currenciesRepository, + quotesRepository = quotesRepository, + networksRepository = networksRepository, + dispatchers = dispatchers, + ) + } + + @SwapScope + @Provides + @Singleton + fun providesGetCardTokensListUseCase( + currenciesRepository: CurrenciesRepository, + quotesRepository: QuotesRepository, + networksRepository: NetworksRepository, + dispatchers: CoroutineDispatcherProvider, + ): GetCardTokensListUseCase { + return GetCardTokensListUseCase( + currenciesRepository = currenciesRepository, + quotesRepository = quotesRepository, + networksRepository = networksRepository, + dispatchers = dispatchers, + ) + } } @Qualifier diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PermissionOptions.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PermissionOptions.kt index 7c0f6812fb..88d57a301f 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PermissionOptions.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PermissionOptions.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.domain.models.domain +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData import com.tangem.feature.swap.domain.models.ui.TxFee @@ -15,7 +16,7 @@ import com.tangem.feature.swap.domain.models.ui.TxFee data class PermissionOptions( val approveData: RequestApproveStateData, val forTokenContractAddress: String, - val fromToken: Currency, + val fromToken: CryptoCurrency, val approveType: SwapApproveType, val txFee: TxFee, ) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TokensDataStateExpress.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TokensDataStateExpress.kt new file mode 100644 index 0000000000..e4909bfb51 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TokensDataStateExpress.kt @@ -0,0 +1,31 @@ +package com.tangem.feature.swap.domain.models.ui + +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.feature.swap.domain.models.domain.SwapPair + +data class TokensDataStateExpress( + val initialCryptoCurrency: CryptoCurrency, + val preselectTokens: PreselectTokensExpress, + val foundTokensState: FoundTokensStateExpress, + val pairs: List, +) + +data class FoundTokensStateExpress( + val tokensInWallet: List, + val loadedTokens: List, +) + +data class PreselectTokensExpress( + val fromToken: CryptoCurrency, + val toToken: CryptoCurrency, +) + +data class TokenWithBalanceExpress( + val token: CryptoCurrency, + val tokenBalanceData: TokenBalanceDataExpress? = null, +) + +data class TokenBalanceDataExpress( + val amount: String?, + val amountEquivalent: String?, +) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt index ef3b9c7ac3..bb52e2b01d 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt @@ -2,10 +2,10 @@ package com.tangem.feature.swap.converters import com.tangem.common.Provider import com.tangem.core.ui.components.currency.tokenicon.TokenIconState -import com.tangem.feature.swap.domain.models.domain.Currency +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.feature.swap.domain.models.domain.NetworkInfo -import com.tangem.feature.swap.domain.models.ui.FoundTokensState -import com.tangem.feature.swap.domain.models.ui.TokenWithBalance +import com.tangem.feature.swap.domain.models.ui.FoundTokensStateExpress +import com.tangem.feature.swap.domain.models.ui.TokenWithBalanceExpress import com.tangem.feature.swap.models.Network import com.tangem.feature.swap.models.SwapSelectTokenStateHolder import com.tangem.feature.swap.models.TokenBalanceData @@ -18,7 +18,7 @@ class TokensDataConverter( private val isBalanceHiddenProvider: Provider, ) { - fun convertWithNetwork(value: FoundTokensState, network: NetworkInfo): SwapSelectTokenStateHolder { + fun convertWithNetwork(value: FoundTokensStateExpress, network: NetworkInfo): SwapSelectTokenStateHolder { return SwapSelectTokenStateHolder( availableTokens = value.tokensInWallet.map { tokenWithBalanceToTokenToSelect(it) }.toImmutableList(), unavailableTokens = value.loadedTokens.map { tokenWithBalanceToTokenToSelect(it) }.toImmutableList(), @@ -28,12 +28,14 @@ class TokensDataConverter( ) } - private fun tokenWithBalanceToTokenToSelect(tokenWithBalance: TokenWithBalance): TokenToSelectState.TokenToSelect { + private fun tokenWithBalanceToTokenToSelect( + tokenWithBalance: TokenWithBalanceExpress, + ): TokenToSelectState.TokenToSelect { return TokenToSelectState.TokenToSelect( - id = tokenWithBalance.token.id, + id = tokenWithBalance.token.id.value, name = tokenWithBalance.token.name, symbol = tokenWithBalance.token.symbol, - isNative = tokenWithBalance.token is Currency.NativeToken, + isNative = tokenWithBalance.token is CryptoCurrency.Coin, // todo replace converting tokenIcon = TokenIconState.CoinIcon( url = "", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index 1fc4583726..31688a00c0 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -37,7 +37,7 @@ data class SwapCardData( val amountEquivalent: String?, val coinId: String?, val amountTextFieldValue: TextFieldValue?, - val tokenIconUrl: String, + val tokenIconUrl: String?, val tokenCurrency: String, val balance: String, val isBalanceHidden: Boolean, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index af9fc07c61..9f591cd1c8 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -6,6 +6,7 @@ import com.tangem.common.Provider import com.tangem.core.ui.components.states.Item import com.tangem.core.ui.components.states.SelectableItemsState import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.feature.swap.converters.TokensDataConverter import com.tangem.feature.swap.domain.models.DataError import com.tangem.feature.swap.domain.models.domain.Currency @@ -73,21 +74,21 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider: fun createQuotesLoadingState( uiStateHolder: SwapStateHolder, - fromToken: Currency, - toToken: Currency, + fromToken: CryptoCurrency, + toToken: CryptoCurrency, mainTokenId: String, ): SwapStateHolder { - val canSelectSendToken = mainTokenId != fromToken.id - val canSelectReceiveToken = mainTokenId != toToken.id + val canSelectSendToken = mainTokenId != fromToken.id.value // TODO look at id matching + val canSelectReceiveToken = mainTokenId != toToken.id.value // TODO look at id matching return uiStateHolder.copy( sendCardData = SwapCardData( type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard), amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue, amountEquivalent = null, - tokenIconUrl = fromToken.logoUrl, + tokenIconUrl = fromToken.iconUrl, tokenCurrency = fromToken.symbol, - coinId = fromToken.id, - isNotNativeToken = fromToken.isNonNative(), + coinId = fromToken.id.value, + isNotNativeToken = fromToken is CryptoCurrency.Token, canSelectAnotherToken = canSelectSendToken, balance = if (!canSelectSendToken) uiStateHolder.sendCardData.balance else "", isBalanceHidden = isBalanceHiddenProvider(), @@ -96,10 +97,10 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider: type = TransactionCardType.ReceiveCard(), amountTextFieldValue = null, amountEquivalent = null, - tokenIconUrl = toToken.logoUrl, + tokenIconUrl = toToken.iconUrl, tokenCurrency = toToken.symbol, - coinId = toToken.id, - isNotNativeToken = toToken.isNonNative(), + coinId = toToken.id.value, + isNotNativeToken = toToken is CryptoCurrency.Token, canSelectAnotherToken = canSelectReceiveToken, balance = if (!canSelectReceiveToken) uiStateHolder.receiveCardData.balance else "", isBalanceHidden = isBalanceHiddenProvider(), @@ -123,7 +124,7 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider: fun createQuotesLoadedState( uiStateHolder: SwapStateHolder, quoteModel: SwapState.QuotesLoadedState, - fromToken: Currency, + fromToken: CryptoCurrency, onFeeSetup: (TxFee) -> Unit, ): SwapStateHolder { val warnings = mutableListOf() @@ -238,7 +239,7 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider: fun addTokensToState( uiState: SwapStateHolder, - dataState: FoundTokensState, + dataState: FoundTokensStateExpress, networkInfo: NetworkInfo, ): SwapStateHolder { return uiState.copy( diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 703f7bb82c..b3c9b23c76 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -151,7 +151,7 @@ private fun MainInfo(state: SwapStateHolder) { }, textFieldValue = state.sendCardData.amountTextFieldValue, amountEquivalent = state.sendCardData.amountEquivalent, - tokenIconUrl = state.sendCardData.tokenIconUrl, + tokenIconUrl = state.sendCardData.tokenIconUrl ?: "", tokenCurrency = state.sendCardData.tokenCurrency, priceImpact = priceImpactWarning, networkIconRes = if (state.sendCardData.isNotNativeToken) networkIconRes else null, @@ -169,7 +169,7 @@ private fun MainInfo(state: SwapStateHolder) { balance = if (state.receiveCardData.isBalanceHidden) STARS else state.receiveCardData.balance, textFieldValue = state.receiveCardData.amountTextFieldValue, amountEquivalent = state.receiveCardData.amountEquivalent, - tokenIconUrl = state.receiveCardData.tokenIconUrl, + tokenIconUrl = state.receiveCardData.tokenIconUrl ?: "", tokenCurrency = state.receiveCardData.tokenCurrency, priceImpact = priceImpactWarning, networkIconRes = if (state.receiveCardData.isNotNativeToken) networkIconRes else null, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 71c151673d..1da348c56c 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -8,6 +8,7 @@ import com.tangem.common.Provider import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.utils.InputNumberFormatter import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.feature.swap.analytics.SwapEvents import com.tangem.feature.swap.domain.BlockchainInteractor @@ -51,10 +52,14 @@ internal class SwapViewModel @Inject constructor( savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver { + // try to get rid of this and use only CryptoCurrency private val currency = Json.decodeFromString( savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY] ?: error("no expected parameter Currency found"), ) + + private var cryptoCurrency: CryptoCurrency by Delegates.notNull() + private val derivationPath = savedStateHandle.get(SwapFragment.DERIVATION_PATH) private val network = savedStateHandle.get(SwapFragment.NETWORK) @@ -132,8 +137,21 @@ internal class SwapViewModel @Inject constructor( // new flow viewModelScope.launch(dispatchers.main) { runCatching(dispatchers.io) { - val pairs = swapInteractor.getPairs(currency) - // TODO + swapInteractor.getTokensDataState(currency) + }.onSuccess { state -> + dataState = dataState.copy( + fromCryptoCurrency = state.preselectTokens.fromToken, + toCryptoCurrency = state.preselectTokens.toToken, + ) + cryptoCurrency = state.initialCryptoCurrency + // updateTokensState(dataState = state.foundTokensState) + // startLoadingQuotes( + // fromToken = state.preselectTokens.fromToken, + // toToken = state.preselectTokens.toToken, + // amount = lastAmount.value, + // ) + }.onFailure { + Timber.tag(loggingTag).e(it) } } @@ -143,24 +161,24 @@ internal class SwapViewModel @Inject constructor( swapInteractor.initTokensToSwap(currency) } .onSuccess { state -> - dataState = dataState.copy( - fromCurrency = state.preselectTokens.fromToken, - toCurrency = state.preselectTokens.toToken, - ) - updateTokensState(dataState = state.foundTokensState) - startLoadingQuotes( - fromToken = state.preselectTokens.fromToken, - toToken = state.preselectTokens.toToken, - amount = lastAmount.value, - ) + // dataState = dataState.copy( + // fromCurrency = state.preselectTokens.fromToken, + // toCurrency = state.preselectTokens.toToken, + // ) + // updateTokensState(dataState = state.foundTokensState) + // startLoadingQuotes( + // fromToken = state.preselectTokens.fromToken, + // toToken = state.preselectTokens.toToken, + // amount = lastAmount.value, + // ) } .onFailure { - Timber.e(it) + Timber.tag(loggingTag).e(it) } } } - private fun updateTokensState(dataState: FoundTokensState) { + private fun updateTokensState(dataState: FoundTokensStateExpress) { uiState = stateBuilder.addTokensToState( uiState = uiState, dataState = dataState, @@ -168,9 +186,9 @@ internal class SwapViewModel @Inject constructor( ) } - private fun startLoadingQuotes(fromToken: Currency, toToken: Currency, amount: String) { + private fun startLoadingQuotes(fromToken: CryptoCurrency, toToken: CryptoCurrency, amount: String) { singleTaskScheduler.cancelTask() - uiState = stateBuilder.createQuotesLoadingState(uiState, fromToken, toToken, currency.id) + uiState = stateBuilder.createQuotesLoadingState(uiState, fromToken, toToken, cryptoCurrency.id.value) singleTaskScheduler.scheduleTask( viewModelScope, loadQuotesTask( @@ -182,15 +200,19 @@ internal class SwapViewModel @Inject constructor( } private fun startLoadingQuotesFromLastState() { - val fromCurrency = dataState.fromCurrency - val toCurrency = dataState.toCurrency + val fromCurrency = dataState.fromCryptoCurrency + val toCurrency = dataState.toCryptoCurrency val amount = dataState.amount if (fromCurrency != null && toCurrency != null && amount != null) { startLoadingQuotes(fromCurrency, toCurrency, amount) } } - private fun loadQuotesTask(fromToken: Currency, toToken: Currency, amount: String): PeriodicTask { + private fun loadQuotesTask( + fromToken: CryptoCurrency, + toToken: CryptoCurrency, + amount: String, + ): PeriodicTask { return PeriodicTask( UPDATE_DELAY, task = { @@ -264,8 +286,8 @@ internal class SwapViewModel @Inject constructor( swapInteractor.onSwap( networkId = dataState.networkId, swapStateData = requireNotNull(dataState.swapDataModel), - currencyToSend = requireNotNull(dataState.fromCurrency), - currencyToGet = requireNotNull(dataState.toCurrency), + currencyToSend = requireNotNull(dataState.fromCryptoCurrency), + currencyToGet = requireNotNull(dataState.toCryptoCurrency), amountToSwap = requireNotNull(dataState.amount), fee = requireNotNull(dataState.selectedFee), ) @@ -314,9 +336,9 @@ internal class SwapViewModel @Inject constructor( approveData = requireNotNull(dataState.approveDataModel) { "dataState.approveDataModel might not be null" }, - forTokenContractAddress = (dataState.fromCurrency as? Currency.NonNativeToken)?.contractAddress + forTokenContractAddress = (dataState.fromCryptoCurrency as? CryptoCurrency.Token)?.contractAddress ?: "", - fromToken = requireNotNull(dataState.fromCurrency) { + fromToken = requireNotNull(dataState.fromCryptoCurrency) { "dataState.fromCurrency might not be null" }, approveType = requireNotNull(uiState.permissionState as? SwapPermissionState.ReadyForRequest) { @@ -367,18 +389,18 @@ internal class SwapViewModel @Inject constructor( ) if (foundToken != null) { - val fromToken: Currency - val toToken: Currency + val fromToken: CryptoCurrency + val toToken: CryptoCurrency if (isOrderReversed) { fromToken = foundToken - toToken = currency + toToken = cryptoCurrency } else { - fromToken = currency + fromToken = cryptoCurrency toToken = foundToken } dataState = dataState.copy( - fromCurrency = fromToken, - toCurrency = toToken, + fromCryptoCurrency = fromToken, + toCryptoCurrency = toToken, ) startLoadingQuotes(fromToken, toToken, lastAmount.value) swapRouter.openScreen(SwapNavScreen.Main) @@ -386,12 +408,12 @@ internal class SwapViewModel @Inject constructor( } private fun onChangeCardsClicked() { - val newFromToken = dataState.toCurrency - val newToToken = dataState.fromCurrency + val newFromToken = dataState.toCryptoCurrency + val newToToken = dataState.fromCryptoCurrency if (newFromToken != null && newToToken != null) { dataState = dataState.copy( - fromCurrency = newFromToken, - toCurrency = newToToken, + fromCryptoCurrency = newFromToken, + toCryptoCurrency = newToToken, ) isOrderReversed = !isOrderReversed val decimals = blockchainInteractor.getTokenDecimals(newFromToken) @@ -405,8 +427,8 @@ internal class SwapViewModel @Inject constructor( } private fun onAmountChanged(value: String) { - val fromToken = dataState.fromCurrency - val toToken = dataState.toCurrency + val fromToken = dataState.fromCryptoCurrency + val toToken = dataState.toCryptoCurrency if (fromToken != null && toToken != null) { val decimals = blockchainInteractor.getTokenDecimals(fromToken) val cutValue = cutAmountWithDecimals(decimals, value) @@ -420,8 +442,8 @@ internal class SwapViewModel @Inject constructor( } private fun onMaxAmountClicked() { - dataState.fromCurrency?.let { - val balance = swapInteractor.getTokenBalance(currency.networkId, it) + dataState.fromCryptoCurrency?.let { + val balance = swapInteractor.getTokenBalance(cryptoCurrency.network.id.value, it) onAmountChanged(balance.formatToUIRepresentation()) } } @@ -496,11 +518,11 @@ internal class SwapViewModel @Inject constructor( onSelectItemFee = { feeItem -> dataState = dataState.copy(selectedFee = feeItem.data) val spendAmount = dataState.amount?.let { amount -> - val fromToken = dataState.fromCurrency ?: return@let null + val fromToken = dataState.fromCryptoCurrency ?: return@let null swapInteractor.getSwapAmountForToken(amount, fromToken) } ?: dataState.approveDataModel?.fromTokenAmount spendAmount ?: return@UiActions - val fromToken = dataState.fromCurrency ?: return@UiActions + val fromToken = dataState.fromCryptoCurrency ?: return@UiActions viewModelScope.launch(dispatchers.io) { val isFeeEnough = swapInteractor.checkFeeIsEnough( fee = feeItem.data.feeValue, @@ -517,6 +539,7 @@ internal class SwapViewModel @Inject constructor( } companion object { + private const val loggingTag = "SwapViewModel" private const val INITIAL_AMOUNT = "" private const val UPDATE_DELAY = 10000L private const val DEBOUNCE_AMOUNT_DELAY = 1000L