Updated on 2026-08-14
This commit is contained in:
parent
8f78031374
commit
2756dc5687
24 changed files with 843 additions and 314 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) },
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue