Updated on 2026-08-14
This commit is contained in:
parent
11b8f4f366
commit
9489035904
8 changed files with 104 additions and 53 deletions
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<SwapSelectTokensUM> = controller.state
|
||||
|
|
@ -26,6 +32,8 @@ internal class SwapSelectTokensModel @Inject constructor(
|
|||
private val _fromCurrencyStatus = MutableStateFlow<CryptoCurrencyStatus?>(value = null)
|
||||
private val _toCurrencyStatus = MutableStateFlow<CryptoCurrencyStatus?>(value = null)
|
||||
|
||||
private val params = paramsContainer.require<SwapSelectTokensComponent.Params>()
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<Bundle>(AppRoute.Swap.CURRENCY_BUNDLE_KEY)
|
||||
?.unbundle(CryptoCurrency.serializer())
|
||||
?: error("no expected parameter CryptoCurrency found")
|
||||
private val initialCurrencyFrom: CryptoCurrency
|
||||
get() {
|
||||
return savedStateHandle.get<Bundle>(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<Bundle>(AppRoute.Swap.CURRENCY_TO_KEY)
|
||||
?.unbundle(CryptoCurrency.serializer())
|
||||
?: error("no expected parameter CryptoCurrency (to) found")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private val userWalletId: UserWalletId = savedStateHandle.get<Bundle>(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(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -380,7 +380,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
appRouter.push(
|
||||
AppRoute.Swap(
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
currencyFrom = cryptoCurrencyStatus.currency,
|
||||
userWalletId = userWalletId,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue