Updated on 2026-08-14
This commit is contained in:
parent
30c34a9e14
commit
01e5b6d95a
15 changed files with 322 additions and 124 deletions
|
|
@ -11,6 +11,7 @@ import java.text.DecimalFormat
|
|||
import java.text.DecimalFormatSymbols
|
||||
import java.util.*
|
||||
|
||||
//todo move extensions to utils
|
||||
fun BigDecimal.toFormattedString(
|
||||
decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US
|
||||
): String {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ val navigationMiddleware: Middleware<AppState> = { _, state ->
|
|||
screen = action.screen,
|
||||
addToBackstack = action.addToBackstack,
|
||||
fgShareTransition = action.fragmentShareTransition,
|
||||
bundle = action.bundle,
|
||||
)
|
||||
}
|
||||
is NavigationAction.PopBackTo -> {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ class TransactionManagerImpl(
|
|||
private val appStateHolder: AppStateHolder,
|
||||
) : TransactionManager {
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
override suspend fun sendTransaction(
|
||||
networkId: String,
|
||||
amountToSend: BigDecimal,
|
||||
|
|
@ -63,6 +62,10 @@ class TransactionManagerImpl(
|
|||
return handleSendResult(sendResult)
|
||||
}
|
||||
|
||||
override fun getNativeTokenDecimals(networkId: String): Int {
|
||||
return Blockchain.fromNetworkId(networkId)?.decimals() ?: error("blockchain not found")
|
||||
}
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
override suspend fun getFee(
|
||||
networkId: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.utils
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.*
|
||||
|
||||
fun BigDecimal.toFormattedString(
|
||||
decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US,
|
||||
): String {
|
||||
val symbols = DecimalFormatSymbols(locale)
|
||||
val df = DecimalFormat().apply {
|
||||
decimalFormatSymbols = symbols
|
||||
maximumFractionDigits = decimals
|
||||
minimumFractionDigits = 0
|
||||
isGroupingUsed = false
|
||||
this.roundingMode = roundingMode
|
||||
}
|
||||
return df.format(this)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.swap.converters
|
|||
|
||||
import com.tangem.datasource.api.oneinch.models.QuoteResponse
|
||||
import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel
|
||||
import com.tangem.feature.swap.domain.models.data.createFromAmountWithOffset
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -9,8 +10,10 @@ class QuotesConverter @Inject constructor() : Converter<QuoteResponse, QuoteMode
|
|||
|
||||
override fun convert(value: QuoteResponse): QuoteModel {
|
||||
return QuoteModel(
|
||||
fromTokenAmount = value.fromTokenAmount,
|
||||
toTokenAmount = value.toTokenAmount,
|
||||
fromTokenAmount = createFromAmountWithOffset(value.fromTokenAmount, value.fromToken.decimals),
|
||||
toTokenAmount = createFromAmountWithOffset(value.toTokenAmount, value.toToken.decimals),
|
||||
fromTokenAddress = value.fromToken.address,
|
||||
toTokenAddress = value.toToken.address,
|
||||
estimatedGas = value.estimatedGas,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.data.SwapState
|
||||
import java.math.BigDecimal
|
||||
|
||||
interface SwapInteractor {
|
||||
|
||||
|
|
@ -10,15 +10,18 @@ interface SwapInteractor {
|
|||
|
||||
suspend fun getTokenBalance(tokenId: String): String
|
||||
|
||||
suspend fun getFee(): BigDecimal
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun givePermissionToSwap(tokenToApprove: Currency)
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun findBestQuote(
|
||||
fromToken: Currency,
|
||||
toToken: Currency,
|
||||
amount: String,
|
||||
amount: SwapAmount,
|
||||
): SwapState
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun onSwap(): SwapState
|
||||
|
||||
fun getTokenDecimals(token: Currency): Int
|
||||
}
|
||||
|
|
@ -4,8 +4,10 @@ import com.tangem.feature.swap.domain.cache.SwapDataCache
|
|||
import com.tangem.feature.swap.domain.converters.CryptoCurrencyConverter
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.DataError
|
||||
import com.tangem.feature.swap.domain.models.data.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.data.SwapState
|
||||
import com.tangem.feature.swap.domain.models.data.TransactionModel
|
||||
import com.tangem.feature.swap.domain.models.data.toStringWithRightOffset
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -34,23 +36,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getFee(): BigDecimal {
|
||||
return transactionManager.getFee(
|
||||
networkId = cache.getNetworkId() ?: error("no networkId found, call getTokensToSwap first"),
|
||||
amountToSend = cache.getAmountToSwap() ?: error("no amount found, call findBestQuote first"),
|
||||
currencyToSend = cryptoCurrencyConverter.convert(
|
||||
cache.getExchangeCurrencies()?.fromCurrency ?: error(
|
||||
"no fromCurrency found, call findBestQuote first",
|
||||
),
|
||||
),
|
||||
destinationAddress = getTokenAddress(
|
||||
cache.getExchangeCurrencies()?.toCurrency ?: error(
|
||||
"no toCurrency found, call findBestQuote first",
|
||||
),
|
||||
),
|
||||
).value
|
||||
}
|
||||
|
||||
override suspend fun givePermissionToSwap(tokenToApprove: Currency) {
|
||||
cache.getNetworkId()?.let { networkId ->
|
||||
if (tokenToApprove is Currency.NonNativeToken) {
|
||||
|
|
@ -71,11 +56,10 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun findBestQuote(fromToken: Currency, toToken: Currency, amount: String): SwapState {
|
||||
override suspend fun findBestQuote(fromToken: Currency, toToken: Currency, amount: SwapAmount): SwapState {
|
||||
val networkId = cache.getNetworkId()
|
||||
val fromTokenAddress = getTokenAddress(fromToken)
|
||||
val toTokenAddress = getTokenAddress(toToken)
|
||||
val bigDecimalAmount = amount.toBigDecimalOrNull() ?: error("wrong amount format, use only digits")
|
||||
val isAllowedToSpend = if (networkId.isNullOrEmpty()) {
|
||||
error("no networkId found, please call getTokensToSwap first")
|
||||
} else {
|
||||
|
|
@ -91,13 +75,13 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
networkId = networkId,
|
||||
fromTokenAddress = fromTokenAddress,
|
||||
toTokenAddress = toTokenAddress,
|
||||
amount = amount,
|
||||
amount = amount.toStringWithRightOffset(),
|
||||
).let { quotes ->
|
||||
val quoteDataModel = quotes.dataModel
|
||||
if (quoteDataModel != null) {
|
||||
cache.cacheSwapParams(
|
||||
quoteModel = quoteDataModel.copy(isAllowedToSpend = isAllowedToSpend),
|
||||
amount = bigDecimalAmount,
|
||||
amount = amount,
|
||||
fromCurrency = fromToken,
|
||||
toCurrency = toToken,
|
||||
)
|
||||
|
|
@ -115,9 +99,9 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
if (quoteModel != null && amountToSwap != null && networkId != null) {
|
||||
repository.prepareSwapTransaction(
|
||||
networkId = networkId,
|
||||
fromTokenAddress = quoteModel.fromTokenAmount,
|
||||
toTokenAddress = quoteModel.toTokenAmount,
|
||||
amount = amountToSwap.toPlainString(),
|
||||
fromTokenAddress = quoteModel.fromTokenAddress,
|
||||
toTokenAddress = quoteModel.toTokenAddress,
|
||||
amount = amountToSwap.toStringWithRightOffset(),
|
||||
slippage = DEFAULT_SLIPPAGE,
|
||||
fromWalletAddress = getWalletAddress(networkId),
|
||||
).let {
|
||||
|
|
@ -137,6 +121,14 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getTokenDecimals(token: Currency): Int {
|
||||
return if (token is Currency.NonNativeToken) {
|
||||
token.decimalCount
|
||||
} else {
|
||||
transactionManager.getNativeTokenDecimals(token.networkId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWalletAddress(networkId: String): String {
|
||||
return userWalletManager.getWalletAddress(networkId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@ package com.tangem.feature.swap.domain.cache
|
|||
|
||||
import com.tangem.feature.swap.domain.models.ExchangeCurrencies
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel
|
||||
import java.math.BigDecimal
|
||||
|
||||
interface SwapDataCache {
|
||||
|
||||
fun cacheNetworkId(networkId: String)
|
||||
fun cacheSwapParams(quoteModel: QuoteModel, amount: BigDecimal, fromCurrency: Currency, toCurrency: Currency)
|
||||
fun cacheSwapParams(quoteModel: QuoteModel, amount: SwapAmount, fromCurrency: Currency, toCurrency: Currency)
|
||||
fun cacheAvailableToSwapTokens(networkId: String, tokens: List<Currency>)
|
||||
fun getAvailableTokens(networkId: String): List<Currency>
|
||||
fun getLastQuote(): QuoteModel?
|
||||
fun getAmountToSwap(): BigDecimal?
|
||||
fun getAmountToSwap(): SwapAmount?
|
||||
fun getNetworkId(): String?
|
||||
fun getExchangeCurrencies(): ExchangeCurrencies?
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ package com.tangem.feature.swap.domain.cache
|
|||
import com.tangem.feature.swap.domain.models.ExchangeCurrencies
|
||||
import com.tangem.feature.swap.domain.models.SwapDataHolder
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel
|
||||
import java.math.BigDecimal
|
||||
|
||||
class SwapDataCacheImpl : SwapDataCache {
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ class SwapDataCacheImpl : SwapDataCache {
|
|||
|
||||
override fun cacheSwapParams(
|
||||
quoteModel: QuoteModel,
|
||||
amount: BigDecimal,
|
||||
amount: SwapAmount,
|
||||
fromCurrency: Currency,
|
||||
toCurrency: Currency,
|
||||
) {
|
||||
|
|
@ -52,7 +52,7 @@ class SwapDataCacheImpl : SwapDataCache {
|
|||
return lastDataForSwap.quoteModel
|
||||
}
|
||||
|
||||
override fun getAmountToSwap(): BigDecimal? {
|
||||
override fun getAmountToSwap(): SwapAmount? {
|
||||
return lastDataForSwap.amountToSwap
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
package com.tangem.feature.swap.domain.models
|
||||
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.data.SwapState.QuoteModel
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class SwapDataHolder(
|
||||
val quoteModel: QuoteModel? = null,
|
||||
val amountToSwap: BigDecimal? = null,
|
||||
val amountToSwap: SwapAmount? = null,
|
||||
val networkId: String? = null,
|
||||
val exchangeCurrencies: ExchangeCurrencies? = null,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.feature.swap.domain.models.data
|
||||
|
||||
import com.tangem.utils.toFormattedString
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class SwapAmount(
|
||||
val value: BigDecimal,
|
||||
val decimals: Int,
|
||||
)
|
||||
|
||||
fun SwapAmount.formatToUIRepresentation(): String {
|
||||
return value.toFormattedString(decimals = decimals)
|
||||
}
|
||||
|
||||
fun createFromAmountWithoutOffset(amountWithoutOffset: String, decimals: Int): SwapAmount {
|
||||
return SwapAmount(
|
||||
value = requireNotNull(
|
||||
amountWithoutOffset.toBigDecimalOrNull(),
|
||||
) { "wrong amount format, use only digits" },
|
||||
decimals = decimals,
|
||||
)
|
||||
}
|
||||
|
||||
fun createFromAmountWithOffset(amountWithOffset: String, decimals: Int): SwapAmount {
|
||||
return SwapAmount(
|
||||
value = requireNotNull(
|
||||
amountWithOffset.toBigDecimalOrNull()?.movePointLeft(decimals),
|
||||
) { "wrong amount format, use only digits" },
|
||||
decimals = decimals,
|
||||
)
|
||||
}
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
fun SwapAmount.toStringWithRightOffset(): String {
|
||||
return value.movePointRight(decimals).toPlainString()
|
||||
}
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
fun SwapAmount.toStringWithLeftOffset(): String {
|
||||
return value.movePointLeft(decimals).toPlainString()
|
||||
}
|
||||
|
|
@ -3,15 +3,17 @@ package com.tangem.feature.swap.domain.models.data
|
|||
sealed interface SwapState {
|
||||
|
||||
data class QuoteModel(
|
||||
val fromTokenAmount: String,
|
||||
val toTokenAmount: String,
|
||||
val fromTokenAmount: SwapAmount,
|
||||
val toTokenAmount: SwapAmount,
|
||||
val fromTokenAddress: String,
|
||||
val toTokenAddress: String,
|
||||
val estimatedGas: Int,
|
||||
val isAllowedToSpend: Boolean = false,
|
||||
) : SwapState
|
||||
|
||||
data class SwapSuccess(
|
||||
val fromTokenAmount: String,
|
||||
val toTokenAmount: String,
|
||||
val fromTokenAmount: SwapAmount,
|
||||
val toTokenAmount: SwapAmount,
|
||||
) : SwapState
|
||||
|
||||
data class SwapError(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.SwapState
|
||||
import com.tangem.feature.swap.domain.models.data.formatToUIRepresentation
|
||||
import com.tangem.feature.swap.models.FeeState
|
||||
import com.tangem.feature.swap.models.SwapButton
|
||||
import com.tangem.feature.swap.models.SwapCardData
|
||||
import com.tangem.feature.swap.models.SwapStateHolder
|
||||
import com.tangem.feature.swap.models.SwapWarning
|
||||
import com.tangem.feature.swap.models.TransactionCardType
|
||||
|
||||
/**
|
||||
* State builder creates a specific states for SwapScreen
|
||||
*/
|
||||
class StateBuilder {
|
||||
|
||||
fun createInitialLoadingState(networkCurrency: String, onAmountChanged: (String) -> Unit): SwapStateHolder {
|
||||
return SwapStateHolder(
|
||||
sendCardData = SwapCardData(
|
||||
type = TransactionCardType.SendCard("", true, onAmountChanged),
|
||||
amount = null,
|
||||
amountEquivalent = null,
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "",
|
||||
canSelectAnotherToken = false,
|
||||
),
|
||||
receiveCardData = SwapCardData(
|
||||
type = TransactionCardType.ReceiveCard(),
|
||||
amount = null,
|
||||
amountEquivalent = null,
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "",
|
||||
canSelectAnotherToken = false,
|
||||
),
|
||||
fee = FeeState.Loading,
|
||||
networkCurrency = networkCurrency,
|
||||
swapButton = SwapButton(enabled = false, loading = true, onClick = {}),
|
||||
onRefresh = {},
|
||||
onBackClicked = {},
|
||||
onChangeCardsClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
fun createQuotesLoadingState(
|
||||
uiStateHolder: SwapStateHolder,
|
||||
fromToken: Currency,
|
||||
toToken: Currency,
|
||||
mainTokenId: String,
|
||||
): SwapStateHolder {
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardData(
|
||||
type = (uiStateHolder.sendCardData.type as TransactionCardType.SendCard),
|
||||
amount = uiStateHolder.sendCardData.amount,
|
||||
amountEquivalent = uiStateHolder.sendCardData.amountEquivalent,
|
||||
tokenIconUrl = fromToken.logoUrl,
|
||||
tokenCurrency = fromToken.symbol,
|
||||
canSelectAnotherToken = mainTokenId != fromToken.id,
|
||||
),
|
||||
receiveCardData = SwapCardData(
|
||||
type = TransactionCardType.ReceiveCard(),
|
||||
amount = uiStateHolder.receiveCardData.amount,
|
||||
amountEquivalent = uiStateHolder.receiveCardData.amountEquivalent,
|
||||
tokenIconUrl = toToken.logoUrl,
|
||||
tokenCurrency = toToken.symbol,
|
||||
canSelectAnotherToken = mainTokenId != toToken.id,
|
||||
),
|
||||
fee = FeeState.Loading,
|
||||
swapButton = SwapButton(enabled = false, loading = true, onClick = {}),
|
||||
)
|
||||
}
|
||||
|
||||
fun createQuotesLoadedState(
|
||||
uiStateHolder: SwapStateHolder,
|
||||
quoteModel: SwapState.QuoteModel,
|
||||
fromToken: Currency,
|
||||
onSwapClick: () -> Unit,
|
||||
): SwapStateHolder {
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardData(
|
||||
type = (uiStateHolder.sendCardData.type as TransactionCardType.SendCard)
|
||||
.copy(permissionIsGiven = quoteModel.isAllowedToSpend),
|
||||
amount = quoteModel.fromTokenAmount.formatToUIRepresentation(),
|
||||
amountEquivalent = uiStateHolder.sendCardData.amountEquivalent,
|
||||
tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl,
|
||||
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
|
||||
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
|
||||
),
|
||||
receiveCardData = SwapCardData(
|
||||
type = TransactionCardType.ReceiveCard(),
|
||||
amount = quoteModel.toTokenAmount.formatToUIRepresentation(),
|
||||
amountEquivalent = uiStateHolder.receiveCardData.amountEquivalent,
|
||||
tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl,
|
||||
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
|
||||
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
|
||||
),
|
||||
warnings = if (!quoteModel.isAllowedToSpend) {
|
||||
listOf(SwapWarning.PermissionNeeded(fromToken.symbol))
|
||||
} else {
|
||||
emptyList()
|
||||
},
|
||||
fee = FeeState.Loaded(quoteModel.estimatedGas.toString()),
|
||||
swapButton = SwapButton(
|
||||
enabled = quoteModel.isAllowedToSpend,
|
||||
loading = false,
|
||||
onClick = onSwapClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,38 +4,44 @@ import android.util.Log
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.feature.swap.models.ApprovePermissionButton
|
||||
import com.tangem.feature.swap.models.CancelPermissionButton
|
||||
import com.tangem.feature.swap.models.FeeState
|
||||
import com.tangem.feature.swap.models.SwapButton
|
||||
import com.tangem.feature.swap.models.SwapCardData
|
||||
import com.tangem.feature.swap.models.SwapPermissionStateHolder
|
||||
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.SwapState
|
||||
import com.tangem.feature.swap.domain.models.data.createFromAmountWithoutOffset
|
||||
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.TokenBalanceData
|
||||
import com.tangem.feature.swap.models.TokenToSelect
|
||||
import com.tangem.feature.swap.models.TransactionCardType
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.feature.swap.router.SwapRouter
|
||||
import com.tangem.feature.swap.router.SwapScreen
|
||||
import com.tangem.feature.swap.ui.StateBuilder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@HiltViewModel
|
||||
internal class SwapViewModel @Inject constructor(
|
||||
private val referralInteractor: SwapInteractor,
|
||||
private val swapInteractor: SwapInteractor,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
var uiState: SwapStateHolder by mutableStateOf(createInitialUiState())
|
||||
private val stateBuilder = StateBuilder()
|
||||
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)
|
||||
{ /** [REDACTED_TODO_COMMENT]*/ },
|
||||
)
|
||||
private set
|
||||
|
||||
private var swapRouter: SwapRouter by Delegates.notNull()
|
||||
|
|
@ -52,78 +58,87 @@ internal class SwapViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun createInitialUiState(): SwapStateHolder { // TODO: create using real state
|
||||
return createTestUiState()
|
||||
}
|
||||
|
||||
// region temp for test
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
runCatching {
|
||||
val tokens = referralInteractor.getTokensToSwap("binance-smart-chain")
|
||||
val token = tokens[1]
|
||||
val token2 = tokens[2]
|
||||
}.onFailure {
|
||||
Log.e("SwapViewModel", it.message ?: it.cause.toString())
|
||||
withContext(dispatchers.io) {
|
||||
swapInteractor.getTokensToSwap(currency.networkId)
|
||||
}
|
||||
}
|
||||
.onSuccess { tokens ->
|
||||
if (tokens.size > MIN_TOKENS_IN_LIST) {
|
||||
tokens.firstOrNull { token ->
|
||||
token.id == currency.id
|
||||
}?.let {
|
||||
loadQuotes(it, tokens[1], INITIAL_AMOUNT)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
Log.e("SwapViewModel", it.message ?: it.cause.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTestUiState(): SwapStateHolder {
|
||||
val sendCard = SwapCardData(
|
||||
type = TransactionCardType.SendCard("123", false, {}),
|
||||
amount = "1 000 000 000 000 000 000",
|
||||
amountEquivalent = "1 000 000 000 000 000 000",
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "DAI",
|
||||
networkIconRes = R.drawable.img_polygon_22,
|
||||
)
|
||||
|
||||
val receiveCard = SwapCardData(
|
||||
type = TransactionCardType.ReceiveCard(),
|
||||
amount = "1 000 000",
|
||||
amountEquivalent = "1 000 000",
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "DAI",
|
||||
networkIconRes = R.drawable.img_polygon_22,
|
||||
canSelectAnotherToken = true,
|
||||
)
|
||||
|
||||
val permissionState = SwapPermissionStateHolder(
|
||||
currency = "DAI",
|
||||
amount = "∞",
|
||||
walletAddress = "",
|
||||
spenderAddress = "",
|
||||
fee = "2,14$",
|
||||
approveButton = ApprovePermissionButton(true) {},
|
||||
cancelButton = CancelPermissionButton(true) {},
|
||||
)
|
||||
|
||||
val token = TokenToSelect(
|
||||
id = "USDC",
|
||||
name = "USDC",
|
||||
symbol = "USDC",
|
||||
iconUrl = "",
|
||||
addedTokenBalanceData = TokenBalanceData(amount = "15 000 $", amountEquivalent = "15 000 USDT"),
|
||||
)
|
||||
|
||||
val selectTokenState = SwapSelectTokenStateHolder(
|
||||
listOf(token, token, token, token, token, token, token, token, token),
|
||||
listOf(token, token, token, token, token, token, token, token, token), {}, {},
|
||||
)
|
||||
|
||||
return SwapStateHolder(
|
||||
sendCardData = sendCard,
|
||||
receiveCardData = receiveCard,
|
||||
fee = FeeState.Loaded(fee = "0.155 MATIC (0.14 $)"),
|
||||
warnings = listOf(SwapWarning.PermissionNeeded("DAI")),
|
||||
networkCurrency = "MATIC",
|
||||
swapButton = SwapButton(enabled = true, loading = false, onClick = {}),
|
||||
onRefresh = {}, onBackClicked = {}, onChangeCardsClicked = {},
|
||||
permissionState = permissionState,
|
||||
successState = SwapSuccessStateHolder("Success"),
|
||||
selectTokenState = selectTokenState,
|
||||
)
|
||||
private fun loadQuotes(fromToken: Currency, toToken: Currency, amount: String) {
|
||||
uiState = stateBuilder.createQuotesLoadingState(uiState, fromToken, toToken, currency.id)
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
runCatching {
|
||||
withContext(dispatchers.io) {
|
||||
swapInteractor.findBestQuote(
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
amount = createFromAmountWithoutOffset(
|
||||
amountWithoutOffset = amount,
|
||||
decimals = swapInteractor.getTokenDecimals(fromToken),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
.onSuccess { swapState ->
|
||||
when (swapState) {
|
||||
is SwapState.QuoteModel -> {
|
||||
uiState = stateBuilder.createQuotesLoadedState(uiState, swapState, fromToken) {
|
||||
onSwapClick(toToken, swapState)
|
||||
}
|
||||
}
|
||||
is SwapState.SwapSuccess -> {
|
||||
//todo implement
|
||||
}
|
||||
is SwapState.SwapError -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFailure { }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSwapClick(toToken: Currency, quoteModel: SwapState.QuoteModel) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
runCatching {
|
||||
if (!quoteModel.isAllowedToSpend) {
|
||||
swapInteractor.givePermissionToSwap(toToken)
|
||||
} else {
|
||||
swapInteractor.onSwap()
|
||||
}
|
||||
}
|
||||
.onSuccess {
|
||||
if (it is SwapState) {
|
||||
when (it) {
|
||||
is SwapState.SwapSuccess -> {
|
||||
}
|
||||
is SwapState.SwapError -> {
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFailure { }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MIN_TOKENS_IN_LIST = 2
|
||||
private const val INITIAL_AMOUNT = "1"
|
||||
}
|
||||
// endregion temp for test
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import java.math.BigDecimal
|
|||
|
||||
interface TransactionManager {
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun sendTransaction(
|
||||
networkId: String,
|
||||
amountToSend: BigDecimal,
|
||||
|
|
@ -16,6 +17,7 @@ interface TransactionManager {
|
|||
dataToSign: String,
|
||||
): SendTxResult
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun getFee(
|
||||
networkId: String,
|
||||
amountToSend: BigDecimal,
|
||||
|
|
@ -24,4 +26,7 @@ interface TransactionManager {
|
|||
): ProxyAmount
|
||||
|
||||
fun getNativeAddress(networkId: String): String
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
fun getNativeTokenDecimals(networkId: String): Int
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue