Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-17 17:32:35 +02:00
commit bb5275e21e
7 changed files with 61 additions and 153 deletions

View file

@ -9,11 +9,8 @@ import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.tap.common.analytics.events.AnalyticsParam
@ -25,7 +22,6 @@ import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.tokens.getIconUrl
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.send.redux.PrepareSendScreen
@ -42,7 +38,6 @@ import com.tangem.tap.store
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import com.tangem.feature.swap.domain.models.domain.Currency as SwapCurrency
@Suppress("LargeClass")
class TradeCryptoMiddleware {
@ -57,17 +52,12 @@ class TradeCryptoMiddleware {
is TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action)
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
is TradeCryptoAction.Swap -> {
openSwap(
currency = store.state.walletState.selectedWalletData?.currency?.toSwapCurrency(),
derivationPath = store.state.walletState.selectedWalletData?.currency?.derivationPath,
)
// todo remove old flow
}
is TradeCryptoAction.New.Buy -> proceedNewBuyAction(state, action)
is TradeCryptoAction.New.Sell -> proceedNewSellAction(action)
is TradeCryptoAction.New.Swap -> openSwap(
currency = action.cryptoCurrency.toSwapCurrency(),
derivationPath = action.cryptoCurrency.network.derivationPath.value,
network = action.cryptoCurrency.network,
currency = action.cryptoCurrency,
)
is TradeCryptoAction.New.SendToken -> handleNewSendToken(action = action)
is TradeCryptoAction.New.SendCoin -> handleNewSendCoin(action = action)
@ -267,70 +257,14 @@ class TradeCryptoMiddleware {
)?.let { store.dispatchOpenUrl(it) }
}
private fun openSwap(currency: SwapCurrency?, derivationPath: String?, network: Network? = null) {
private fun openSwap(currency: CryptoCurrency) {
val bundle = bundleOf(
SwapFragment.CURRENCY_BUNDLE_KEY to Json.encodeToString(currency),
SwapFragment.DERIVATION_PATH to derivationPath,
SwapFragment.NETWORK to network,
)
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Swap, bundle = bundle))
}
private fun CryptoCurrency.toSwapCurrency(): SwapCurrency {
val blockchain = Blockchain.fromId(network.id.value)
return when (this) {
is CryptoCurrency.Coin -> {
SwapCurrency.NativeToken(
id = blockchain.toCoinId(),
name = name,
symbol = symbol,
networkId = blockchain.toNetworkId(),
// no need to set logoUrl for blockchain cause
// error when form url with coinId, coinId of eth and arbitrum the same
logoUrl = "",
)
}
is CryptoCurrency.Token -> {
SwapCurrency.NonNativeToken(
id = id.rawCurrencyId ?: "",
name = name,
symbol = symbol,
networkId = blockchain.toNetworkId(),
logoUrl = getIconUrl(id.rawCurrencyId ?: ""),
contractAddress = contractAddress,
decimalCount = decimals,
)
}
}
}
private fun Currency.toSwapCurrency(): SwapCurrency {
return when (this) {
is Currency.Blockchain -> {
SwapCurrency.NativeToken(
id = blockchain.toCoinId(),
name = this.currencyName,
symbol = this.currencySymbol,
networkId = this.blockchain.toNetworkId(),
// no need to set logoUrl for blockchain cause
// error when form url with coinId, coinId of eth and arbitrum the same
logoUrl = "",
)
}
is Currency.Token -> SwapCurrency.NonNativeToken(
id = this.token.id ?: "",
name = this.currencyName,
symbol = this.currencySymbol,
networkId = this.blockchain.toNetworkId(),
logoUrl = getIconUrl(this.token.id ?: ""),
contractAddress = this.token.contractAddress,
decimalCount = decimals,
)
}
}
private fun handleNewSendToken(action: TradeCryptoAction.New.SendToken) {
val currency = action.tokenCurrency
val blockchain = Blockchain.fromId(currency.network.id.value)

View file

@ -9,9 +9,9 @@ import java.math.BigDecimal
interface SwapInteractor {
suspend fun getTokensDataState(currency: Currency): TokensDataStateExpress
suspend fun getTokensDataState(currency: CryptoCurrency): TokensDataStateExpress
fun initDerivationPathAndNetwork(derivationPath: String?, network: Network?)
fun initDerivationPathAndNetwork(derivationPath: String?, network: Network)
/**
* Init tokens to swap, load tokens list available to swap for given network

View file

@ -1,7 +1,7 @@
package com.tangem.feature.swap.domain
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import arrow.core.getOrElse
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@ -52,7 +52,7 @@ internal class SwapInteractorImpl @Inject constructor(
private var derivationPath: String? = null
private var network: Network? = null
override suspend fun getTokensDataState(currency: Currency): TokensDataStateExpress {
override suspend fun getTokensDataState(currency: CryptoCurrency): TokensDataStateExpress {
val selectedWallet = getSelectedWalletSyncUseCase().fold(
ifLeft = { null },
ifRight = { it },
@ -65,36 +65,32 @@ internal class SwapInteractorImpl @Inject constructor(
.getOrElse { emptyList() }
val walletCurrencyStatusesExceptInitial = walletCurrencyStatuses.filter {
it.currency.network.backendId != currency.networkId ||
it.currency.network.backendId != currency.network.backendId ||
it.currency.getContractAddress() != currency.getContractAddress()
}
val pairsLeast = getPairs(
initialCurrency = LeastTokenInfo(
contractAddress = (currency as? Currency.NonNativeToken)?.contractAddress ?: "0",
network = currency.networkId,
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0",
network = currency.network.backendId,
),
currenciesList = walletCurrencyStatusesExceptInitial.map { it.currency },
)
val initialCryptoCurrency = mapLegacyCurrencyToCryptoCurrency(currency, walletCurrencyStatuses)
?: error("Initial crypto currency must not be null")
return TokensDataStateExpress(
initialCryptoCurrency = initialCryptoCurrency,
fromGroup = getToCurrenciesGroup(
currency = initialCryptoCurrency,
currency = currency,
leastPairs = pairsLeast,
cryptoCurrenciesList = walletCurrencyStatusesExceptInitial,
tokenInfoForFilter = { it.from },
tokenInfoForAvailable = { it.to }
tokenInfoForAvailable = { it.to },
),
toGroup = getToCurrenciesGroup(
currency = initialCryptoCurrency,
currency = currency,
leastPairs = pairsLeast,
cryptoCurrenciesList = walletCurrencyStatusesExceptInitial,
tokenInfoForFilter = { it.to },
tokenInfoForAvailable = { it.from }
tokenInfoForAvailable = { it.from },
),
)
}
@ -107,8 +103,8 @@ internal class SwapInteractorImpl @Inject constructor(
tokenInfoForAvailable: (SwapPairLeast) -> LeastTokenInfo,
): CurrenciesGroup {
val filteredPairs = leastPairs.filter {
tokenInfoForFilter(it).contractAddress == currency.getContractAddress()
&& tokenInfoForFilter(it).network == currency.network.backendId
tokenInfoForFilter(it).contractAddress == currency.getContractAddress() &&
tokenInfoForFilter(it).network == currency.network.backendId
}
val availableCryptoCurrencies = filteredPairs.mapNotNull { pair ->
@ -125,17 +121,6 @@ internal class SwapInteractorImpl @Inject constructor(
)
}
private fun mapLegacyCurrencyToCryptoCurrency(
currency: Currency,
currencies: List<CryptoCurrencyStatus>,
): CryptoCurrency? {
return currencies.map { it.currency }
.find {
it.network.backendId == currency.networkId &&
it.getContractAddress() == currency.getContractAddress()
}
}
private fun findCryptoCurrencyStatusByLeastInfo(
leastTokenInfo: LeastTokenInfo,
cryptoCurrencyStatusesList: List<CryptoCurrencyStatus>,
@ -165,7 +150,7 @@ internal class SwapInteractorImpl @Inject constructor(
}
@Deprecated("used in old swap mechanism")
override fun initDerivationPathAndNetwork(derivationPath: String?, network: Network?) {
override fun initDerivationPathAndNetwork(derivationPath: String?, network: Network) {
this.derivationPath = derivationPath
this.network = network
}

View file

@ -4,7 +4,6 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.domain.CryptoCurrencySwapInfo
data class TokensDataStateExpress(
val initialCryptoCurrency: CryptoCurrency,
val fromGroup: CurrenciesGroup,
val toGroup: CurrenciesGroup,
)

View file

@ -82,7 +82,5 @@ class SwapFragment : ComposeFragment() {
companion object {
const val CURRENCY_BUNDLE_KEY = "swap_currency"
const val DERIVATION_PATH = "DERIVATION_STYLE"
const val NETWORK = "NETWORK"
}
}

View file

@ -12,9 +12,7 @@ import com.tangem.core.ui.extensions.wrappedList
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
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
import com.tangem.feature.swap.domain.models.domain.isNonNative
import com.tangem.feature.swap.domain.models.formatToUIRepresentation
import com.tangem.feature.swap.domain.models.ui.*
import com.tangem.feature.swap.models.*
@ -34,19 +32,19 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider:
isBalanceHiddenProvider = isBalanceHiddenProvider,
)
fun createInitialLoadingState(initialCurrency: Currency, networkInfo: NetworkInfo): SwapStateHolder {
fun createInitialLoadingState(initialCurrency: CryptoCurrency, networkInfo: NetworkInfo): SwapStateHolder {
return SwapStateHolder(
networkId = initialCurrency.networkId,
networkId = initialCurrency.network.backendId,
blockchainId = networkInfo.blockchainId,
sendCardData = SwapCardData(
type = TransactionCardType.SendCard(actions.onAmountChanged, actions.onAmountSelected),
amountEquivalent = null,
amountTextFieldValue = null,
tokenIconUrl = initialCurrency.logoUrl,
tokenIconUrl = initialCurrency.iconUrl,
tokenCurrency = initialCurrency.symbol,
coinId = initialCurrency.id,
coinId = null,
canSelectAnotherToken = false,
isNotNativeToken = initialCurrency.isNonNative(),
isNotNativeToken = initialCurrency is CryptoCurrency.Token,
balance = "",
isBalanceHidden = true,
),

View file

@ -9,11 +9,9 @@ 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
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.feature.swap.domain.models.domain.PermissionOptions
import com.tangem.feature.swap.domain.models.formatToUIRepresentation
import com.tangem.feature.swap.domain.models.ui.*
@ -52,17 +50,11 @@ internal class SwapViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver {
// try to get rid of this and use only CryptoCurrency
private val currency = Json.decodeFromString<Currency>(
private val initialCryptoCurrency = Json.decodeFromString<CryptoCurrency>(
savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY]
?: error("no expected parameter Currency found"),
)
private var cryptoCurrency: CryptoCurrency by Delegates.notNull()
private val derivationPath = savedStateHandle.get<String>(SwapFragment.DERIVATION_PATH)
private val network = savedStateHandle.get<Network>(SwapFragment.NETWORK)
private var isBalanceHidden = true
private val stateBuilder = StateBuilder(
@ -75,12 +67,12 @@ internal class SwapViewModel @Inject constructor(
private val amountDebouncer = Debouncer()
private val singleTaskScheduler = SingleTaskScheduler<SwapState>()
private var dataState by mutableStateOf(SwapProcessDataState(networkId = currency.networkId))
private var dataState by mutableStateOf(SwapProcessDataState(networkId = initialCryptoCurrency.network.backendId))
var uiState: SwapStateHolder by mutableStateOf(
stateBuilder.createInitialLoadingState(
initialCurrency = currency,
networkInfo = blockchainInteractor.getBlockchainInfo(currency.networkId),
initialCurrency = initialCryptoCurrency,
networkInfo = blockchainInteractor.getBlockchainInfo(initialCryptoCurrency.network.backendId),
),
)
private set
@ -93,8 +85,11 @@ internal class SwapViewModel @Inject constructor(
get() = swapRouter.currentScreen
init {
swapInteractor.initDerivationPathAndNetwork(derivationPath, network)
initTokens(currency)
swapInteractor.initDerivationPathAndNetwork(
derivationPath = initialCryptoCurrency.network.derivationPath.value,
network = initialCryptoCurrency.network,
)
initTokens(initialCryptoCurrency)
}
override fun onCreate(owner: LifecycleOwner) {
@ -115,7 +110,7 @@ internal class SwapViewModel @Inject constructor(
}
fun onScreenOpened() {
analyticsEventHandler.send(SwapEvents.SwapScreenOpened(currency.symbol))
analyticsEventHandler.send(SwapEvents.SwapScreenOpened(initialCryptoCurrency.symbol))
}
fun setRouter(router: SwapRouter) {
@ -133,21 +128,20 @@ internal class SwapViewModel @Inject constructor(
}
@Suppress("UnusedPrivateMember")
private fun initTokens(currency: Currency) {
private fun initTokens(currency: CryptoCurrency) {
// new flow
viewModelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) {
swapInteractor.getTokensDataState(currency)
swapInteractor.getTokensDataState(initialCryptoCurrency)
}.onSuccess { state ->
dataState = dataState.copy(
fromCryptoCurrency = state.initialCryptoCurrency,
fromCryptoCurrency = initialCryptoCurrency,
toCryptoCurrency = state.toGroup.available.first().currencyStatus.currency
)
cryptoCurrency = state.initialCryptoCurrency
// updateTokensState(dataState = state.foundTokensState)
startLoadingQuotes(
fromToken = state.initialCryptoCurrency,
fromToken = initialCryptoCurrency,
toToken = state.toGroup.available.first().currencyStatus.currency,
amount = lastAmount.value,
)
@ -157,39 +151,39 @@ internal class SwapViewModel @Inject constructor(
}
// 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)
}
}
// 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 updateTokensState(dataState: FoundTokensStateExpress) {
uiState = stateBuilder.addTokensToState(
uiState = uiState,
dataState = dataState,
networkInfo = blockchainInteractor.getBlockchainInfo(currency.networkId),
networkInfo = blockchainInteractor.getBlockchainInfo(initialCryptoCurrency.network.backendId),
)
}
private fun startLoadingQuotes(fromToken: CryptoCurrency, toToken: CryptoCurrency, amount: String) {
singleTaskScheduler.cancelTask()
uiState = stateBuilder.createQuotesLoadingState(uiState, fromToken, toToken, cryptoCurrency.id.value)
uiState = stateBuilder.createQuotesLoadingState(uiState, fromToken, toToken, initialCryptoCurrency.id.value)
singleTaskScheduler.scheduleTask(
viewModelScope,
loadQuotesTask(
@ -395,9 +389,9 @@ internal class SwapViewModel @Inject constructor(
val toToken: CryptoCurrency
if (isOrderReversed) {
fromToken = foundToken
toToken = cryptoCurrency
toToken = initialCryptoCurrency
} else {
fromToken = cryptoCurrency
fromToken = initialCryptoCurrency
toToken = foundToken
}
dataState = dataState.copy(
@ -445,7 +439,7 @@ internal class SwapViewModel @Inject constructor(
private fun onMaxAmountClicked() {
dataState.fromCryptoCurrency?.let {
val balance = swapInteractor.getTokenBalance(cryptoCurrency.network.id.value, it)
val balance = swapInteractor.getTokenBalance(initialCryptoCurrency.network.id.value, it)
onAmountChanged(balance.formatToUIRepresentation())
}
}