Updated on 2026-08-14

This commit is contained in:
Tangem 2023-01-23 17:01:05 +03:00
parent 8f78031374
commit 2756dc5687
24 changed files with 843 additions and 314 deletions

View file

@ -30,6 +30,7 @@ data class SwapCardData(
val amount: String?,
val amountEquivalent: String?,
val tokenIconUrl: String,
val tokenId: String,
val tokenCurrency: String,
val balance: String,
@DrawableRes val networkIconRes: Int? = null,
@ -68,7 +69,7 @@ sealed interface TransactionCardType {
sealed interface SwapWarning {
data class PermissionNeeded(val tokenCurrency: String) : SwapWarning
data class InsufficientFunds(val tokenCurrency: String) : SwapWarning
object InsufficientFunds : SwapWarning
data class GenericWarning(val message: String?, val onClick: () -> Unit) : SwapWarning
// data class RateExpired(val onClick: () -> Unit) : SwapWarning
// object HighPriceImpact : SwapWarning

View file

@ -1,11 +1,13 @@
package com.tangem.feature.swap.ui
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.formatToUIRepresentation
import com.tangem.feature.swap.domain.models.ui.FoundTokensState
import com.tangem.feature.swap.domain.models.ui.PermissionDataState
import com.tangem.feature.swap.domain.models.ui.SwapState
import com.tangem.feature.swap.domain.models.ui.TxState
import com.tangem.feature.swap.models.ApprovePermissionButton
import com.tangem.feature.swap.models.CancelPermissionButton
import com.tangem.feature.swap.models.FeeState
@ -13,6 +15,7 @@ import com.tangem.feature.swap.models.SwapButton
import com.tangem.feature.swap.models.SwapCardData
import com.tangem.feature.swap.models.SwapPermissionState
import com.tangem.feature.swap.models.SwapStateHolder
import com.tangem.feature.swap.models.SwapSuccessStateHolder
import com.tangem.feature.swap.models.SwapWarning
import com.tangem.feature.swap.models.TransactionCardType
import com.tangem.feature.swap.models.UiActions
@ -24,14 +27,15 @@ class StateBuilder(val actions: UiActions) {
private val tokensDataConverter = TokensDataConverter(actions.onSearchEntered, actions.onTokenSelected)
fun createInitialLoadingState(networkCurrency: String): SwapStateHolder {
fun createInitialLoadingState(initialCurrency: Currency): SwapStateHolder {
return SwapStateHolder(
sendCardData = SwapCardData(
type = TransactionCardType.SendCard(actions.onAmountChanged),
amount = null,
amountEquivalent = null,
tokenIconUrl = "",
tokenCurrency = "",
tokenIconUrl = initialCurrency.logoUrl,
tokenCurrency = initialCurrency.symbol,
tokenId = initialCurrency.id,
canSelectAnotherToken = false,
balance = "",
),
@ -43,9 +47,10 @@ class StateBuilder(val actions: UiActions) {
tokenCurrency = "",
canSelectAnotherToken = false,
balance = "",
tokenId = "",
),
fee = FeeState.Loading,
networkCurrency = networkCurrency,
networkCurrency = initialCurrency.symbol,
swapButton = SwapButton(enabled = false, loading = true, onClick = {}),
onRefresh = {},
onBackClicked = actions.onBackClicked,
@ -66,6 +71,7 @@ class StateBuilder(val actions: UiActions) {
amountEquivalent = uiStateHolder.sendCardData.amountEquivalent,
tokenIconUrl = fromToken.logoUrl,
tokenCurrency = fromToken.symbol,
tokenId = fromToken.id,
canSelectAnotherToken = mainTokenId != fromToken.id,
balance = "",
),
@ -75,6 +81,7 @@ class StateBuilder(val actions: UiActions) {
amountEquivalent = null,
tokenIconUrl = toToken.logoUrl,
tokenCurrency = toToken.symbol,
tokenId = toToken.id,
canSelectAnotherToken = mainTokenId != toToken.id,
balance = "",
),
@ -89,40 +96,87 @@ class StateBuilder(val actions: UiActions) {
quoteModel: SwapState.QuotesLoadedState,
fromToken: Currency,
): SwapStateHolder {
val warnings = mutableListOf<SwapWarning>()
if (!quoteModel.preparedSwapConfigState.isAllowedToSpend && quoteModel.preparedSwapConfigState.isFeeEnough) {
warnings.add(SwapWarning.PermissionNeeded(fromToken.symbol))
}
if (!quoteModel.preparedSwapConfigState.isBalanceEnough) {
warnings.add(SwapWarning.InsufficientFunds)
}
val feeState = if (quoteModel.preparedSwapConfigState.isFeeEnough) {
FeeState.Loaded(quoteModel.fee)
} else {
FeeState.NotEnoughFundsWarning(quoteModel.fee)
}
return uiStateHolder.copy(
sendCardData = SwapCardData(
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard),
amount = quoteModel.fromTokenAmount.formatToUIRepresentation(),
amountEquivalent = quoteModel.fromTokenFiatBalance,
amount = quoteModel.fromTokenInfo.tokenAmount.formatToUIRepresentation(),
amountEquivalent = quoteModel.fromTokenInfo.tokenFiatBalance,
tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl,
tokenId = quoteModel.fromTokenInfo.tokenId,
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
balance = quoteModel.fromTokenWalletBalance,
balance = quoteModel.fromTokenInfo.tokenWalletBalance,
),
receiveCardData = SwapCardData(
type = TransactionCardType.ReceiveCard(),
amount = quoteModel.toTokenAmount.formatToUIRepresentation(),
amountEquivalent = quoteModel.toTokenFiatBalance,
amount = quoteModel.toTokenInfo.tokenAmount.formatToUIRepresentation(),
amountEquivalent = quoteModel.toTokenInfo.tokenFiatBalance,
tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl,
tokenId = quoteModel.toTokenInfo.tokenId,
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
balance = quoteModel.toTokenWalletBalance,
balance = quoteModel.toTokenInfo.tokenWalletBalance,
),
warnings = if (!quoteModel.isAllowedToSpend) {
listOf(SwapWarning.PermissionNeeded(fromToken.symbol))
} else {
emptyList()
},
networkCurrency = quoteModel.networkCurrency,
warnings = warnings,
permissionState = convertPermissionState(quoteModel.permissionState, actions.onGivePermissionClick),
fee = FeeState.Loaded(quoteModel.fee),
fee = feeState,
swapButton = SwapButton(
enabled = quoteModel.isAllowedToSpend,
enabled = quoteModel.preparedSwapConfigState.isAllowedToSpend
&& quoteModel.preparedSwapConfigState.isBalanceEnough
&& quoteModel.preparedSwapConfigState.isFeeEnough,
loading = false,
onClick = actions.onSwapClick,
),
)
}
fun createQuotesEmptyAmountState(
uiStateHolder: SwapStateHolder,
emptyAmountState: SwapState.EmptyAmountState,
): SwapStateHolder {
return uiStateHolder.copy(
sendCardData = SwapCardData(
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard),
amount = uiStateHolder.sendCardData.amount,
amountEquivalent = uiStateHolder.sendCardData.amountEquivalent,
tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl,
tokenId = uiStateHolder.sendCardData.tokenId,
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
balance = emptyAmountState.fromTokenWalletBalance,
),
receiveCardData = SwapCardData(
type = TransactionCardType.ReceiveCard(),
amount = "0",
amountEquivalent = uiStateHolder.receiveCardData.amountEquivalent,
tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl,
tokenId = uiStateHolder.receiveCardData.tokenId,
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
balance = emptyAmountState.toTokenWalletBalance,
),
fee = FeeState.Empty,
swapButton = SwapButton(
enabled = false,
loading = false,
onClick = { },
),
)
}
fun createSwapInProgressState(uiState: SwapStateHolder): SwapStateHolder {
return uiState.copy(
swapButton = uiState.swapButton.copy(
@ -164,12 +218,78 @@ class StateBuilder(val actions: UiActions) {
}
}
fun updateSwapAmount(uiState: SwapStateHolder, amount: String, amountEquivalent: String): SwapStateHolder {
fun updateSwapAmount(uiState: SwapStateHolder, amount: String): SwapStateHolder {
return uiState.copy(
sendCardData = uiState.sendCardData.copy(
amount = amount,
amountEquivalent = amountEquivalent,
),
)
}
fun loadingPermissionState(uiState: SwapStateHolder): SwapStateHolder {
return uiState.copy(
permissionState = SwapPermissionState.InProgress,
warnings = uiState.warnings.filterNot { it is SwapWarning.PermissionNeeded },
)
}
fun createSuccessState(uiState: SwapStateHolder, txState: TxState.TxSent): SwapStateHolder {
return uiState.copy(
successState = SwapSuccessStateHolder(
fromTokenAmount = txState.fromAmount ?: "",
toTokenAmount = txState.toAmount ?: "",
onSecondaryButtonClick = {},
),
)
}
fun createSwapErrorTransaction(uiState: SwapStateHolder, onAlertClick: () -> Unit): SwapStateHolder {
return uiState.copy(
swapButton = uiState.swapButton.copy(
enabled = true,
loading = false,
),
alert = SwapWarning.GenericWarning(
message = null,
onClick = onAlertClick,
),
)
}
fun addWarning(uiState: SwapStateHolder, message: String?): SwapStateHolder {
return if (message != null) {
val renewWarnings = uiState.warnings.filterNot { it is SwapWarning.GenericWarning }.toMutableList()
renewWarnings.add(SwapWarning.GenericWarning(message) {})
uiState.copy(
warnings = renewWarnings,
)
} else {
uiState
}
}
fun mapError(uiState: SwapStateHolder, error: DataError): SwapStateHolder {
return when (error) {
//todo use if needed later
// DataError.InsufficientLiquidity -> TODO()
// DataError.NoError -> TODO()
is DataError.UnknownError -> addWarning(uiState, error.message)
else -> addWarning(uiState, null)
}
}
fun addAlert(uiState: SwapStateHolder, onClick: () -> Unit): SwapStateHolder {
return uiState.copy(
alert = SwapWarning.GenericWarning(
message = null,
onClick = onClick,
),
)
}
fun clearAlert(uiState: SwapStateHolder): SwapStateHolder {
return uiState.copy(
alert = null,
)
}
}

View file

@ -38,6 +38,7 @@ import com.tangem.core.ui.components.SmallInfoCard
import com.tangem.core.ui.components.SmallInfoCardWithWarning
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.keyboardAsState
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.models.FeeState
import com.tangem.feature.swap.models.SwapButton
@ -161,6 +162,7 @@ private fun MainInfo(state: SwapStateHolder) {
tokenIconUrl = state.sendCardData.tokenIconUrl,
tokenCurrency = state.sendCardData.tokenCurrency,
networkIconRes = state.sendCardData.networkIconRes,
iconPlaceholder = getActiveIconRes(state.sendCardData.tokenId),
onChangeTokenClick = if (state.sendCardData.canSelectAnotherToken) state.onSelectTokenClick else null,
)
SpacerH16()
@ -172,6 +174,7 @@ private fun MainInfo(state: SwapStateHolder) {
tokenIconUrl = state.receiveCardData.tokenIconUrl,
tokenCurrency = state.receiveCardData.tokenCurrency,
networkIconRes = state.receiveCardData.networkIconRes,
iconPlaceholder = getActiveIconRes(state.receiveCardData.tokenId),
onChangeTokenClick = if (state.receiveCardData.canSelectAnotherToken) {
state.onSelectTokenClick
} else {
@ -228,7 +231,7 @@ private fun FeeItem(feeState: FeeState, currency: String) {
SmallInfoCardWithWarning(
startText = titleString,
endText = feeState.fee,
warningText = stringResource(id = R.string.token_details_send_blocked_fee_format, currency),
warningText = stringResource(id = R.string.token_details_send_blocked_fee_format, currency, currency),
)
}
is FeeState.Empty -> {}
@ -298,6 +301,7 @@ private val sendCard = SwapCardData(
networkIconRes = R.drawable.img_polygon_22,
canSelectAnotherToken = false,
balance = "123",
tokenId = "",
)
private val receiveCard = SwapCardData(
@ -309,6 +313,7 @@ private val receiveCard = SwapCardData(
networkIconRes = R.drawable.img_polygon_22,
canSelectAnotherToken = true,
balance = "33333",
tokenId = "",
)
private val state = SwapStateHolder(

View file

@ -74,6 +74,7 @@ fun TransactionCard(
amountEquivalent: String?,
tokenIconUrl: String,
tokenCurrency: String,
@DrawableRes iconPlaceholder: Int? = null,
@DrawableRes networkIconRes: Int? = null,
onChangeTokenClick: (() -> Unit)? = null,
) {
@ -104,6 +105,7 @@ fun TransactionCard(
tokenIconUrl = tokenIconUrl,
tokenCurrency = tokenCurrency,
networkIconRes = networkIconRes,
iconPlaceholder = iconPlaceholder,
)
}
@ -328,6 +330,7 @@ private fun AutoSizeTextField(amount: String, onAmountChange: (String) -> Unit)
fun Token(
tokenIconUrl: String,
tokenCurrency: String,
@DrawableRes iconPlaceholder: Int? = null,
@DrawableRes networkIconRes: Int? = null,
) {
Column(
@ -353,6 +356,8 @@ fun Token(
model = ImageRequest.Builder(LocalContext.current)
.data(tokenIconUrl)
.crossfade(true)
.error(iconPlaceholder ?: 0)
.placeholder(iconPlaceholder ?: 0)
.build(),
loading = { TokenImageShimmer(modifier = tokenImageModifier) },
// error = { CurrencyPlaceholderIcon(modifier = tokenImageModifier, tokenCurrency) },

View file

@ -5,9 +5,11 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicBoolean
class PeriodicTask(
class PeriodicTask<T>(
private val delay: Long,
private val task: suspend () -> Unit,
private val task: suspend () -> Result<T>,
private val onSuccess: (T) -> Unit,
private val onError: (Throwable) -> Unit,
) {
private var isActive: AtomicBoolean = AtomicBoolean(false)
@ -16,6 +18,18 @@ class PeriodicTask(
isActive.set(true)
while (isActive.get()) {
task.invoke()
.onSuccess {
if (!isActive.get()) {
return@onSuccess
}
onSuccess.invoke(it)
}
.onFailure {
if (!isActive.get()) {
return@onFailure
}
onError.invoke(it)
}
delay(delay)
}
}
@ -25,11 +39,11 @@ class PeriodicTask(
}
}
class SingleTaskScheduler {
class SingleTaskScheduler<T> {
private var lastTask: PeriodicTask? = null
private var lastTask: PeriodicTask<T>? = null
fun scheduleTask(scope: CoroutineScope, task: PeriodicTask) {
fun scheduleTask(scope: CoroutineScope, task: PeriodicTask<T>) {
lastTask?.cancel()
lastTask = task
scope.launch {

View file

@ -0,0 +1,15 @@
package com.tangem.feature.swap.viewmodels
import com.tangem.feature.swap.domain.models.domain.ApproveModel
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.feature.swap.domain.models.domain.SwapDataModel
data class SwapProcessDataState(
val networkId: String,
val fromCurrency: Currency? = null,
val toCurrency: Currency? = null,
val amount: String? = null,
val estimatedGas: Int? = null,
val approveModel: ApproveModel? = null,
val swapModel: SwapDataModel? = null,
)

View file

@ -8,10 +8,12 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.feature.swap.domain.models.createFromAmountWithoutOffset
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.feature.swap.domain.models.domain.SwapDataModel
import com.tangem.feature.swap.domain.models.ui.FoundTokensState
import com.tangem.feature.swap.domain.models.ui.PermissionDataState
import com.tangem.feature.swap.domain.models.ui.SwapState
import com.tangem.feature.swap.domain.models.ui.TxState
import com.tangem.feature.swap.models.SwapStateHolder
import com.tangem.feature.swap.models.UiActions
import com.tangem.feature.swap.presentation.SwapFragment
@ -35,18 +37,19 @@ internal class SwapViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val stateBuilder = StateBuilder(
actions = createUiActions(),
)
private val amountDebouncer = Debouncer()
private val singleTaskScheduler = SingleTaskScheduler()
private val currency = Json.decodeFromString<Currency>(
savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY]
?: error("no expected parameter Currency found"),
)
var uiState: SwapStateHolder by mutableStateOf(stateBuilder.createInitialLoadingState(currency.symbol))
private val stateBuilder = StateBuilder(
actions = createUiActions(),
)
private val amountDebouncer = Debouncer()
private val singleTaskScheduler = SingleTaskScheduler<SwapState>()
private var dataState by mutableStateOf(SwapProcessDataState(networkId = currency.networkId))
var uiState: SwapStateHolder by mutableStateOf(stateBuilder.createInitialLoadingState(currency))
private set
// shows currency order (direct - swap initial to selected, reversed = selected to initial)
@ -60,6 +63,11 @@ internal class SwapViewModel @Inject constructor(
initTokens(currency)
}
override fun onCleared() {
singleTaskScheduler.cancelTask()
super.onCleared()
}
fun setRouter(router: SwapRouter) {
swapRouter = router
uiState = uiState.copy(
@ -75,6 +83,10 @@ internal class SwapViewModel @Inject constructor(
swapInteractor.initTokensToSwap(currency)
}
.onSuccess { state ->
dataState = dataState.copy(
fromCurrency = state.preselectTokens.fromToken,
toCurrency = state.preselectTokens.toToken,
)
updateTokensState(dataState = state.foundTokensState)
startLoadingQuotes(
fromToken = state.preselectTokens.fromToken,
@ -97,71 +109,101 @@ internal class SwapViewModel @Inject constructor(
uiState = stateBuilder.createQuotesLoadingState(uiState, fromToken, toToken, currency.id)
singleTaskScheduler.scheduleTask(
viewModelScope,
PeriodicTask(
delay = UPDATE_DELAY,
) {
loadQuotesInternal(
fromToken = fromToken,
toToken = toToken,
amount = amount,
)
loadQuotesTask(
fromToken = fromToken,
toToken = toToken,
amount = amount,
),
)
}
private fun loadQuotesTask(
fromToken: Currency,
toToken: Currency,
amount: String,
): PeriodicTask<SwapState> {
return PeriodicTask(
UPDATE_DELAY,
task = {
runCatching(dispatchers.io) {
dataState = dataState.copy(
amount = amount,
swapModel = null,
estimatedGas = null,
approveModel = null,
)
swapInteractor.findBestQuote(
networkId = dataState.networkId,
fromToken = fromToken,
toToken = toToken,
amountToSwap = amount,
)
}
},
onSuccess = { swapState ->
when (swapState) {
is SwapState.QuotesLoadedState -> {
fillDataState(swapState.permissionState, swapState.swapDataModel)
uiState = stateBuilder.createQuotesLoadedState(
uiStateHolder = uiState,
quoteModel = swapState,
fromToken = fromToken,
)
}
is SwapState.EmptyAmountState -> {
uiState = stateBuilder.createQuotesEmptyAmountState(
uiStateHolder = uiState,
emptyAmountState = swapState,
)
}
is SwapState.SwapError -> {
uiState = stateBuilder.mapError(uiState, swapState.error)
}
}
},
onError = {
uiState = stateBuilder.addWarning(uiState, it.message)
},
)
}
private fun loadQuotesInternal(fromToken: Currency, toToken: Currency, amount: String) {
viewModelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) {
swapInteractor.findBestQuote(
fromToken = fromToken,
toToken = toToken,
amount = createFromAmountWithoutOffset(
amountWithoutOffset = amount,
decimals = swapInteractor.getTokenDecimals(fromToken),
),
)
}
.onSuccess { swapState ->
when (swapState) {
is SwapState.QuotesLoadedState -> {
uiState = stateBuilder.createQuotesLoadedState(
uiStateHolder = uiState,
quoteModel = swapState,
fromToken = fromToken,
)
}
is SwapState.SwapError -> {
}
}
}
.onFailure { }
private fun fillDataState(permissionState: PermissionDataState, swapDataModel: SwapDataModel?) {
dataState = if (permissionState is PermissionDataState.PermissionReadyForRequest) {
dataState.copy(
estimatedGas = permissionState.requestApproveData.estimatedGas,
approveModel = permissionState.requestApproveData.approveModel,
)
} else {
dataState.copy(
swapModel = swapDataModel,
)
}
}
private fun createUiActions(): UiActions {
return UiActions(
onSearchEntered = { onSearchEntered(it) },
onTokenSelected = { onTokenSelect(it) },
onAmountChanged = { onAmountChanged(it) },
onSwapClick = { onSwapClick() },
onGivePermissionClick = { givePermissionsToSwap() },
onChangeCardsClicked = { onChangeCardsClicked() },
onBackClicked = { onSearchEntered("") },
)
}
private fun onSwapClick() {
singleTaskScheduler.cancelTask()
uiState = stateBuilder.createSwapInProgressState(uiState)
viewModelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) {
swapInteractor.onSwap()
swapInteractor.onSwap(
networkId = dataState.networkId,
swapData = dataState.swapModel!!,
currencyToSend = dataState.fromCurrency!!,
currencyToGet = dataState.toCurrency!!,
amountToSwap = dataState.amount!!,
)
}
.onSuccess {
when (it) {
is SwapState.SwapError -> {
is TxState.TxSent -> {
uiState = stateBuilder.createSuccessState(uiState, it)
swapRouter.openScreen(SwapScreen.Success)
}
else -> {
uiState = stateBuilder.createSwapErrorTransaction(uiState) {
uiState = stateBuilder.clearAlert(uiState)
}
}
else -> {}
}
}
.onFailure { }
@ -171,17 +213,38 @@ internal class SwapViewModel @Inject constructor(
private fun givePermissionsToSwap() {
viewModelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) {
swapInteractor.givePermissionToSwap()
swapInteractor.givePermissionToSwap(
networkId = dataState.networkId,
estimatedGas = dataState.estimatedGas!!,
transactionData = dataState.approveModel!!,
forTokenContractAddress = (dataState.fromCurrency as? Currency.NonNativeToken)?.contractAddress
?: "",
)
}
.onSuccess { }
.onFailure { }
.onSuccess {
when (it) {
is TxState.TxSent -> {
uiState = stateBuilder.loadingPermissionState(uiState)
}
else -> {
uiState = stateBuilder.addAlert(uiState) {
uiState = stateBuilder.clearAlert(uiState)
}
}
}
}
.onFailure {
uiState = stateBuilder.addAlert(uiState) {
uiState = stateBuilder.clearAlert(uiState)
}
}
}
}
private fun onSearchEntered(searchQuery: String) {
viewModelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) {
swapInteractor.onSearchToken(searchQuery)
swapInteractor.onSearchToken(dataState.networkId, searchQuery)
}
.onSuccess {
updateTokensState(it)
@ -202,42 +265,55 @@ internal class SwapViewModel @Inject constructor(
fromToken = currency
toToken = foundToken
}
swapRouter.openScreen(SwapScreen.Main)
dataState = dataState.copy(
fromCurrency = fromToken,
toCurrency = toToken,
)
startLoadingQuotes(fromToken, toToken, lastAmount.value)
swapRouter.openScreen(SwapScreen.Main)
}
}
private fun onChangeCardsClicked() {
val currencies = swapInteractor.getExchangeCurrencies()
val newFromToken = currencies?.toCurrency
val newToToken = currencies?.fromCurrency
val newFromToken = dataState.toCurrency
val newToToken = dataState.fromCurrency
if (newFromToken != null && newToToken != null) {
dataState = dataState.copy(
fromCurrency = newFromToken,
toCurrency = newToToken,
)
isOrderReversed = !isOrderReversed
startLoadingQuotes(newFromToken, newToToken, lastAmount.value)
}
}
private fun onAmountChanged(value: String) {
uiState = stateBuilder.updateSwapAmount(uiState, value, value)
uiState = stateBuilder.updateSwapAmount(uiState, value)
lastAmount.value = value
amountDebouncer.debounce(DEBOUNCE_AMOUNT_DELAY, viewModelScope) {
val currencies = swapInteractor.getExchangeCurrencies()
val fromToken = currencies?.fromCurrency
val toToken = currencies?.toCurrency
val fromToken = dataState.fromCurrency
val toToken = dataState.toCurrency
if (fromToken != null && toToken != null) {
startLoadingQuotes(fromToken, toToken, lastAmount.value)
}
}
}
override fun onCleared() {
singleTaskScheduler.cancelTask()
super.onCleared()
private fun createUiActions(): UiActions {
return UiActions(
onSearchEntered = { onSearchEntered(it) },
onTokenSelected = { onTokenSelect(it) },
onAmountChanged = { onAmountChanged(it) },
onSwapClick = { onSwapClick() },
onGivePermissionClick = { givePermissionsToSwap() },
onChangeCardsClicked = { onChangeCardsClicked() },
onBackClicked = { onSearchEntered("") },
)
}
companion object {
private const val INITIAL_AMOUNT = "1"
private const val UPDATE_DELAY = 10000L
private const val INITIAL_AMOUNT = ""
private const val UPDATE_DELAY = 60000L
private const val DEBOUNCE_AMOUNT_DELAY = 1000L
}
}