From 537543636bceda85f3743b46907ad0ef7abdbb3f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Nov 2023 18:49:21 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../src/main/res/drawable/ic_no_token_44.xml | 10 ++ .../repository/DefaultQuotesRepository.kt | 8 ++ .../GetCryptoCurrencyStatusSyncUseCase.kt | 36 +++++ .../CurrenciesStatusesOperations.kt | 22 +++ .../tokens/repository/QuotesRepository.kt | 2 + .../swap/domain/BlockchainInteractor.kt | 3 - .../domain/DefaultBlockchainInteractor.kt | 9 -- .../feature/swap/domain/SwapInteractor.kt | 3 + .../feature/swap/domain/SwapInteractorImpl.kt | 5 + .../swap/domain/di/SwapDomainModule.kt | 8 -- features/swap/presentation/build.gradle.kts | 4 + .../swap/converters/TokensDataConverter.kt | 8 +- .../feature/swap/di/SwapPresentationModule.kt | 34 +++++ .../feature/swap/models/SwapStateHolder.kt | 38 ++++-- .../tangem/feature/swap/ui/StateBuilder.kt | 93 +++++++++++-- .../feature/swap/ui/SwapScreenContent.kt | 93 ++++++++----- .../tangem/feature/swap/ui/TransactionCard.kt | 65 ++++++++- .../swap/viewmodels/SwapProcessDataState.kt | 6 +- .../feature/swap/viewmodels/SwapViewModel.kt | 129 ++++++++++-------- 19 files changed, 431 insertions(+), 145 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_no_token_44.xml create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/di/SwapPresentationModule.kt diff --git a/core/ui/src/main/res/drawable/ic_no_token_44.xml b/core/ui/src/main/res/drawable/ic_no_token_44.xml new file mode 100644 index 0000000000..49e3b75b2a --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_no_token_44.xml @@ -0,0 +1,10 @@ + + + diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt index cdd04c62ce..7aa81fc78c 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt @@ -67,6 +67,14 @@ internal class DefaultQuotesRepository( } } + override suspend fun getQuoteSync(currencyId: CryptoCurrency.ID): Quote { + return withContext(dispatchers.io) { + val quote = quotesStore.getSync(setOf(currencyId)).firstOrNull() + requireNotNull(quote) { "Unable to get quote for $currencyId" } + quotesConverter.convert(quote) + } + } + private suspend fun fetchExpiredQuotes( currenciesIds: Set, appCurrencyId: String, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt new file mode 100644 index 0000000000..ffe6e79c3b --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt @@ -0,0 +1,36 @@ +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.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations +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 + +class GetCryptoCurrencyStatusSyncUseCase( + internal val currenciesRepository: CurrenciesRepository, + internal val quotesRepository: QuotesRepository, + internal val networksRepository: NetworksRepository, + internal val dispatchers: CoroutineDispatcherProvider, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + ): Either { + val operations = CurrenciesStatusesOperations( + userWalletId = userWalletId, + currenciesRepository = currenciesRepository, + quotesRepository = quotesRepository, + networksRepository = networksRepository, + ) + + return operations.getCurrencyStatusSync(cryptoCurrencyId) + .mapLeft { error -> error.mapToTokenListError() } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index 3ce6112296..a86698763b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -85,6 +85,28 @@ internal class CurrenciesStatusesOperations( } } + suspend fun getCurrencyStatusSync(cryptoCurrencyId: CryptoCurrency.ID): Either { + return either { + catch( + block = { + val currency = + currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, cryptoCurrencyId) + val quotes = quotesRepository.getQuoteSync(cryptoCurrencyId).right() + val networkStatuses = + networksRepository.getNetworkStatusesSync( + userWalletId, + setOf(currency.network), + false, + ).firstOrNull { + it.network == currency.network + }.right() + return createCurrencyStatus(currency, quotes, networkStatuses) + }, + catch = { raise(Error.DataError(it)) }, + ) + } + } + fun getCardCurrenciesStatusesFlow(): Flow>> { return flow { val nonEmptyCurrencies = recover( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt index a44b1e6c99..4c38eda580 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt @@ -29,4 +29,6 @@ interface QuotesRepository { * @return A [Flow] emitting a set of quotes corresponding to the specified cryptocurrencies. */ suspend fun getQuotesSync(currenciesIds: Set, refresh: Boolean): Set + + suspend fun getQuoteSync(currencyId: CryptoCurrency.ID): Quote } \ No newline at end of file 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 229911c654..8883e0f1a3 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,12 +1,9 @@ package com.tangem.feature.swap.domain -import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.feature.swap.domain.models.domain.NetworkInfo interface BlockchainInteractor { - 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/DefaultBlockchainInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/DefaultBlockchainInteractor.kt index cb99b58076..836f193fe3 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/DefaultBlockchainInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/DefaultBlockchainInteractor.kt @@ -1,6 +1,5 @@ package com.tangem.feature.swap.domain -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 @@ -22,12 +21,4 @@ internal class DefaultBlockchainInteractor @Inject constructor( override fun getExplorerTransactionLink(networkId: String, txAddress: String): String { return transactionManager.getExplorerTransactionLink(networkId, txAddress) } - - override fun getTokenDecimals(token: CryptoCurrency): Int { - return if (token is CryptoCurrency.Token) { - token.decimals - } else { - transactionManager.getNativeTokenDecimals(token.network.backendId) - } - } } \ 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 2757583249..073c2464d8 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 @@ -2,6 +2,7 @@ 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.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.swap.domain.models.ui.* @@ -110,4 +111,6 @@ interface SwapInteractor { networkId: String, fromToken: CryptoCurrency, ): Boolean + + fun getSelectedWallet(): UserWallet? } \ 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 ec089f6df7..1cfcebfb20 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 @@ -8,6 +8,7 @@ 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 @@ -93,6 +94,10 @@ internal class SwapInteractorImpl @Inject constructor( ) } + override fun getSelectedWallet(): UserWallet? { + return getSelectedWalletSyncUseCase().getOrNull() + } + private fun getToCurrenciesGroup( currency: CryptoCurrency, leastPairs: List, 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 7fa93f1c70..8845c68949 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,7 +1,6 @@ package com.tangem.feature.swap.domain.di import com.tangem.domain.tokens.GetCardTokensListUseCase -import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository @@ -66,13 +65,6 @@ class SwapDomainModule { return GetSelectedWalletSyncUseCase(walletsStateHolder = walletsStateHolder) } - @SwapScope - @Provides - @Singleton - fun providesGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase { - return GetCryptoCurrenciesUseCase(currenciesRepository = currenciesRepository) - } - @SwapScope @Provides @Singleton diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index 8be06eaf61..86879ce609 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -21,6 +21,10 @@ dependencies { implementation(projects.domain.appCurrency.models) implementation(projects.domain.balanceHiding) implementation(projects.domain.balanceHiding.models) + implementation(projects.domain.tokens) + implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets) + implementation(projects.domain.wallets.models) /** AndroidX */ implementation(deps.androidx.activity.compose) 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 e3eff35d4c..6f42bbd1d2 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 @@ -32,13 +32,17 @@ class TokensDataConverter( availableTokens = value.available.map { tokenWithBalanceToTokenToSelect(it, true) } .toMutableList() .apply { - this.add(0, availableTitle) + if (this.isNotEmpty()) { + this.add(0, availableTitle) + } } .toImmutableList(), unavailableTokens = value.unavailable.map { tokenWithBalanceToTokenToSelect(it, false) } .toMutableList() .apply { - this.add(0, unavailableTitle) + if (this.isNotEmpty()) { + this.add(0, unavailableTitle) + } } .toImmutableList(), onSearchEntered = onSearchEntered, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/SwapPresentationModule.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/SwapPresentationModule.kt new file mode 100644 index 0000000000..441abf4a2e --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/SwapPresentationModule.kt @@ -0,0 +1,34 @@ +package com.tangem.feature.swap.di + +import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.feature.swap.domain.* +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.android.scopes.ViewModelScoped + +@Module +@InstallIn(ViewModelComponent::class) +class SwapPresentationModule { + + @ViewModelScoped + @Provides + fun providesGetCryptoCurrenciesUseCase( + currenciesRepository: CurrenciesRepository, + dispatcherProvider: CoroutineDispatcherProvider, + quotesRepository: QuotesRepository, + networksRepository: NetworksRepository, + ): GetCryptoCurrencyStatusSyncUseCase { + return GetCryptoCurrencyStatusSyncUseCase( + currenciesRepository = currenciesRepository, + quotesRepository = quotesRepository, + networksRepository = networksRepository, + dispatchers = dispatcherProvider, + ) + } +} \ No newline at end of file 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 ace9ee54d5..6427891e34 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 @@ -7,8 +7,8 @@ import com.tangem.core.ui.components.states.SelectableItemsState import com.tangem.feature.swap.domain.models.ui.TxFee data class SwapStateHolder( - val sendCardData: SwapCardData, - val receiveCardData: SwapCardData, + val sendCardData: SwapCardState, + val receiveCardData: SwapCardState, val networkCurrency: String, val networkId: String, val blockchainId: String, // not the same as networkId, its local id in app @@ -33,18 +33,28 @@ data class SwapStateHolder( val onCancelPermissionBottomSheet: () -> Unit = {}, ) -data class SwapCardData( - val type: TransactionCardType, - val amountEquivalent: String?, - val coinId: String?, - val amountTextFieldValue: TextFieldValue?, - val tokenIconUrl: String?, - val tokenCurrency: String, - val balance: String, - val isBalanceHidden: Boolean, - val isNotNativeToken: Boolean, - val canSelectAnotherToken: Boolean = false, -) +sealed class SwapCardState { + + data class SwapCardData( + val type: TransactionCardType, + val amountEquivalent: String?, + val coinId: String?, + val amountTextFieldValue: TextFieldValue?, + val tokenIconUrl: String?, + val tokenCurrency: String, + val balance: String, + val isBalanceHidden: Boolean, + val isNotNativeToken: Boolean, + val canSelectAnotherToken: Boolean = false, + ) : SwapCardState() + + data class Empty( + val type: TransactionCardType, + val amountEquivalent: String?, + val amountTextFieldValue: TextFieldValue?, + val canSelectAnotherToken: Boolean = false, + ) : SwapCardState() +} data class SwapButton( val enabled: 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 510502e088..2b1c60f67a 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 @@ -8,9 +8,12 @@ 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.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.swap.converters.TokensDataConverter import com.tangem.feature.swap.domain.models.DataError import com.tangem.feature.swap.domain.models.domain.NetworkInfo @@ -28,7 +31,7 @@ import kotlinx.collections.immutable.toImmutableList internal class StateBuilder( private val actions: UiActions, private val isBalanceHiddenProvider: Provider, - appCurrencyProvider: Provider, + private val appCurrencyProvider: Provider, ) { private val tokensDataConverter = TokensDataConverter( @@ -42,7 +45,7 @@ internal class StateBuilder( return SwapStateHolder( networkId = initialCurrency.network.backendId, blockchainId = networkInfo.blockchainId, - sendCardData = SwapCardData( + sendCardData = SwapCardState.SwapCardData( type = TransactionCardType.SendCard(actions.onAmountChanged, actions.onAmountSelected), amountEquivalent = null, amountTextFieldValue = null, @@ -54,7 +57,7 @@ internal class StateBuilder( balance = "", isBalanceHidden = true, ), - receiveCardData = SwapCardData( + receiveCardData = SwapCardState.SwapCardData( type = TransactionCardType.ReceiveCard(), amountEquivalent = null, tokenIconUrl = "", @@ -79,6 +82,53 @@ internal class StateBuilder( ) } + fun createNoAvailableTokensToSwapState( + uiStateHolder: SwapStateHolder, + fromToken: CryptoCurrencyStatus, + ): SwapStateHolder { + if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder + return uiStateHolder.copy( + sendCardData = SwapCardState.SwapCardData( + type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard), + amountTextFieldValue = TextFieldValue( + text = "0", + ), + amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}", + tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl, + coinId = uiStateHolder.sendCardData.coinId, + isNotNativeToken = uiStateHolder.sendCardData.isNotNativeToken, + tokenCurrency = uiStateHolder.sendCardData.tokenCurrency, + canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken, + balance = fromToken.getFormattedAmount(), + isBalanceHidden = isBalanceHiddenProvider(), + ), + receiveCardData = SwapCardState.Empty( + type = TransactionCardType.ReceiveCard(), + amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}", + amountTextFieldValue = TextFieldValue( + text = "0", + ), + canSelectAnotherToken = true, + ), + warnings = listOf( + SwapWarning.NoAvailableTokensToSwap( + notificationConfig = NotificationConfig( + title = stringReference("No tokens"), + subtitle = stringReference("Swap tokens not available"), + iconResId = R.drawable.ic_alert_24, + ), + ), + ), + fee = FeeState.Empty, + swapButton = SwapButton( + enabled = false, + loading = false, + onClick = { }, + ), + updateInProgress = false, + ) + } + fun createQuotesLoadingState( uiStateHolder: SwapStateHolder, fromToken: CryptoCurrency, @@ -87,8 +137,10 @@ internal class StateBuilder( ): SwapStateHolder { val canSelectSendToken = mainTokenId != fromToken.id.value // TODO look at id matching val canSelectReceiveToken = mainTokenId != toToken.id.value // TODO look at id matching + if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder + if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder return uiStateHolder.copy( - sendCardData = SwapCardData( + sendCardData = SwapCardState.SwapCardData( type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard), amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue, amountEquivalent = null, @@ -100,7 +152,7 @@ internal class StateBuilder( balance = if (!canSelectSendToken) uiStateHolder.sendCardData.balance else "", isBalanceHidden = isBalanceHiddenProvider(), ), - receiveCardData = SwapCardData( + receiveCardData = SwapCardState.SwapCardData( type = TransactionCardType.ReceiveCard(), amountTextFieldValue = null, amountEquivalent = null, @@ -128,12 +180,15 @@ internal class StateBuilder( * @param onFeeSetup callback for reset fee after auto update * @return updated whole screen state */ + @Suppress("LongMethod") fun createQuotesLoadedState( uiStateHolder: SwapStateHolder, quoteModel: SwapState.QuotesLoadedState, fromToken: CryptoCurrency, onFeeSetup: (TxFee) -> Unit, ): SwapStateHolder { + if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder + if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder val warnings = mutableListOf() if (!quoteModel.preparedSwapConfigState.isAllowedToSpend && quoteModel.preparedSwapConfigState.isFeeEnough && @@ -159,7 +214,7 @@ internal class StateBuilder( } val feeState = createFeeState(quoteModel, uiStateHolder, onFeeSetup) return uiStateHolder.copy( - sendCardData = SwapCardData( + sendCardData = SwapCardState.SwapCardData( type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard), amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue, amountEquivalent = quoteModel.fromTokenInfo.tokenFiatBalance, @@ -171,7 +226,7 @@ internal class StateBuilder( balance = quoteModel.fromTokenInfo.tokenWalletBalance, isBalanceHidden = isBalanceHiddenProvider(), ), - receiveCardData = SwapCardData( + receiveCardData = SwapCardState.SwapCardData( type = TransactionCardType.ReceiveCard(), amountTextFieldValue = TextFieldValue(quoteModel.toTokenInfo.tokenAmount.formatToUIRepresentation()), amountEquivalent = quoteModel.toTokenInfo.tokenFiatBalance, @@ -208,8 +263,10 @@ internal class StateBuilder( uiStateHolder: SwapStateHolder, emptyAmountState: SwapState.EmptyAmountState, ): SwapStateHolder { + if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder + if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder return uiStateHolder.copy( - sendCardData = SwapCardData( + sendCardData = SwapCardState.SwapCardData( type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard), amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue, amountEquivalent = emptyAmountState.zeroAmountEquivalent, @@ -221,7 +278,7 @@ internal class StateBuilder( balance = emptyAmountState.fromTokenWalletBalance, isBalanceHidden = isBalanceHiddenProvider(), ), - receiveCardData = SwapCardData( + receiveCardData = SwapCardState.SwapCardData( type = TransactionCardType.ReceiveCard(), amountTextFieldValue = TextFieldValue("0"), amountEquivalent = emptyAmountState.zeroAmountEquivalent, @@ -268,6 +325,7 @@ internal class StateBuilder( } fun updateSwapAmount(uiState: SwapStateHolder, amount: String): SwapStateHolder { + if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState return uiState.copy( sendCardData = uiState.sendCardData.copy( amountTextFieldValue = TextFieldValue( @@ -279,6 +337,8 @@ internal class StateBuilder( } fun updateBalanceHiddenState(uiState: SwapStateHolder, isBalanceHidden: Boolean): SwapStateHolder { + if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState + if (uiState.receiveCardData !is SwapCardState.SwapCardData) return uiState val patchedSendCardData = uiState.sendCardData.copy( isBalanceHidden = isBalanceHidden, ) @@ -666,11 +726,26 @@ internal class StateBuilder( return "$firstAddressPart...$secondAddressPart" } + private fun CryptoCurrencyStatus.getFormattedAmount(): String { + val amount = value.amount ?: return UNKNOWN_AMOUNT_SIGN + + return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals) + } + + @Suppress("UnusedPrivateMember") + private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String { + val fiatAmount = value.fiatAmount ?: return UNKNOWN_AMOUNT_SIGN + val appCurrency = appCurrencyProvider() + + return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol) + } + private companion object { const val ADDRESS_MIN_LENGTH = 11 const val ADDRESS_FIRST_PART_LENGTH = 7 const val ADDRESS_SECOND_PART_LENGTH = 4 private const val PRICE_IMPACT_THRESHOLD = 0.1 private const val HUNDRED_PERCENTS = 100 + private const val UNKNOWN_AMOUNT_SIGN = "—" } } \ No newline at end of file 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 1ea66c1e90..153e3cd1b4 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 @@ -145,48 +145,24 @@ private fun MainInfo(state: SwapStateHolder) { } val (topCard, bottomCard, button) = createRefs() val priceImpactWarning = state.warnings.filterIsInstance().firstOrNull() - TransactionCard( - type = state.sendCardData.type, - balance = if (state.sendCardData.isBalanceHidden) { - STARS - } else { - state.sendCardData.balance - }, - textFieldValue = state.sendCardData.amountTextFieldValue, - amountEquivalent = state.sendCardData.amountEquivalent, - tokenIconUrl = state.sendCardData.tokenIconUrl ?: "", - tokenCurrency = state.sendCardData.tokenCurrency, - priceImpact = priceImpactWarning, - networkIconRes = if (state.sendCardData.isNotNativeToken) networkIconRes else null, - iconPlaceholder = state.sendCardData.coinId?.let { - getActiveIconResByCoinId(it) - }, - onChangeTokenClick = if (state.sendCardData.canSelectAnotherToken) state.onSelectTokenClick else null, + TransactionCardData( + priceImpactWarning = priceImpactWarning, + networkIconRes = networkIconRes, + swapCardState = state.sendCardData, modifier = Modifier.constrainAs(topCard) { top.linkTo(parent.top) }, + onSelectTokenClick = state.onSelectTokenClick, ) val marginCard = TangemTheme.dimens.spacing16 - TransactionCard( - type = state.receiveCardData.type, - balance = if (state.receiveCardData.isBalanceHidden) STARS else state.receiveCardData.balance, - textFieldValue = state.receiveCardData.amountTextFieldValue, - amountEquivalent = state.receiveCardData.amountEquivalent, - tokenIconUrl = state.receiveCardData.tokenIconUrl ?: "", - tokenCurrency = state.receiveCardData.tokenCurrency, - priceImpact = priceImpactWarning, - networkIconRes = if (state.receiveCardData.isNotNativeToken) networkIconRes else null, - iconPlaceholder = state.receiveCardData.coinId?.let { - getActiveIconResByCoinId(it) - }, - onChangeTokenClick = if (state.receiveCardData.canSelectAnotherToken) { - state.onSelectTokenClick - } else { - null - }, + TransactionCardData( + priceImpactWarning = priceImpactWarning, + networkIconRes = networkIconRes, + swapCardState = state.receiveCardData, modifier = Modifier.constrainAs(bottomCard) { top.linkTo(topCard.bottom, margin = marginCard) }, + onSelectTokenClick = state.onSelectTokenClick, ) val marginButton = TangemTheme.dimens.spacing32 SwapButton( @@ -200,6 +176,48 @@ private fun MainInfo(state: SwapStateHolder) { } } +@Composable +private fun TransactionCardData( + priceImpactWarning: SwapWarning.HighPriceImpact?, + networkIconRes: Int?, + swapCardState: SwapCardState, + onSelectTokenClick: (() -> Unit)?, + modifier: Modifier = Modifier, +) { + when (swapCardState) { + is SwapCardState.Empty -> { + TransactionCardEmpty( + type = swapCardState.type, + amountEquivalent = swapCardState.amountEquivalent, + textFieldValue = swapCardState.amountTextFieldValue, + onChangeTokenClick = if (swapCardState.canSelectAnotherToken) onSelectTokenClick else null, + modifier = modifier, + ) + } + is SwapCardState.SwapCardData -> { + TransactionCard( + type = swapCardState.type, + balance = if (swapCardState.isBalanceHidden) { + STARS + } else { + swapCardState.balance + }, + textFieldValue = swapCardState.amountTextFieldValue, + amountEquivalent = swapCardState.amountEquivalent, + tokenIconUrl = swapCardState.tokenIconUrl ?: "", + tokenCurrency = swapCardState.tokenCurrency, + priceImpact = priceImpactWarning, + networkIconRes = if (swapCardState.isNotNativeToken) networkIconRes else null, + iconPlaceholder = swapCardState.coinId?.let { + getActiveIconResByCoinId(it) + }, + onChangeTokenClick = if (swapCardState.canSelectAnotherToken) onSelectTokenClick else null, + modifier = modifier, + ) + } + } +} + @OptIn(ExperimentalMaterialApi::class) @Composable private fun SwapButton(state: SwapStateHolder, modifier: Modifier = Modifier) { @@ -268,7 +286,8 @@ private fun FeeItem(feeState: FeeState, currency: String) { } } is FeeState.Empty -> { - SmallInfoCard(startText = titleString, endText = "") + // show nothing + // SmallInfoCard(startText = titleString, endText = "") } } } @@ -355,7 +374,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U // region preview -private val sendCard = SwapCardData( +private val sendCard = SwapCardState.SwapCardData( type = TransactionCardType.SendCard({}) {}, amountTextFieldValue = TextFieldValue(), amountEquivalent = "1 000 000", @@ -368,7 +387,7 @@ private val sendCard = SwapCardData( isBalanceHidden = false, ) -private val receiveCard = SwapCardData( +private val receiveCard = SwapCardState.SwapCardData( type = TransactionCardType.ReceiveCard(), amountTextFieldValue = TextFieldValue(), amountEquivalent = "1 000 000", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index 3cbd35a23f..d5996c36a5 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -68,7 +68,7 @@ fun TransactionCard( verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.Start, ) { - Header(balance = balance, type = type) + Header(balance = stringResource(R.string.common_balance, balance), type = type) Content( type = type, @@ -106,6 +106,67 @@ fun TransactionCard( } } +@Composable +fun TransactionCardEmpty( + type: TransactionCardType, + amountEquivalent: String?, + textFieldValue: TextFieldValue?, + modifier: Modifier = Modifier, + onChangeTokenClick: (() -> Unit)? = null, +) { + Card( + shape = RoundedCornerShape(TangemTheme.dimens.radius12), + backgroundColor = TangemTheme.colors.background.primary, + elevation = TangemTheme.dimens.elevation2, + modifier = modifier, + ) { + Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxWidth(), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start, + ) { + Header( + balance = stringResource(id = R.string.swapping_token_not_available), + type = type, + ) + + Content( + type = type, + amountEquivalent = amountEquivalent, + textFieldValue = textFieldValue, + priceImpact = null, + ) + } + + Box(modifier = Modifier.align(Alignment.BottomEnd)) { + Token( + tokenIconUrl = "", + tokenCurrency = "", + iconPlaceholder = R.drawable.ic_no_token_44, + ) + } + + if (onChangeTokenClick != null) { + Box(modifier = Modifier.align(Alignment.CenterEnd)) { + ChangeTokenSelector() + } + Box( + Modifier + .align(Alignment.CenterEnd) + .height(TangemTheme.dimens.size116) + .width(TangemTheme.dimens.size102) + .clickable( + indication = rememberRipple(bounded = false), + interactionSource = remember { MutableInteractionSource() }, + ) { onChangeTokenClick() }, + ) + } + } + } +} + @Composable private fun Header(type: TransactionCardType, balance: String, modifier: Modifier = Modifier) { Row( @@ -134,7 +195,7 @@ private fun Header(type: TransactionCardType, balance: String, modifier: Modifie SpacerW16() if (balance.isNotBlank()) { Text( - text = stringResource(R.string.common_balance, balance), + text = balance, color = TangemTheme.colors.text.tertiary, style = MaterialTheme.typography.body2, modifier = Modifier diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt index c9018a2345..9c756453d6 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt @@ -1,6 +1,6 @@ package com.tangem.feature.swap.viewmodels -import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.swap.domain.models.domain.Currency import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData import com.tangem.feature.swap.domain.models.ui.SwapStateData @@ -14,8 +14,8 @@ data class SwapProcessDataState( val fromCurrency: Currency? = null, @Deprecated("used in old swap mechanism") val toCurrency: Currency? = null, - val fromCryptoCurrency: CryptoCurrency? = null, - val toCryptoCurrency: CryptoCurrency? = null, + val fromCryptoCurrency: CryptoCurrencyStatus? = null, + val toCryptoCurrency: CryptoCurrencyStatus? = null, // Amount from input val amount: String? = null, val approveDataModel: RequestApproveStateData? = 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 0f2d531f35..68b7f39e22 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 @@ -11,7 +11,9 @@ import com.tangem.core.ui.utils.InputNumberFormatter import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase +import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.swap.analytics.SwapEvents import com.tangem.feature.swap.domain.BlockchainInteractor import com.tangem.feature.swap.domain.SwapInteractor @@ -49,11 +51,13 @@ internal class SwapViewModel @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver { private val initialCryptoCurrency: CryptoCurrency = savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY] ?: error("no expected parameter CryptoCurrency found`") + private lateinit var initialCryptoCurrencyStatus: CryptoCurrencyStatus private var isBalanceHidden = true @@ -88,11 +92,20 @@ internal class SwapViewModel @Inject constructor( get() = swapRouter.currentScreen init { - swapInteractor.initDerivationPathAndNetwork( - derivationPath = initialCryptoCurrency.network.derivationPath.value, - network = initialCryptoCurrency.network, - ) - initTokens() + viewModelScope.launch(dispatchers.io) { + swapInteractor.getSelectedWallet()?.let { + initialCryptoCurrencyStatus = + requireNotNull(getCryptoCurrencyStatusUseCase(it.walletId, initialCryptoCurrency.id).getOrNull()) { + "Failed to get initial crypto currency status" + } + + swapInteractor.initDerivationPathAndNetwork( + derivationPath = initialCryptoCurrency.network.derivationPath.value, + network = initialCryptoCurrency.network, + ) + initTokens() + } + } } override fun onCreate(owner: LifecycleOwner) { @@ -137,44 +150,38 @@ internal class SwapViewModel @Inject constructor( runCatching(dispatchers.io) { swapInteractor.getTokensDataState(initialCryptoCurrency) }.onSuccess { state -> - dataState = dataState.copy( - fromCryptoCurrency = initialCryptoCurrency, - toCryptoCurrency = state.toGroup.available.first().currencyStatus.currency, - tokensDataState = state, - ) - updateTokensState(state) - startLoadingQuotes( - fromToken = initialCryptoCurrency, - toToken = state.toGroup.available.first().currencyStatus.currency, - amount = lastAmount.value, - ) + applyInitialTokenChoice(state, selectInitialCurrencyToSwap(state)) }.onFailure { Timber.tag(loggingTag).e(it) } } + } - // old flow - // viewModelScope.launch(dispatchers.main) { - // runCatching(dispatchers.io) { - // 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, - // // ) - // } - // .onFailure { - // Timber.tag(loggingTag).e(it) - // } - // } + private fun applyInitialTokenChoice(state: TokensDataStateExpress, selectedCurrency: CryptoCurrencyStatus?) { + val fromCurrencyStatus = initialCryptoCurrencyStatus + dataState = dataState.copy( + fromCryptoCurrency = fromCurrencyStatus, + toCryptoCurrency = selectedCurrency, + tokensDataState = state, + ) + if (selectedCurrency == null) { + uiState = stateBuilder.createNoAvailableTokensToSwapState( + uiStateHolder = uiState, + fromToken = fromCurrencyStatus, + ) + } else { + startLoadingQuotes( + fromToken = fromCurrencyStatus.currency, + toToken = selectedCurrency.currency, + amount = lastAmount.value, + ) + } + } + + private fun selectInitialCurrencyToSwap(state: TokensDataStateExpress): CryptoCurrencyStatus? { + // todo add algorithm to select initial currency + return state.toGroup.available.firstOrNull()?.currencyStatus } private fun updateTokensState(dataState: TokensDataStateExpress) { @@ -203,7 +210,7 @@ internal class SwapViewModel @Inject constructor( val toCurrency = dataState.toCryptoCurrency val amount = dataState.amount if (fromCurrency != null && toCurrency != null && amount != null) { - startLoadingQuotes(fromCurrency, toCurrency, amount) + startLoadingQuotes(fromCurrency.currency, toCurrency.currency, amount) } } @@ -285,8 +292,8 @@ internal class SwapViewModel @Inject constructor( swapInteractor.onSwap( networkId = dataState.networkId, swapStateData = requireNotNull(dataState.swapDataModel), - currencyToSend = requireNotNull(dataState.fromCryptoCurrency), - currencyToGet = requireNotNull(dataState.toCryptoCurrency), + currencyToSend = requireNotNull(dataState.fromCryptoCurrency?.currency), + currencyToGet = requireNotNull(dataState.toCryptoCurrency?.currency), amountToSwap = requireNotNull(dataState.amount), fee = requireNotNull(dataState.selectedFee), ) @@ -335,10 +342,10 @@ internal class SwapViewModel @Inject constructor( approveData = requireNotNull(dataState.approveDataModel) { "dataState.approveDataModel might not be null" }, - forTokenContractAddress = (dataState.fromCryptoCurrency as? CryptoCurrency.Token) + forTokenContractAddress = (dataState.fromCryptoCurrency?.currency as? CryptoCurrency.Token) ?.contractAddress ?: "", - fromToken = requireNotNull(dataState.fromCryptoCurrency) { + fromToken = requireNotNull(dataState.fromCryptoCurrency?.currency) { "dataState.fromCurrency might not be null" }, approveType = requireNotNull(uiState.permissionState as? SwapPermissionState.ReadyForRequest) { @@ -384,28 +391,34 @@ internal class SwapViewModel @Inject constructor( private fun onTokenSelect(id: String) { val tokens = dataState.tokensDataState ?: return - val foundToken = tokens.toGroup.available.firstOrNull { - it.currencyStatus.currency.id.value == id + val foundToken = if (isOrderReversed) { + tokens.fromGroup.available.firstOrNull { + it.currencyStatus.currency.id.value == id + } + } else { + tokens.toGroup.available.firstOrNull { + it.currencyStatus.currency.id.value == id + } } analyticsEventHandler.send( event = SwapEvents.SearchTokenClicked(currencySymbol = foundToken?.currencyStatus?.currency?.symbol), ) if (foundToken != null) { - val fromToken: CryptoCurrency - val toToken: CryptoCurrency + val fromToken: CryptoCurrencyStatus + val toToken: CryptoCurrencyStatus if (isOrderReversed) { - fromToken = foundToken.currencyStatus.currency - toToken = initialCryptoCurrency + fromToken = foundToken.currencyStatus + toToken = initialCryptoCurrencyStatus } else { - fromToken = initialCryptoCurrency - toToken = foundToken.currencyStatus.currency + fromToken = initialCryptoCurrencyStatus + toToken = foundToken.currencyStatus } dataState = dataState.copy( fromCryptoCurrency = fromToken, toCryptoCurrency = toToken, ) - startLoadingQuotes(fromToken, toToken, lastAmount.value) + startLoadingQuotes(fromToken.currency, toToken.currency, lastAmount.value) swapRouter.openScreen(SwapNavScreen.Main) } } @@ -419,13 +432,13 @@ internal class SwapViewModel @Inject constructor( toCryptoCurrency = newToToken, ) isOrderReversed = !isOrderReversed - val decimals = blockchainInteractor.getTokenDecimals(newFromToken) + val decimals = newFromToken.currency.decimals lastAmount.value = cutAmountWithDecimals(decimals, lastAmount.value) uiState = stateBuilder.updateSwapAmount( uiState, inputNumberFormatter.formatWithThousands(lastAmount.value, decimals), ) - startLoadingQuotes(newFromToken, newToToken, lastAmount.value) + startLoadingQuotes(newFromToken.currency, newToToken.currency, lastAmount.value) } } @@ -433,20 +446,20 @@ internal class SwapViewModel @Inject constructor( val fromToken = dataState.fromCryptoCurrency val toToken = dataState.toCryptoCurrency if (fromToken != null && toToken != null) { - val decimals = blockchainInteractor.getTokenDecimals(fromToken) + val decimals = fromToken.currency.decimals val cutValue = cutAmountWithDecimals(decimals, value) lastAmount.value = cutValue uiState = stateBuilder.updateSwapAmount(uiState, inputNumberFormatter.formatWithThousands(cutValue, decimals)) amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY) { - startLoadingQuotes(fromToken, toToken, lastAmount.value) + startLoadingQuotes(fromToken.currency, toToken.currency, lastAmount.value) } } } private fun onMaxAmountClicked() { dataState.fromCryptoCurrency?.let { - val balance = swapInteractor.getTokenBalance(initialCryptoCurrency.network.id.value, it) + val balance = swapInteractor.getTokenBalance(initialCryptoCurrency.network.id.value, it.currency) onAmountChanged(balance.formatToUIRepresentation()) } } @@ -522,7 +535,7 @@ internal class SwapViewModel @Inject constructor( dataState = dataState.copy(selectedFee = feeItem.data) val spendAmount = dataState.amount?.let { amount -> val fromToken = dataState.fromCryptoCurrency ?: return@let null - swapInteractor.getSwapAmountForToken(amount, fromToken) + swapInteractor.getSwapAmountForToken(amount, fromToken.currency) } ?: dataState.approveDataModel?.fromTokenAmount spendAmount ?: return@UiActions val fromToken = dataState.fromCryptoCurrency ?: return@UiActions @@ -531,7 +544,7 @@ internal class SwapViewModel @Inject constructor( fee = feeItem.data.feeValue, spendAmount = spendAmount, networkId = dataState.networkId, - fromToken = fromToken, + fromToken = fromToken.currency, ) uiState = stateBuilder.updateFeeSelectedItem(uiState, feeItem, isFeeEnough) }