From 9489035904e7c164248ce8d59171490f32adceec Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Nov 2024 15:00:16 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/common/routing/AppRoute.kt | 14 ++- .../impl/model/TokenActionsHandler.kt | 2 +- .../swap/model/SwapSelectTokensModel.kt | 16 +++- features/swap/presentation/build.gradle.kts | 1 + .../tangem/feature/swap/ui/StateBuilder.kt | 28 +++--- .../feature/swap/viewmodels/SwapViewModel.kt | 92 ++++++++++++------- .../viewmodels/TokenDetailsViewModel.kt | 2 +- .../WalletCurrencyActionsClickIntents.kt | 2 +- 8 files changed, 104 insertions(+), 53 deletions(-) diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index d69c5ed969..f2903ad378 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -219,16 +219,24 @@ sealed class AppRoute(val path: String) : Route { @Serializable data class Swap( - val currency: CryptoCurrency, + val currencyFrom: CryptoCurrency, + val currencyTo: CryptoCurrency? = null, val userWalletId: UserWalletId, val isInitialReverseOrder: Boolean = false, - ) : AppRoute(path = "/swap/${currency.id.value}/${userWalletId.stringValue}/$isInitialReverseOrder"), + ) : AppRoute( + path = "/swap" + + "/${currencyFrom.id.value}" + + "/${currencyTo?.id?.value}" + + "/${userWalletId.stringValue}" + + "/$isInitialReverseOrder", + ), RouteBundleParams { override fun getBundle(): Bundle = bundle(serializer()) companion object { - const val CURRENCY_BUNDLE_KEY = "currency" + const val CURRENCY_FROM_KEY = "currencyFrom" + const val CURRENCY_TO_KEY = "currencyTo" const val USER_WALLET_ID_KEY = "userWalletId" const val IS_INITIAL_REVERSE_ORDER = "isInitialReverseOrder" } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt index 8d7bc12e6c..2333f6b48f 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt @@ -149,7 +149,7 @@ internal class TokenActionsHandler @AssistedInject constructor( private fun onExchangeClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) { router.push( AppRoute.Swap( - currency = cryptoCurrencyData.status.currency, + currencyFrom = cryptoCurrencyData.status.currency, userWalletId = cryptoCurrencyData.userWallet.walletId, isInitialReverseOrder = true, ), diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt index a5208ac04a..40a3b14a91 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt @@ -1,8 +1,12 @@ package com.tangem.features.onramp.swap.model +import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.onramp.component.SwapSelectTokensComponent import com.tangem.features.onramp.swap.entity.SwapSelectTokensController import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM import com.tangem.features.onramp.swap.entity.transformer.RemoveSelectedFromTokenTransformer @@ -15,8 +19,10 @@ import javax.inject.Inject @Suppress("LongParameterList") internal class SwapSelectTokensModel @Inject constructor( + paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, private val controller: SwapSelectTokensController, + private val router: Router, ) : Model() { val state: StateFlow = controller.state @@ -26,6 +32,8 @@ internal class SwapSelectTokensModel @Inject constructor( private val _fromCurrencyStatus = MutableStateFlow(value = null) private val _toCurrencyStatus = MutableStateFlow(value = null) + private val params = paramsContainer.require() + /** * Select "from" token * @@ -54,7 +62,13 @@ internal class SwapSelectTokensModel @Inject constructor( controller.update(transformer = SelectToTokenTransformer(selectedTokenItemState)) - // TODO: [REDACTED_JIRA] + router.push( + route = AppRoute.Swap( + currencyFrom = requireNotNull(fromCurrencyStatus.value).currency, + currencyTo = status.currency, + userWalletId = params.userWalletId, + ), + ) } private fun onRemoveClick() { diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index 3b0dbe2d6d..84cbe75297 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { implementation(projects.features.swap.domain) implementation(projects.features.swap.domain.api) implementation(projects.features.swap.domain.models) + implementation(projects.features.wallet.api) /** AndroidX */ implementation(deps.androidx.activity.compose) 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 c270a8d837..6f3ae57e47 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 @@ -63,9 +63,13 @@ internal class StateBuilder( appCurrencyProvider = appCurrencyProvider, ) - fun createInitialLoadingState(initialCurrency: CryptoCurrency, networkInfo: NetworkInfo): SwapStateHolder { + fun createInitialLoadingState( + initialCurrencyFrom: CryptoCurrency, + initialCurrencyTo: CryptoCurrency?, + fromNetworkInfo: NetworkInfo, + ): SwapStateHolder { return SwapStateHolder( - blockchainId = networkInfo.blockchainId, + blockchainId = fromNetworkInfo.blockchainId, sendCardData = SwapCardState.SwapCardData( type = TransactionCardType.Inputtable( onAmountChanged = actions.onAmountChanged, @@ -75,27 +79,27 @@ internal class StateBuilder( amountEquivalent = null, amountTextFieldValue = null, token = null, - tokenIconUrl = initialCurrency.iconUrl, - tokenCurrency = initialCurrency.symbol, - coinId = initialCurrency.network.backendId, + tokenIconUrl = initialCurrencyFrom.iconUrl, + tokenCurrency = initialCurrencyFrom.symbol, + coinId = initialCurrencyFrom.network.backendId, canSelectAnotherToken = false, - isNotNativeToken = initialCurrency is CryptoCurrency.Token, + isNotNativeToken = initialCurrencyFrom is CryptoCurrency.Token, balance = "", - networkIconRes = getActiveIconRes(initialCurrency.network.id.value), + networkIconRes = getActiveIconRes(initialCurrencyFrom.network.id.value), isBalanceHidden = true, ), receiveCardData = SwapCardState.SwapCardData( type = TransactionCardType.ReadOnly(), amountEquivalent = null, - tokenIconUrl = "", - tokenCurrency = "", + tokenIconUrl = initialCurrencyTo?.iconUrl, + tokenCurrency = initialCurrencyTo?.symbol ?: "", token = null, amountTextFieldValue = null, canSelectAnotherToken = false, balance = "", - isNotNativeToken = false, - networkIconRes = null, - coinId = null, + isNotNativeToken = initialCurrencyTo is CryptoCurrency.Token, + networkIconRes = initialCurrencyTo?.let { getActiveIconRes(it.network.id.value) }, + coinId = initialCurrencyTo?.network?.backendId, isBalanceHidden = true, ), fee = FeeItemState.Empty, 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 c4854ddb29..ed003efb7c 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 @@ -49,6 +49,7 @@ import com.tangem.feature.swap.router.SwapNavScreen import com.tangem.feature.swap.router.SwapRouter import com.tangem.feature.swap.ui.StateBuilder import com.tangem.feature.swap.utils.formatToUIRepresentation +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.utils.Provider import com.tangem.utils.coroutines.* import com.tangem.utils.isNullOrZero @@ -83,13 +84,26 @@ internal class SwapViewModel @Inject constructor( private val getCardInfoUseCase: GetCardInfoUseCase, private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, + private val walletFeatureToggles: WalletFeatureToggles, swapInteractorFactory: SwapInteractor.Factory, - savedStateHandle: SavedStateHandle, + private val savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver { - private val initialCryptoCurrency: CryptoCurrency = savedStateHandle.get(AppRoute.Swap.CURRENCY_BUNDLE_KEY) - ?.unbundle(CryptoCurrency.serializer()) - ?: error("no expected parameter CryptoCurrency found") + private val initialCurrencyFrom: CryptoCurrency + get() { + return savedStateHandle.get(AppRoute.Swap.CURRENCY_FROM_KEY) + ?.unbundle(CryptoCurrency.serializer()) + ?: error("no expected parameter CryptoCurrency (from) found") + } + + private val initialCurrencyTo: CryptoCurrency? + get() = if (walletFeatureToggles.isMainActionButtonsEnabled) { + savedStateHandle.get(AppRoute.Swap.CURRENCY_TO_KEY) + ?.unbundle(CryptoCurrency.serializer()) + ?: error("no expected parameter CryptoCurrency (to) found") + } else { + null + } private val userWalletId: UserWalletId = savedStateHandle.get(AppRoute.Swap.USER_WALLET_ID_KEY) ?.unbundle(UserWalletId.serializer()) @@ -100,7 +114,8 @@ internal class SwapViewModel @Inject constructor( private val swapInteractor = swapInteractorFactory.create(userWalletId) - private lateinit var initialCryptoCurrencyStatus: CryptoCurrencyStatus + private lateinit var initialFromStatus: CryptoCurrencyStatus + private var initialToStatus: CryptoCurrencyStatus? = null private var userWallet: UserWallet by Delegates.notNull() private var isBalanceHidden = true @@ -122,8 +137,9 @@ internal class SwapViewModel @Inject constructor( var uiState: SwapStateHolder by mutableStateOf( stateBuilder.createInitialLoadingState( - initialCurrency = initialCryptoCurrency, - networkInfo = blockchainInteractor.getBlockchainInfo(initialCryptoCurrency.network.backendId), + initialCurrencyFrom = initialCurrencyFrom, + initialCurrencyTo = initialCurrencyTo, + fromNetworkInfo = blockchainInteractor.getBlockchainInfo(initialCurrencyFrom.network.backendId), ), ) private set @@ -151,14 +167,16 @@ internal class SwapViewModel @Inject constructor( init { viewModelScope.launch(dispatchers.io) { - val cryptoCurrencyStatus = - getCryptoCurrencyStatusUseCase(userWalletId, initialCryptoCurrency.id).getOrNull() + val fromStatus = getCryptoCurrencyStatusUseCase(userWalletId, initialCurrencyFrom.id).getOrNull() + val toStatus = initialCurrencyTo?.let { getCryptoCurrencyStatusUseCase(userWalletId, it.id).getOrNull() } val wallet = getUserWalletUseCase(userWalletId).getOrNull() - if (cryptoCurrencyStatus == null || wallet == null) { + val isStatusToNull = walletFeatureToggles.isMainActionButtonsEnabled && toStatus == null + if (fromStatus == null || wallet == null || isStatusToNull) { uiState = stateBuilder.addAlert(uiState = uiState, onDismiss = swapRouter::back) } else { userWallet = wallet - initialCryptoCurrencyStatus = cryptoCurrencyStatus + initialFromStatus = fromStatus + initialToStatus = toStatus initTokens(isInitiallyReversed) } } @@ -179,7 +197,7 @@ internal class SwapViewModel @Inject constructor( } fun onScreenOpened() { - analyticsEventHandler.send(SwapEvents.SwapScreenOpened(initialCryptoCurrency.symbol)) + analyticsEventHandler.send(SwapEvents.SwapScreenOpened(initialCurrencyFrom.symbol)) } fun setRouter(router: SwapRouter) { @@ -205,14 +223,19 @@ internal class SwapViewModel @Inject constructor( private fun initTokens(isReverseFromTo: Boolean) { viewModelScope.launch(dispatchers.main) { runCatching(dispatchers.io) { - swapInteractor.getTokensDataState(initialCryptoCurrency) + swapInteractor.getTokensDataState(initialCurrencyFrom) }.onSuccess { state -> updateTokensState(state) - val selectedCurrency = swapInteractor.getInitialCurrencyToSwap( - initialCryptoCurrency = initialCryptoCurrency, - state = state, - isReverseFromTo = isReverseFromTo, - ) + val selectedCurrency = if (walletFeatureToggles.isMainActionButtonsEnabled) { + initialToStatus + } else { + swapInteractor.getInitialCurrencyToSwap( + initialCryptoCurrency = initialCurrencyFrom, + state = state, + isReverseFromTo = isReverseFromTo, + ) + } + applyInitialTokenChoice( state = state, selectedCurrency = selectedCurrency, @@ -248,9 +271,10 @@ internal class SwapViewModel @Inject constructor( (it as? ExpressException)?.expressDataError?.code ?: ExpressDataError.UnknownError.code, ) { uiState = stateBuilder.createInitialLoadingState( - initialCurrency = initialCryptoCurrency, - networkInfo = blockchainInteractor.getBlockchainInfo( - initialCryptoCurrency.network.backendId, + initialCurrencyFrom = initialCurrencyFrom, + initialCurrencyTo = initialCurrencyTo, + fromNetworkInfo = blockchainInteractor.getBlockchainInfo( + initialCurrencyFrom.network.backendId, ), ) initTokens(isReverseFromTo) @@ -269,15 +293,15 @@ internal class SwapViewModel @Inject constructor( analyticsEventHandler.send(SwapEvents.NoticeNoAvailableTokensToSwap) uiState = stateBuilder.createNoAvailableTokensToSwapState( uiStateHolder = uiState, - fromToken = initialCryptoCurrencyStatus, + fromToken = initialFromStatus, ) return } isOrderReversed = isReverseFromTo val (fromCurrencyStatus, toCurrencyStatus) = if (isOrderReversed) { - selectedCurrency to initialCryptoCurrencyStatus + selectedCurrency to initialFromStatus } else { - initialCryptoCurrencyStatus to selectedCurrency + initialFromStatus to selectedCurrency } dataState = dataState.copy( fromCryptoCurrency = fromCurrencyStatus, @@ -297,7 +321,7 @@ internal class SwapViewModel @Inject constructor( uiState = stateBuilder.addTokensToState( uiState = uiState, tokensDataState = tokensDataState, - fromToken = dataState.fromCryptoCurrency?.currency ?: initialCryptoCurrency, + fromToken = dataState.fromCryptoCurrency?.currency ?: initialCurrencyFrom, ) } @@ -314,7 +338,7 @@ internal class SwapViewModel @Inject constructor( uiState, fromToken.currency, toToken.currency, - initialCryptoCurrency.id.value, + initialCurrencyFrom.id.value, ) } singleTaskScheduler.scheduleTask( @@ -410,7 +434,7 @@ internal class SwapViewModel @Inject constructor( if (uiState.warnings.any { it is SwapWarning.UnableToCoverFeeWarning }) { analyticsEventHandler.send( SwapEvents.NoticeNotEnoughFee( - token = initialCryptoCurrency.symbol, + token = initialCurrencyFrom.symbol, blockchain = fromToken.currency.network.name, ), ) @@ -448,7 +472,7 @@ internal class SwapViewModel @Inject constructor( } analyticsEventHandler.send( SwapEvents.NoticeProviderError( - sendToken = "${initialCryptoCurrency.network.backendId}:${initialCryptoCurrency.symbol}", + sendToken = "${initialCurrencyFrom.network.backendId}:${initialCurrencyFrom.symbol}", receiveToken = receiveToken ?: "", provider = provider, errorCode = error.code, @@ -586,7 +610,7 @@ internal class SwapViewModel @Inject constructor( swapRouter.openUrl(url) } analyticsEventHandler.send( - event = SwapEvents.ButtonExplore(initialCryptoCurrency.symbol), + event = SwapEvents.ButtonExplore(initialCurrencyFrom.symbol), ) }, onStatusClick = { @@ -594,7 +618,7 @@ internal class SwapViewModel @Inject constructor( if (!txExternalUrl.isNullOrBlank()) { swapRouter.openUrl(txExternalUrl) analyticsEventHandler.send( - event = SwapEvents.ButtonStatus(initialCryptoCurrency.symbol), + event = SwapEvents.ButtonStatus(initialCurrencyFrom.symbol), ) } }, @@ -765,7 +789,7 @@ internal class SwapViewModel @Inject constructor( val toToken: CryptoCurrencyStatus if (isOrderReversed) { fromToken = foundToken.currencyStatus - toToken = initialCryptoCurrencyStatus + toToken = initialFromStatus val newToken = fromToken.currency as? CryptoCurrency.Coin if (newToken != null) { @@ -776,7 +800,7 @@ internal class SwapViewModel @Inject constructor( ) } } else { - fromToken = initialCryptoCurrencyStatus + fromToken = initialFromStatus toToken = foundToken.currencyStatus val newToken = toToken.currency as? CryptoCurrency.Coin @@ -1264,7 +1288,7 @@ internal class SwapViewModel @Inject constructor( private fun onFailedTxEmailClick(errorMessage: String) { viewModelScope.launch { - val network = initialCryptoCurrencyStatus.currency.network + val network = initialFromStatus.currency.network val cardInfo = getCardInfoUseCase(userWallet.scanResponse).getOrElse { error("CardInfo must be not null") } saveBlockchainErrorUseCase( @@ -1273,7 +1297,7 @@ internal class SwapViewModel @Inject constructor( blockchainId = network.id.value, derivationPath = network.derivationPath.value, destinationAddress = dataState.swapDataModel?.transaction?.txTo.orEmpty(), - tokenSymbol = initialCryptoCurrency.symbol, + tokenSymbol = initialCurrencyFrom.symbol, amount = dataState.amount.orEmpty(), fee = dataState.selectedFee?.feeCryptoFormatted.orEmpty(), ), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index b648664a77..c07f123af4 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -621,7 +621,7 @@ internal class TokenDetailsViewModel @Inject constructor( if (handleUnavailabilityReason(unavailabilityReason)) return - appRouter.push(AppRoute.Swap(currency = cryptoCurrency, userWalletId = userWalletId)) + appRouter.push(AppRoute.Swap(currencyFrom = cryptoCurrency, userWalletId = userWalletId)) } override fun onDismissDialog() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 1a4283bbd6..23d45eab8b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -380,7 +380,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( appRouter.push( AppRoute.Swap( - currency = cryptoCurrencyStatus.currency, + currencyFrom = cryptoCurrencyStatus.currency, userWalletId = userWalletId, ), )