Updated on 2026-08-14
This commit is contained in:
parent
3e5ab44b4c
commit
9505a1404b
26 changed files with 576 additions and 542 deletions
|
|
@ -224,20 +224,18 @@ class TransactionManagerImpl(
|
|||
// for not EVM blockchains set gasLimit ZERO for now
|
||||
when (fee.data) {
|
||||
is TransactionFee.Single -> {
|
||||
val fee = (fee.data as TransactionFee.Single).normal
|
||||
val normalFee = (fee.data as TransactionFee.Single).normal
|
||||
val singleFee = ProxyFee(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = fee.amount),
|
||||
fee = convertToProxyAmount(amount = normalFee.amount),
|
||||
)
|
||||
ProxyFees(
|
||||
minFee = singleFee,
|
||||
normalFee = singleFee,
|
||||
priorityFee = singleFee,
|
||||
ProxyFees.SingleFee(
|
||||
singleFee = singleFee,
|
||||
)
|
||||
}
|
||||
is TransactionFee.Choosable -> {
|
||||
val choosableFee = fee.data as TransactionFee.Choosable
|
||||
ProxyFees(
|
||||
ProxyFees.MultipleFees(
|
||||
minFee = ProxyFee(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = choosableFee.minimum.amount),
|
||||
|
|
@ -280,7 +278,7 @@ class TransactionManagerImpl(
|
|||
).increaseBigIntegerByPercents(increaseBy)
|
||||
return when (val gasPrice = walletManager.getGasPrice()) {
|
||||
is Result.Success -> {
|
||||
createProxyFees(gasPrice = gasPrice.data, gasLimit = gasLimit, blockchain = blockchain)
|
||||
createMultipleProxyFees(gasPrice = gasPrice.data, gasLimit = gasLimit, blockchain = blockchain)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
error(gasPrice.error.message ?: gasPrice.error.customMessage)
|
||||
|
|
@ -316,7 +314,7 @@ class TransactionManagerImpl(
|
|||
fee = convertToProxyAmount(amount = choosableFee.priority.amount),
|
||||
)
|
||||
|
||||
ProxyFees(
|
||||
ProxyFees.MultipleFees(
|
||||
minFee = minProxyFee,
|
||||
normalFee = normalProxyFee,
|
||||
priorityFee = priorityProxyFee,
|
||||
|
|
@ -456,7 +454,7 @@ class TransactionManagerImpl(
|
|||
* @param gasLimit
|
||||
* @param blockchain
|
||||
*/
|
||||
private fun createProxyFees(gasPrice: BigInteger, gasLimit: BigInteger, blockchain: Blockchain): ProxyFees {
|
||||
private fun createMultipleProxyFees(gasPrice: BigInteger, gasLimit: BigInteger, blockchain: Blockchain): ProxyFees {
|
||||
val gasPriceNormal = gasPrice.increaseBigIntegerByPercents(MULTIPLIER_GAS_PRICE_FOR_NORMAL_FEE)
|
||||
val gasPricePriority = gasPrice.increaseBigIntegerByPercents(MULTIPLIER_GAS_PRICE_FOR_PRIORITY_FEE)
|
||||
val feeMin = gasLimit.multiply(gasPrice).toBigDecimal(
|
||||
|
|
@ -495,7 +493,7 @@ class TransactionManagerImpl(
|
|||
decimals = blockchain.decimals(),
|
||||
),
|
||||
)
|
||||
return ProxyFees(
|
||||
return ProxyFees.MultipleFees(
|
||||
minFee = minFee,
|
||||
normalFee = normalFee,
|
||||
priorityFee = priorityFee,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import retrofit2.http.Body
|
|||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Interface of Tangem Express API (new swap mechanism)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
* https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-807&mode=design&t=Ygv5sohTTHYAQcBS-4
|
||||
*/
|
||||
@Composable
|
||||
fun SimpleActionRow(title: String, description: String, modifier: Modifier = Modifier) {
|
||||
fun SimpleActionRow(title: String, description: String, modifier: Modifier = Modifier, isClickable: Boolean = true) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(color = TangemTheme.colors.background.action)
|
||||
|
|
@ -44,14 +44,16 @@ fun SimpleActionRow(title: String, description: String, modifier: Modifier = Mod
|
|||
)
|
||||
}
|
||||
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_chevron_right_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.CenterEnd)
|
||||
.padding(end = TangemTheme.dimens.spacing12),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
if (isClickable) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_chevron_right_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.CenterEnd)
|
||||
.padding(end = TangemTheme.dimens.spacing12),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ fun BigDecimal.toFormattedString(
|
|||
@Suppress("MagicNumber")
|
||||
fun BigDecimal.toFormattedCurrencyString(
|
||||
decimals: Int,
|
||||
currency: String,
|
||||
currency: String? = null,
|
||||
roundingMode: RoundingMode = RoundingMode.DOWN,
|
||||
limitNumberOfDecimals: Boolean = true,
|
||||
): String {
|
||||
|
|
@ -38,7 +38,8 @@ fun BigDecimal.toFormattedCurrencyString(
|
|||
decimals = decimalsForRounding,
|
||||
roundingMode = roundingMode,
|
||||
)
|
||||
return "$formattedAmount $currency"
|
||||
val formattedCurrency = currency?.let { " $it " } ?: ""
|
||||
return "$formattedAmount$formattedCurrency"
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatString(
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ import com.tangem.datasource.api.express.models.response.SwapPair
|
|||
import com.tangem.datasource.api.express.models.response.SwapPairsWithProviders
|
||||
import com.tangem.datasource.api.oneinch.OneInchApi
|
||||
import com.tangem.datasource.api.oneinch.OneInchApiFactory
|
||||
import com.tangem.datasource.api.oneinch.OneInchErrorsHandler
|
||||
import com.tangem.datasource.api.oneinch.errors.OneIncResponseException
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
|
|
@ -41,7 +39,6 @@ internal class SwapRepositoryImpl @Inject constructor(
|
|||
private val tangemTechApi: TangemTechApi,
|
||||
private val tangemExpressApi: TangemExpressApi,
|
||||
private val oneInchApiFactory: OneInchApiFactory,
|
||||
private val oneInchErrorsHandler: OneInchErrorsHandler,
|
||||
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
private val configManager: ConfigManager,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
|
|
@ -203,7 +200,7 @@ internal class SwapRepositoryImpl @Inject constructor(
|
|||
toAddress = toAddress,
|
||||
).getOrThrow()
|
||||
AggregatedSwapDataModel(
|
||||
dataModel = expressDataConverter.convert(response)
|
||||
dataModel = expressDataConverter.convert(response),
|
||||
)
|
||||
} catch (ex: Exception) {
|
||||
AggregatedSwapDataModel(null, mapErrors(ex.message))
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class ExpressDataConverter : Converter<ExchangeDataResponse, SwapDataModel> {
|
|||
txId = transactionDto.txId,
|
||||
txTo = transactionDto.txTo,
|
||||
txFrom = requireNotNull(transactionDto.txFrom),
|
||||
txData = requireNotNull(transactionDto.txData)
|
||||
txData = requireNotNull(transactionDto.txData),
|
||||
)
|
||||
} else {
|
||||
ExpressTransactionModel.CEX(
|
||||
|
|
@ -37,5 +37,4 @@ class ExpressDataConverter : Converter<ExchangeDataResponse, SwapDataModel> {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.feature.swap.di
|
|||
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.oneinch.OneInchApiFactory
|
||||
import com.tangem.datasource.api.oneinch.OneInchErrorsHandler
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -26,7 +25,6 @@ class SwapDataModule {
|
|||
tangemTechApi: TangemTechApi,
|
||||
tangemExpressApi: TangemExpressApi,
|
||||
oneInchApiFactory: OneInchApiFactory,
|
||||
oneInchErrorsHandler: OneInchErrorsHandler,
|
||||
coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
configManager: ConfigManager,
|
||||
walletManagerFacade: WalletManagersFacade,
|
||||
|
|
@ -36,7 +34,6 @@ class SwapDataModule {
|
|||
tangemTechApi = tangemTechApi,
|
||||
tangemExpressApi = tangemExpressApi,
|
||||
oneInchApiFactory = oneInchApiFactory,
|
||||
oneInchErrorsHandler = oneInchErrorsHandler,
|
||||
coroutineDispatcher = coroutineDispatcher,
|
||||
configManager = configManager,
|
||||
walletManagersFacade = walletManagerFacade,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ dependencies {
|
|||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.legacy)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.utils)
|
||||
|
|
@ -35,4 +37,5 @@ dependencies {
|
|||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.tangem.blockchain)
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import java.math.BigDecimal
|
||||
|
||||
interface SwapInteractor {
|
||||
|
||||
|
|
@ -71,13 +70,21 @@ interface SwapInteractor {
|
|||
suspend fun onSwap(
|
||||
exchangeProviderType: ExchangeProviderType,
|
||||
networkId: String,
|
||||
swapStateData: SwapStateData,
|
||||
swapData: SwapDataModel,
|
||||
currencyToSend: CryptoCurrency,
|
||||
currencyToGet: CryptoCurrency,
|
||||
amountToSwap: String,
|
||||
fee: TxFee,
|
||||
): TxState
|
||||
|
||||
suspend fun updateQuotesStateWithSelectedFee(
|
||||
state: SwapState.QuotesLoadedState,
|
||||
selectedFee: FeeType,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
amountToSwap: String,
|
||||
networkId: String,
|
||||
): SwapState.QuotesLoadedState
|
||||
|
||||
/**
|
||||
* Returns token in wallet balance
|
||||
*
|
||||
|
|
@ -88,14 +95,5 @@ interface SwapInteractor {
|
|||
|
||||
fun isAvailableToSwap(networkId: String): Boolean
|
||||
|
||||
fun getSwapAmountForToken(amount: String, token: CryptoCurrency): SwapAmount
|
||||
|
||||
suspend fun checkFeeIsEnough(
|
||||
fee: BigDecimal?,
|
||||
spendAmount: SwapAmount,
|
||||
networkId: String,
|
||||
fromToken: CryptoCurrency,
|
||||
): Boolean
|
||||
|
||||
fun getSelectedWallet(): UserWallet?
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -10,6 +12,8 @@ import com.tangem.domain.tokens.model.Quote
|
|||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.feature.swap.domain.cache.SwapDataCache
|
||||
|
|
@ -24,10 +28,10 @@ import com.tangem.lib.crypto.TransactionManager
|
|||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.lib.crypto.models.*
|
||||
import com.tangem.lib.crypto.models.transactions.SendTxResult
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.toFiatString
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
|
@ -45,7 +49,9 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val dispatcher: CoroutineDispatcherProvider,
|
||||
) : SwapInteractor {
|
||||
|
||||
// TODO: Move to DI
|
||||
|
|
@ -53,6 +59,10 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
AddCryptoCurrenciesUseCase(currenciesRepository, networksRepository)
|
||||
}
|
||||
|
||||
private val getFeeUseCase by lazy(LazyThreadSafetyMode.NONE) {
|
||||
GetFeeUseCase(walletManagersFacade, dispatcher)
|
||||
}
|
||||
|
||||
private val swapCurrencyConverter = SwapCurrencyConverter()
|
||||
private val amountFormatter = AmountFormatter()
|
||||
private var derivationPath: String? = null
|
||||
|
|
@ -236,18 +246,11 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
createEmptyAmountState(
|
||||
networkId,
|
||||
fromToken.currency,
|
||||
toToken.currency
|
||||
toToken.currency,
|
||||
)
|
||||
}
|
||||
}
|
||||
val amount = SwapAmount(amountDecimal, getTokenDecimals(fromToken.currency))
|
||||
val fromTokenAddress = getTokenAddress(fromToken.currency)
|
||||
val toTokenAddress = getTokenAddress(toToken.currency)
|
||||
val isAllowedToSpend = isAllowedToSpend(networkId, fromToken.currency, amount)
|
||||
if (isAllowedToSpend && allowPermissionsHandler.isAddressAllowanceInProgress(fromTokenAddress)) {
|
||||
allowPermissionsHandler.removeAddressFromProgress(fromTokenAddress)
|
||||
transactionManager.updateWalletManager(networkId, derivationPath)
|
||||
}
|
||||
val isBalanceWithoutFeeEnough = isBalanceEnough(networkId, fromToken.currency, amount, null)
|
||||
|
||||
when (provider.type) {
|
||||
|
|
@ -256,12 +259,9 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
networkId = networkId,
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
fromTokenAddress = fromTokenAddress,
|
||||
toTokenAddress = toTokenAddress,
|
||||
provider = provider,
|
||||
selectedFee = selectedFee,
|
||||
amount = amount,
|
||||
isAllowedToSpend = isAllowedToSpend,
|
||||
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
|
||||
)
|
||||
}
|
||||
|
|
@ -272,8 +272,8 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
toToken = toToken,
|
||||
provider = provider,
|
||||
amount = amount,
|
||||
isAllowedToSpend = isAllowedToSpend,
|
||||
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
|
||||
selectedFee = selectedFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -284,20 +284,21 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
networkId: String,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
fromTokenAddress: String,
|
||||
toTokenAddress: String,
|
||||
provider: SwapProvider,
|
||||
selectedFee: FeeType,
|
||||
amount: SwapAmount,
|
||||
isAllowedToSpend: Boolean,
|
||||
isBalanceWithoutFeeEnough: Boolean,
|
||||
): Pair<SwapProvider, SwapState> {
|
||||
val fromTokenAddress = getTokenAddress(fromToken.currency)
|
||||
val isAllowedToSpend = isAllowedToSpend(networkId, fromToken.currency, amount)
|
||||
if (isAllowedToSpend && allowPermissionsHandler.isAddressAllowanceInProgress(fromTokenAddress)) {
|
||||
allowPermissionsHandler.removeAddressFromProgress(fromTokenAddress)
|
||||
transactionManager.updateWalletManager(networkId, derivationPath)
|
||||
}
|
||||
return if (isAllowedToSpend && isBalanceWithoutFeeEnough) {
|
||||
provider to loadSwapData(
|
||||
provider = provider,
|
||||
networkId = networkId,
|
||||
fromTokenAddress = fromTokenAddress,
|
||||
toTokenAddress = toTokenAddress,
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
amount = amount,
|
||||
|
|
@ -313,6 +314,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
isAllowedToSpend = isAllowedToSpend,
|
||||
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
|
||||
provider = provider,
|
||||
selectedFee = selectedFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -323,8 +325,8 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
toToken: CryptoCurrencyStatus,
|
||||
provider: SwapProvider,
|
||||
amount: SwapAmount,
|
||||
isAllowedToSpend: Boolean,
|
||||
isBalanceWithoutFeeEnough: Boolean,
|
||||
selectedFee: FeeType,
|
||||
): Pair<SwapProvider, SwapState> {
|
||||
return provider to loadQuoteData(
|
||||
exchangeProviderType = ExchangeProviderType.CEX,
|
||||
|
|
@ -332,9 +334,10 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
amount = amount,
|
||||
fromTokenStatus = fromToken,
|
||||
toTokenStatus = toToken,
|
||||
isAllowedToSpend = isAllowedToSpend,
|
||||
isAllowedToSpend = true,
|
||||
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
|
||||
provider = provider,
|
||||
selectedFee = selectedFee,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +345,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
override suspend fun onSwap(
|
||||
exchangeProviderType: ExchangeProviderType,
|
||||
networkId: String,
|
||||
swapStateData: SwapStateData,
|
||||
swapData: SwapDataModel,
|
||||
currencyToSend: CryptoCurrency,
|
||||
currencyToGet: CryptoCurrency,
|
||||
amountToSwap: String,
|
||||
|
|
@ -355,7 +358,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
ExchangeProviderType.DEX -> {
|
||||
onSwapDex(
|
||||
networkId = networkId,
|
||||
swapStateData = swapStateData,
|
||||
swapData = swapData,
|
||||
currencyToSend = currencyToSend,
|
||||
currencyToGet = currencyToGet,
|
||||
amountToSwap = amountToSwap,
|
||||
|
|
@ -365,9 +368,39 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun updateQuotesStateWithSelectedFee(
|
||||
state: SwapState.QuotesLoadedState,
|
||||
selectedFee: FeeType,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
amountToSwap: String,
|
||||
networkId: String,
|
||||
): SwapState.QuotesLoadedState {
|
||||
val amountDecimal = toBigDecimalOrNull(amountToSwap)
|
||||
if (amountDecimal == null || amountDecimal.signum() == 0) {
|
||||
return state
|
||||
}
|
||||
val amount = SwapAmount(amountDecimal, getTokenDecimals(fromToken.currency))
|
||||
val feeByPriority = selectFeeByType(feeType = selectedFee, txFeeState = state.txFee)
|
||||
val isBalanceIncludeFeeEnough =
|
||||
isBalanceEnough(networkId, fromToken.currency, amount, feeByPriority)
|
||||
val isFeeEnough = checkFeeIsEnough(
|
||||
fee = feeByPriority,
|
||||
spendAmount = amount,
|
||||
networkId = networkId,
|
||||
fromToken = fromToken.currency,
|
||||
)
|
||||
return state.copy(
|
||||
permissionState = PermissionDataState.Empty,
|
||||
preparedSwapConfigState = state.preparedSwapConfigState.copy(
|
||||
isBalanceEnough = isBalanceIncludeFeeEnough,
|
||||
isFeeEnough = isFeeEnough,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun onSwapDex(
|
||||
networkId: String,
|
||||
swapStateData: SwapStateData,
|
||||
swapData: SwapDataModel,
|
||||
currencyToSend: CryptoCurrency,
|
||||
currencyToGet: CryptoCurrency,
|
||||
amountToSwap: String,
|
||||
|
|
@ -382,8 +415,8 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
currencyToSend = swapCurrencyConverter.convert(currencyToSend),
|
||||
feeAmount = fee.feeValue,
|
||||
gasLimit = fee.gasLimit,
|
||||
destinationAddress = swapStateData.swapModel.transaction.txTo,
|
||||
dataToSign = (swapStateData.swapModel.transaction as ExpressTransactionModel.DEX).txData,
|
||||
destinationAddress = swapData.transaction.txTo,
|
||||
dataToSign = (swapData.transaction as ExpressTransactionModel.DEX).txData,
|
||||
),
|
||||
isSwap = true,
|
||||
derivationPath = derivationPath,
|
||||
|
|
@ -405,7 +438,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
currencyToSend.symbol,
|
||||
),
|
||||
toAmount = amountFormatter.formatSwapAmountToUI(
|
||||
swapStateData.swapModel.toTokenAmount,
|
||||
swapData.toTokenAmount,
|
||||
currencyToGet.symbol,
|
||||
),
|
||||
txAddress = userWalletManager.getLastTransactionHash(networkId, derivationPath) ?: "",
|
||||
|
|
@ -437,12 +470,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
return ONE_INCH_SUPPORTED_NETWORKS.contains(networkId)
|
||||
}
|
||||
|
||||
@Deprecated("used in old swap mechanism")
|
||||
override fun getSwapAmountForToken(amount: String, token: CryptoCurrency): SwapAmount {
|
||||
val amountDecimal = requireNotNull(toBigDecimalOrNull(amount)) { "wrong amount format" }
|
||||
return SwapAmount(amountDecimal, getTokenDecimals(token))
|
||||
}
|
||||
|
||||
@Deprecated("used in old swap mechanism")
|
||||
private suspend fun onSuccessLegacyFlow(currency: CryptoCurrency) {
|
||||
userWalletManager.addToken(swapCurrencyConverter.convert(currency), derivationPath)
|
||||
|
|
@ -526,6 +553,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
provider: SwapProvider,
|
||||
isAllowedToSpend: Boolean,
|
||||
isBalanceWithoutFeeEnough: Boolean,
|
||||
selectedFee: FeeType,
|
||||
): SwapState {
|
||||
val fromToken = fromTokenStatus.currency
|
||||
val toToken = toTokenStatus.currency
|
||||
|
|
@ -541,7 +569,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
rateType = RateType.FLOAT,
|
||||
)
|
||||
|
||||
getState(
|
||||
getQuotesState(
|
||||
exchangeProviderType = exchangeProviderType,
|
||||
quoteDataModel = quotes,
|
||||
amount = amount,
|
||||
|
|
@ -550,11 +578,13 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
networkId = networkId,
|
||||
isAllowedToSpend = isAllowedToSpend,
|
||||
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
|
||||
providerType = provider.type,
|
||||
selectedFee = selectedFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getState(
|
||||
private suspend fun getQuotesState(
|
||||
exchangeProviderType: ExchangeProviderType,
|
||||
quoteDataModel: AggregatedSwapDataModel<QuoteModel>,
|
||||
amount: SwapAmount,
|
||||
|
|
@ -563,38 +593,59 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
networkId: String,
|
||||
isAllowedToSpend: Boolean,
|
||||
isBalanceWithoutFeeEnough: Boolean,
|
||||
providerType: ExchangeProviderType,
|
||||
selectedFee: FeeType,
|
||||
): SwapState {
|
||||
val quoteModel = quoteDataModel.dataModel
|
||||
if (quoteModel != null) {
|
||||
val txFee = if (providerType == ExchangeProviderType.CEX) {
|
||||
getFeeForCex(amount, fromToken, networkId)
|
||||
} else {
|
||||
TxFeeState.Empty
|
||||
}
|
||||
val swapState = updateBalances(
|
||||
networkId = networkId,
|
||||
fromTokenStatus = fromToken,
|
||||
toTokenStatus = toToken,
|
||||
fromTokenAmount = amount,
|
||||
toTokenAmount = quoteModel.toTokenAmount,
|
||||
swapStateData = null,
|
||||
swapData = null,
|
||||
txFeeState = txFee,
|
||||
)
|
||||
|
||||
val quotesState = when (exchangeProviderType) {
|
||||
return when (exchangeProviderType) {
|
||||
ExchangeProviderType.DEX -> {
|
||||
updatePermissionState(
|
||||
val state = updatePermissionState(
|
||||
networkId = networkId,
|
||||
fromToken = fromToken.currency,
|
||||
swapAmount = amount,
|
||||
quotesLoadedState = swapState
|
||||
quotesLoadedState = swapState,
|
||||
)
|
||||
state.copy(
|
||||
preparedSwapConfigState = state.preparedSwapConfigState.copy(
|
||||
isAllowedToSpend = isAllowedToSpend,
|
||||
isBalanceEnough = isBalanceWithoutFeeEnough,
|
||||
),
|
||||
)
|
||||
}
|
||||
ExchangeProviderType.CEX -> {
|
||||
swapState.copy(permissionState = PermissionDataState.Empty)
|
||||
val feeByPriority = selectFeeByType(feeType = selectedFee, txFeeState = txFee)
|
||||
val isFeeEnough = checkFeeIsEnough(
|
||||
fee = feeByPriority,
|
||||
spendAmount = amount,
|
||||
networkId = networkId,
|
||||
fromToken = fromToken.currency,
|
||||
)
|
||||
swapState.copy(
|
||||
permissionState = PermissionDataState.Empty,
|
||||
preparedSwapConfigState = PreparedSwapConfigState(
|
||||
isFeeEnough = isFeeEnough,
|
||||
isAllowedToSpend = isAllowedToSpend,
|
||||
isBalanceEnough = isBalanceWithoutFeeEnough,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return quotesState.copy(
|
||||
preparedSwapConfigState = quotesState.preparedSwapConfigState.copy(
|
||||
isAllowedToSpend = isAllowedToSpend,
|
||||
isBalanceEnough = isBalanceWithoutFeeEnough,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
return SwapState.SwapError(quoteDataModel.error)
|
||||
}
|
||||
|
|
@ -606,7 +657,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
val rates = getQuotes(nativeToken.id)
|
||||
return rates[nativeToken.id]?.fiatRate?.let { rate ->
|
||||
fees.map { fee ->
|
||||
" (${fee.toFiatString(rate, appCurrency.symbol, true)})"
|
||||
fee.toFiatString(rate, appCurrency.symbol, true)
|
||||
}
|
||||
}.orEmpty()
|
||||
}
|
||||
|
|
@ -618,8 +669,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
private suspend fun loadSwapData(
|
||||
provider: SwapProvider,
|
||||
networkId: String,
|
||||
fromTokenAddress: String,
|
||||
toTokenAddress: String,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
amount: SwapAmount,
|
||||
|
|
@ -647,11 +696,11 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
data = (swapData.transaction as ExpressTransactionModel.DEX).txData,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
val txFeeState = proxyFeesToFeeState(networkId, feeData)
|
||||
val feeByPriority = when (selectedFee) {
|
||||
FeeType.NORMAL -> txFeeState.normalFee.feeValue
|
||||
FeeType.PRIORITY -> txFeeState.priorityFee.feeValue
|
||||
val txFeeState = when (feeData) {
|
||||
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(networkId)
|
||||
is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(networkId)
|
||||
}
|
||||
val feeByPriority = selectFeeByType(feeType = selectedFee, txFeeState = txFeeState)
|
||||
val isBalanceIncludeFeeEnough =
|
||||
isBalanceEnough(networkId, fromToken.currency, amount, feeByPriority)
|
||||
val isFeeEnough = checkFeeIsEnough(
|
||||
|
|
@ -666,10 +715,8 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
toTokenStatus = toToken,
|
||||
fromTokenAmount = amount,
|
||||
toTokenAmount = swapData.toTokenAmount,
|
||||
swapStateData = SwapStateData(
|
||||
fee = txFeeState,
|
||||
swapModel = swapData,
|
||||
),
|
||||
swapData = swapData,
|
||||
txFeeState = txFeeState,
|
||||
)
|
||||
return swapState.copy(
|
||||
permissionState = PermissionDataState.Empty,
|
||||
|
|
@ -692,7 +739,8 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
toTokenStatus: CryptoCurrencyStatus,
|
||||
fromTokenAmount: SwapAmount,
|
||||
toTokenAmount: SwapAmount,
|
||||
swapStateData: SwapStateData?,
|
||||
swapData: SwapDataModel?,
|
||||
txFeeState: TxFeeState,
|
||||
): SwapState.QuotesLoadedState {
|
||||
val fromToken = fromTokenStatus.currency
|
||||
val toToken = toTokenStatus.currency
|
||||
|
|
@ -719,11 +767,31 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
toRate = rates[toToken.id]?.fiatRate?.toDouble() ?: 0.0,
|
||||
),
|
||||
networkCurrency = userWalletManager.getNetworkCurrency(networkId),
|
||||
swapDataModel = swapStateData,
|
||||
swapDataModel = swapData,
|
||||
tangemFee = getTangemFee(),
|
||||
txFee = txFeeState,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getFeeForCex(
|
||||
amount: SwapAmount,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
networkId: String,
|
||||
): TxFeeState {
|
||||
getSelectedWalletSyncUseCase().getOrNull()?.walletId?.let { userWalletId ->
|
||||
val txFeeResult = getFeeUseCase(
|
||||
amount = amount.value,
|
||||
destination = fromToken.value.networkAddress?.defaultAddress ?: "",
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = fromToken.currency,
|
||||
).firstOrNull()
|
||||
txFeeResult?.getOrNull()?.let { txFee ->
|
||||
return txFee.toTxFeeState(networkId)
|
||||
}
|
||||
}
|
||||
return TxFeeState.Empty
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private suspend fun updatePermissionState(
|
||||
networkId: String,
|
||||
|
|
@ -759,9 +827,17 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
data = transactionData,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
val feeState = proxyFeesToFeeState(networkId, feeData)
|
||||
val feeState = when (feeData) {
|
||||
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(networkId)
|
||||
is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(networkId)
|
||||
}
|
||||
val fee = when (feeState) {
|
||||
TxFeeState.Empty -> BigDecimal.ZERO
|
||||
is TxFeeState.MultipleFeeState -> feeState.normalFee.feeValue
|
||||
is TxFeeState.SingleFeeState -> feeState.fee.feeValue
|
||||
}
|
||||
val isFeeEnough = checkFeeIsEnough(
|
||||
fee = feeData.normalFee.fee.value,
|
||||
fee = fee,
|
||||
spendAmount = SwapAmount.zeroSwapAmount(),
|
||||
networkId = networkId,
|
||||
fromToken = fromToken,
|
||||
|
|
@ -801,30 +877,30 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun proxyFeesToFeeState(networkId: String, proxyFees: ProxyFees): TxFeeState {
|
||||
val normalFeeValue = proxyFees.minFee.fee.value // in swap for normal use min fee
|
||||
val normalFeeGas = proxyFees.minFee.gasLimit.toInt()
|
||||
val priorityFeeValue = proxyFees.normalFee.fee.value // in swap for priority use normal fee
|
||||
val priorityFeeGas = proxyFees.normalFee.gasLimit.toInt()
|
||||
private suspend fun ProxyFees.MultipleFees.proxyFeesToFeeState(networkId: String): TxFeeState {
|
||||
val normalFeeValue = this.minFee.fee.value // in swap for normal use min fee
|
||||
val normalFeeGas = this.minFee.gasLimit.toInt()
|
||||
val priorityFeeValue = this.normalFee.fee.value // in swap for priority use normal fee
|
||||
val priorityFeeGas = this.normalFee.gasLimit.toInt()
|
||||
val feesFiat = getFormattedFiatFees(networkId, normalFeeValue, priorityFeeValue)
|
||||
val normalFiatFee = requireNotNull(feesFiat.getOrNull(0)) { "feesFiat item 0 couldn't be null" }
|
||||
val priorityFiatFee = requireNotNull(feesFiat.getOrNull(1)) { "feesFiat item 1 couldn't be null" }
|
||||
val networkCurrency = userWalletManager.getNetworkCurrency(networkId)
|
||||
val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = normalFeeValue,
|
||||
decimals = transactionManager.getNativeTokenDecimals(networkId),
|
||||
currency = userWalletManager.getNetworkCurrency(networkId),
|
||||
)
|
||||
val priorityCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = priorityFeeValue,
|
||||
decimals = transactionManager.getNativeTokenDecimals(networkId),
|
||||
currency = userWalletManager.getNetworkCurrency(networkId),
|
||||
)
|
||||
return TxFeeState(
|
||||
return TxFeeState.MultipleFeeState(
|
||||
normalFee = TxFee(
|
||||
feeValue = normalFeeValue,
|
||||
gasLimit = normalFeeGas,
|
||||
feeFiatFormatted = normalFiatFee,
|
||||
feeCryptoFormatted = normalCryptoFee,
|
||||
cryptoSymbol = networkCurrency,
|
||||
feeType = FeeType.NORMAL,
|
||||
),
|
||||
priorityFee = TxFee(
|
||||
|
|
@ -832,11 +908,108 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
gasLimit = priorityFeeGas,
|
||||
feeFiatFormatted = priorityFiatFee,
|
||||
feeCryptoFormatted = priorityCryptoFee,
|
||||
cryptoSymbol = networkCurrency,
|
||||
feeType = FeeType.PRIORITY,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun ProxyFees.SingleFee.proxyFeesToFeeState(networkId: String): TxFeeState {
|
||||
val normalFeeValue = this.singleFee.fee.value
|
||||
val normalFeeGas = this.singleFee.gasLimit.toInt()
|
||||
val networkCurrency = userWalletManager.getNetworkCurrency(networkId)
|
||||
val feesFiat = getFormattedFiatFees(networkId, normalFeeValue)
|
||||
val normalFiatFee = requireNotNull(feesFiat.getOrNull(0)) { "feesFiat item 0 couldn't be null" }
|
||||
val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = normalFeeValue,
|
||||
decimals = transactionManager.getNativeTokenDecimals(networkId),
|
||||
)
|
||||
return TxFeeState.SingleFeeState(
|
||||
fee = TxFee(
|
||||
feeValue = normalFeeValue,
|
||||
gasLimit = normalFeeGas,
|
||||
feeFiatFormatted = normalFiatFee,
|
||||
feeCryptoFormatted = normalCryptoFee,
|
||||
cryptoSymbol = networkCurrency,
|
||||
feeType = FeeType.NORMAL,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun TransactionFee.toTxFeeState(networkId: String): TxFeeState {
|
||||
val networkCurrency = userWalletManager.getNetworkCurrency(networkId)
|
||||
return when (this) {
|
||||
is TransactionFee.Choosable -> {
|
||||
val feeNormal = this.normal.amount.value ?: BigDecimal.ZERO
|
||||
val feePriority = this.priority.amount.value ?: BigDecimal.ZERO
|
||||
val normalFiatValue = getFormattedFiatFees(networkId, feeNormal)[0]
|
||||
val priorityFiatValue = getFormattedFiatFees(networkId, feePriority)[0]
|
||||
val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = feeNormal,
|
||||
decimals = transactionManager.getNativeTokenDecimals(networkId),
|
||||
)
|
||||
val priorityCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = feePriority,
|
||||
decimals = transactionManager.getNativeTokenDecimals(networkId),
|
||||
)
|
||||
TxFeeState.MultipleFeeState(
|
||||
normalFee = TxFee(
|
||||
feeValue = feeNormal,
|
||||
gasLimit = this.normal.getGasLimit(),
|
||||
feeFiatFormatted = normalFiatValue,
|
||||
feeCryptoFormatted = normalCryptoFee,
|
||||
cryptoSymbol = networkCurrency,
|
||||
feeType = FeeType.NORMAL,
|
||||
),
|
||||
priorityFee = TxFee(
|
||||
feeValue = feePriority,
|
||||
gasLimit = this.priority.getGasLimit(),
|
||||
feeFiatFormatted = priorityFiatValue,
|
||||
feeCryptoFormatted = priorityCryptoFee,
|
||||
cryptoSymbol = networkCurrency,
|
||||
feeType = FeeType.PRIORITY,
|
||||
),
|
||||
)
|
||||
}
|
||||
is TransactionFee.Single -> {
|
||||
val feeNormal = this.normal.amount.value ?: BigDecimal.ZERO
|
||||
val normalFiatValue = getFormattedFiatFees(networkId, feeNormal)[0]
|
||||
val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = feeNormal,
|
||||
decimals = transactionManager.getNativeTokenDecimals(networkId),
|
||||
)
|
||||
TxFeeState.SingleFeeState(
|
||||
fee = TxFee(
|
||||
feeValue = this.normal.amount.value ?: BigDecimal.ZERO,
|
||||
gasLimit = this.normal.getGasLimit(),
|
||||
feeFiatFormatted = normalFiatValue,
|
||||
feeCryptoFormatted = normalCryptoFee,
|
||||
cryptoSymbol = networkCurrency,
|
||||
feeType = FeeType.NORMAL,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Fee.getGasLimit(): Int {
|
||||
return when (this) {
|
||||
is Fee.Common -> 0
|
||||
is Fee.Ethereum -> this.gasLimit.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): BigDecimal {
|
||||
return when (txFeeState) {
|
||||
TxFeeState.Empty -> BigDecimal.ZERO
|
||||
is TxFeeState.SingleFeeState -> txFeeState.fee.feeValue
|
||||
is TxFeeState.MultipleFeeState -> when (feeType) {
|
||||
FeeType.NORMAL -> txFeeState.normalFee.feeValue
|
||||
FeeType.PRIORITY -> txFeeState.priorityFee.feeValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBalanceEnough(
|
||||
networkId: String,
|
||||
fromToken: CryptoCurrency,
|
||||
|
|
@ -866,7 +1039,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun checkFeeIsEnough(
|
||||
private suspend fun checkFeeIsEnough(
|
||||
fee: BigDecimal?,
|
||||
spendAmount: SwapAmount,
|
||||
networkId: String,
|
||||
|
|
@ -938,10 +1111,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_SLIPPAGE = 2
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private const val INCREASE_FEE_TO_CHECK_ENOUGH_PERCENT = 1.0 // if need to increase fee when check isEnough
|
||||
private const val INCREASE_GAS_LIMIT_BY = 112 // 12%
|
||||
private const val INFINITY_SYMBOL = "∞"
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
|
|||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.feature.swap.domain.*
|
||||
|
|
@ -36,6 +37,8 @@ class SwapDomainModule {
|
|||
@SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
@SwapScope getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
|
||||
quotesRepository: QuotesRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
): SwapInteractor {
|
||||
return SwapInteractorImpl(
|
||||
transactionManager = transactionManager,
|
||||
|
|
@ -49,6 +52,8 @@ class SwapDomainModule {
|
|||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
getMultiCryptoCurrencyStatusUseCase = getCryptoCurrencyStatusUseCase,
|
||||
quotesRepository = quotesRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
dispatcher = coroutineDispatcherProvider,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ sealed class ExpressTransactionModel {
|
|||
override val txTo: String,
|
||||
val txFrom: String,
|
||||
val txData: String,
|
||||
): ExpressTransactionModel()
|
||||
) : ExpressTransactionModel()
|
||||
|
||||
data class CEX(
|
||||
override val fromAmount: SwapAmount,
|
||||
|
|
@ -25,6 +25,5 @@ sealed class ExpressTransactionModel {
|
|||
override val txTo: String,
|
||||
val externalTxId: String,
|
||||
val externalTxUrl: String,
|
||||
): ExpressTransactionModel()
|
||||
|
||||
) : ExpressTransactionModel()
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ class AmountFormatter {
|
|||
* @param currency
|
||||
* @return formatted [String]
|
||||
*/
|
||||
fun formatBigDecimalAmountToUI(amount: BigDecimal, decimals: Int, currency: String): String {
|
||||
fun formatBigDecimalAmountToUI(amount: BigDecimal, decimals: Int, currency: String? = null): String {
|
||||
return amount.toFormattedCurrencyString(decimals, currency)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,8 @@ sealed interface SwapState {
|
|||
isFeeEnough = false,
|
||||
),
|
||||
val permissionState: PermissionDataState = PermissionDataState.Empty,
|
||||
val swapDataModel: SwapStateData? = null,
|
||||
val swapDataModel: SwapDataModel? = null,
|
||||
val txFee: TxFeeState,
|
||||
val tangemFee: Double,
|
||||
) : SwapState
|
||||
|
||||
|
|
@ -62,21 +63,30 @@ data class RequestApproveStateData(
|
|||
val fromTokenAmount: SwapAmount,
|
||||
)
|
||||
|
||||
data class SwapStateData(
|
||||
val fee: TxFeeState,
|
||||
val swapModel: SwapDataModel,
|
||||
)
|
||||
// data class SwapStateData(
|
||||
// val fee: TxFeeState,
|
||||
// val swapModel: SwapDataModel,
|
||||
// )
|
||||
|
||||
data class TxFeeState(
|
||||
val normalFee: TxFee,
|
||||
val priorityFee: TxFee,
|
||||
)
|
||||
sealed class TxFeeState {
|
||||
data class MultipleFeeState(
|
||||
val normalFee: TxFee,
|
||||
val priorityFee: TxFee,
|
||||
) : TxFeeState()
|
||||
|
||||
data class SingleFeeState(
|
||||
val fee: TxFee,
|
||||
) : TxFeeState()
|
||||
|
||||
object Empty : TxFeeState()
|
||||
}
|
||||
|
||||
data class TxFee(
|
||||
val feeValue: BigDecimal,
|
||||
val gasLimit: Int,
|
||||
val feeFiatFormatted: String,
|
||||
val feeCryptoFormatted: String,
|
||||
val cryptoSymbol: String,
|
||||
val feeType: FeeType,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.ui.components.notifications.NotificationConfig
|
|||
import com.tangem.core.ui.components.states.Item
|
||||
import com.tangem.core.ui.components.states.SelectableItemsState
|
||||
import com.tangem.feature.swap.domain.models.ui.TxFee
|
||||
import com.tangem.feature.swap.models.states.FeeItemState
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
|
||||
data class SwapStateHolder(
|
||||
|
|
@ -14,13 +15,14 @@ data class SwapStateHolder(
|
|||
val networkCurrency: String,
|
||||
val networkId: String,
|
||||
val blockchainId: String, // not the same as networkId, its local id in app
|
||||
val fee: FeeState = FeeState.Empty,
|
||||
val warnings: List<SwapWarning> = emptyList(),
|
||||
val alert: SwapWarning.GenericWarning? = null,
|
||||
val updateInProgress: Boolean = false,
|
||||
val providerState: ProviderState,
|
||||
|
||||
val fee: FeeItemState = FeeItemState.Empty,
|
||||
val permissionState: SwapPermissionState = SwapPermissionState.Empty,
|
||||
|
||||
val successState: SwapSuccessStateHolder? = null,
|
||||
val selectTokenState: SwapSelectTokenStateHolder? = null,
|
||||
val bottomSheetConfig: TangemBottomSheetConfig? = null,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package com.tangem.feature.swap.models
|
||||
|
||||
import com.tangem.core.ui.components.states.Item
|
||||
import com.tangem.feature.swap.domain.models.ui.FeeType
|
||||
import com.tangem.feature.swap.domain.models.ui.TxFee
|
||||
|
||||
data class UiActions(
|
||||
|
|
@ -17,10 +15,9 @@ data class UiActions(
|
|||
val openPermissionBottomSheet: () -> Unit,
|
||||
val hidePermissionBottomSheet: () -> Unit,
|
||||
val onChangeApproveType: (ApproveType) -> Unit,
|
||||
val onSelectItemFee: (Item<TxFee>) -> Unit,
|
||||
// region new actions
|
||||
val onClickFee: () -> Unit,
|
||||
val onSelectFeeType: (FeeType) -> Unit,
|
||||
val onSelectFeeType: (TxFee) -> Unit,
|
||||
val onProviderClick: (String) -> Unit,
|
||||
val onProviderSelect: (String) -> Unit,
|
||||
)
|
||||
|
|
@ -4,8 +4,8 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
|||
import com.tangem.feature.swap.domain.models.ui.FeeType
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
class ChooseFeeBottomSheetConfig(
|
||||
data class ChooseFeeBottomSheetConfig(
|
||||
val selectedFee: FeeType,
|
||||
val onSelectFeeType: (FeeType) -> Unit,
|
||||
val feeItems: ImmutableList<FeeItemState>,
|
||||
val feeItems: ImmutableList<FeeItemState.Content>,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
|
@ -1,13 +1,19 @@
|
|||
package com.tangem.feature.swap.models.states
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.feature.swap.domain.models.ui.FeeType
|
||||
|
||||
data class FeeItemState(
|
||||
val feeType: FeeType,
|
||||
val title: String,
|
||||
val amountCrypto: String,
|
||||
val symbolCrypto: String,
|
||||
val amountFiat: String,
|
||||
val symbolFiat: String,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
sealed class FeeItemState {
|
||||
|
||||
data class Content(
|
||||
val feeType: FeeType,
|
||||
val title: TextReference,
|
||||
val amountCrypto: String,
|
||||
val symbolCrypto: String,
|
||||
val amountFiatFormatted: String,
|
||||
val isClickable: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
) : FeeItemState()
|
||||
|
||||
object Empty : FeeItemState()
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.rows.SelectorRowItem
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.swap.domain.models.ui.FeeType
|
||||
import com.tangem.feature.swap.models.states.ChooseFeeBottomSheetConfig
|
||||
|
|
@ -31,7 +32,7 @@ fun ChooseFeeBottomSheet(config: TangemBottomSheetConfig) {
|
|||
@Composable
|
||||
private fun ChooseFeeBottomSheetContent(content: ChooseFeeBottomSheetConfig) {
|
||||
Column(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.secondary),
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
Text(
|
||||
text = "Choose fee", // todo replace with strings
|
||||
|
|
@ -73,7 +74,7 @@ private fun FeeItemsBlock(content: ChooseFeeBottomSheetConfig) {
|
|||
content.feeItems.forEach { feeItem ->
|
||||
val isSelected = feeItem.feeType == content.selectedFee
|
||||
val preEllipsizeText = feeItem.amountCrypto
|
||||
val postEllipsizeText = " ${feeItem.symbolCrypto} (${feeItem.amountFiat} ${feeItem.symbolFiat})"
|
||||
val postEllipsizeText = " ${feeItem.symbolCrypto} (${feeItem.amountFiatFormatted})"
|
||||
when (feeItem.feeType) {
|
||||
FeeType.NORMAL -> {
|
||||
SelectorRowItem(
|
||||
|
|
@ -103,22 +104,22 @@ private fun FeeItemsBlock(content: ChooseFeeBottomSheetConfig) {
|
|||
@Composable
|
||||
private fun ChooseFeeBottomSheetContent_Preview() {
|
||||
val feeItems = listOf(
|
||||
FeeItemState(
|
||||
FeeItemState.Content(
|
||||
feeType = FeeType.NORMAL,
|
||||
title = "Fee",
|
||||
title = stringReference("Fee"),
|
||||
amountCrypto = "1000",
|
||||
symbolCrypto = "MATIC",
|
||||
amountFiat = "10",
|
||||
symbolFiat = "$",
|
||||
amountFiatFormatted = "(10$)",
|
||||
isClickable = false,
|
||||
onClick = {},
|
||||
),
|
||||
FeeItemState(
|
||||
FeeItemState.Content(
|
||||
feeType = FeeType.PRIORITY,
|
||||
title = "Fee",
|
||||
title = stringReference("Fee"),
|
||||
amountCrypto = "2000",
|
||||
symbolCrypto = "MATIC",
|
||||
amountFiat = "20",
|
||||
symbolFiat = "$",
|
||||
amountFiatFormatted = "(10$)",
|
||||
isClickable = false,
|
||||
onClick = {},
|
||||
),
|
||||
).toImmutableList()
|
||||
|
|
|
|||
|
|
@ -5,35 +5,47 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.rows.SimpleActionRow
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.swap.domain.models.ui.FeeType
|
||||
import com.tangem.feature.swap.models.states.FeeItemState
|
||||
|
||||
@Composable
|
||||
fun FeeItem(state: FeeItemState) {
|
||||
fun FeeItemBlock(state: FeeItemState) {
|
||||
if (state is FeeItemState.Content) {
|
||||
FeeItem(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FeeItem(state: FeeItemState.Content) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.clip(shape = TangemTheme.shapes.roundedCornersXMedium)
|
||||
.clickable(
|
||||
onClick = state.onClick,
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size68),
|
||||
) {
|
||||
val description = "${state.amountCrypto} ${state.symbolCrypto} (${state.amountFiat} ${state.symbolFiat})"
|
||||
val description = "${state.amountCrypto} ${state.symbolCrypto} (${state.amountFiatFormatted})"
|
||||
SimpleActionRow(
|
||||
modifier = Modifier.padding(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
title = state.title,
|
||||
title = state.title.resolveReference(),
|
||||
description = description,
|
||||
isClickable = state.isClickable,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -41,13 +53,13 @@ fun FeeItem(state: FeeItemState) {
|
|||
@Preview
|
||||
@Composable
|
||||
private fun FeeItemPreview() {
|
||||
val state = FeeItemState(
|
||||
val state = FeeItemState.Content(
|
||||
feeType = FeeType.NORMAL,
|
||||
title = "Fee",
|
||||
title = stringReference("Fee"),
|
||||
amountCrypto = "1000",
|
||||
symbolCrypto = "MATIC",
|
||||
amountFiat = "10",
|
||||
symbolFiat = "$",
|
||||
amountFiatFormatted = "(1000$)",
|
||||
isClickable = false,
|
||||
onClick = {},
|
||||
)
|
||||
Column {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import androidx.compose.ui.text.input.TextFieldValue
|
|||
import com.tangem.common.Provider
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.components.states.Item
|
||||
import com.tangem.core.ui.components.states.SelectableItemsState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
|
|
@ -22,9 +20,7 @@ import com.tangem.feature.swap.domain.models.domain.SwapProvider
|
|||
import com.tangem.feature.swap.domain.models.formatToUIRepresentation
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig
|
||||
import com.tangem.feature.swap.models.states.GivePermissionBottomSheetConfig
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
import com.tangem.feature.swap.models.states.*
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -76,7 +72,7 @@ internal class StateBuilder(
|
|||
coinId = null,
|
||||
isBalanceHidden = true,
|
||||
),
|
||||
fee = FeeState.Loading,
|
||||
fee = FeeItemState.Empty,
|
||||
networkCurrency = networkInfo.blockchainCurrency,
|
||||
swapButton = SwapButton(enabled = false, loading = true, onClick = {}),
|
||||
onRefresh = {},
|
||||
|
|
@ -86,7 +82,7 @@ internal class StateBuilder(
|
|||
updateInProgress = true,
|
||||
onShowPermissionBottomSheet = actions.openPermissionBottomSheet,
|
||||
onCancelPermissionBottomSheet = actions.hidePermissionBottomSheet,
|
||||
providerState = ProviderState.Loading(),
|
||||
providerState = ProviderState.Empty(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +123,7 @@ internal class StateBuilder(
|
|||
),
|
||||
),
|
||||
),
|
||||
fee = FeeState.Empty,
|
||||
fee = FeeItemState.Empty,
|
||||
swapButton = SwapButton(
|
||||
enabled = false,
|
||||
loading = false,
|
||||
|
|
@ -143,8 +139,8 @@ internal class StateBuilder(
|
|||
toToken: CryptoCurrency,
|
||||
mainTokenId: String,
|
||||
): SwapStateHolder {
|
||||
val canSelectSendToken = mainTokenId != fromToken.id.value // TODO look at id matching
|
||||
val canSelectReceiveToken = mainTokenId != toToken.id.value // TODO look at id matching
|
||||
val canSelectSendToken = mainTokenId != fromToken.id.value
|
||||
val canSelectReceiveToken = mainTokenId != toToken.id.value
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
return uiStateHolder.copy(
|
||||
|
|
@ -172,8 +168,9 @@ internal class StateBuilder(
|
|||
balance = if (!canSelectReceiveToken) uiStateHolder.receiveCardData.balance else "",
|
||||
isBalanceHidden = isBalanceHiddenProvider(),
|
||||
),
|
||||
fee = FeeState.Loading,
|
||||
fee = FeeItemState.Empty,
|
||||
swapButton = SwapButton(enabled = false, loading = true, onClick = {}),
|
||||
providerState = ProviderState.Loading(),
|
||||
permissionState = uiStateHolder.permissionState,
|
||||
updateInProgress = true,
|
||||
)
|
||||
|
|
@ -185,7 +182,6 @@ internal class StateBuilder(
|
|||
* @param uiStateHolder whole screen state
|
||||
* @param quoteModel data model
|
||||
* @param fromToken token data to swap
|
||||
* @param onFeeSetup callback for reset fee after auto update
|
||||
* @return updated whole screen state
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
|
|
@ -194,7 +190,7 @@ internal class StateBuilder(
|
|||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
swapProvider: SwapProvider,
|
||||
onFeeSetup: (TxFee) -> Unit,
|
||||
selectedFeeType: FeeType,
|
||||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
|
|
@ -221,7 +217,7 @@ internal class StateBuilder(
|
|||
),
|
||||
)
|
||||
}
|
||||
val feeState = createFeeState(quoteModel, uiStateHolder, onFeeSetup)
|
||||
val feeState = createFeeState(quoteModel.txFee, selectedFeeType)
|
||||
val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus
|
||||
val toCurrencyStatus = quoteModel.toTokenInfo.cryptoCurrencyStatus
|
||||
return uiStateHolder.copy(
|
||||
|
|
@ -254,7 +250,6 @@ internal class StateBuilder(
|
|||
permissionState = convertPermissionState(
|
||||
lastPermissionState = uiStateHolder.permissionState,
|
||||
permissionDataState = quoteModel.permissionState,
|
||||
feeState = feeState,
|
||||
onGivePermissionClick = actions.onGivePermissionClick,
|
||||
onChangeApproveType = actions.onChangeApproveType,
|
||||
),
|
||||
|
|
@ -308,7 +303,7 @@ internal class StateBuilder(
|
|||
isBalanceHidden = isBalanceHiddenProvider(),
|
||||
),
|
||||
warnings = emptyList(),
|
||||
fee = FeeState.Empty,
|
||||
fee = FeeItemState.Empty,
|
||||
swapButton = SwapButton(
|
||||
enabled = false,
|
||||
loading = false,
|
||||
|
|
@ -397,144 +392,36 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
fun updateFeeSelectedItem(uiState: SwapStateHolder, item: Item<TxFee>, isFeeEnough: Boolean): SwapStateHolder {
|
||||
val newSelectedItem = item.copy(
|
||||
startText = TextReference.Res(R.string.send_network_fee_title),
|
||||
)
|
||||
val permissionState = uiState.permissionState
|
||||
val newPermissionState = if (permissionState is SwapPermissionState.ReadyForRequest) {
|
||||
permissionState.copy(
|
||||
fee = newSelectedItem.endText,
|
||||
)
|
||||
} else {
|
||||
permissionState
|
||||
}
|
||||
val updateState = when (val fee = uiState.fee) {
|
||||
is FeeState.Loaded -> {
|
||||
getUpdatedFeeStateForEnoughFee(uiState, fee, item, newSelectedItem, newPermissionState, isFeeEnough)
|
||||
private fun createFeeState(txFeeState: TxFeeState, feeType: FeeType): FeeItemState {
|
||||
val isClickable: Boolean
|
||||
val fee = when (txFeeState) {
|
||||
TxFeeState.Empty -> return FeeItemState.Empty
|
||||
is TxFeeState.SingleFeeState -> {
|
||||
isClickable = false
|
||||
txFeeState.fee
|
||||
}
|
||||
is FeeState.NotEnoughFundsWarning -> {
|
||||
getUpdatedFeeStateForNotEnoughFee(uiState, fee, item, newSelectedItem, newPermissionState, isFeeEnough)
|
||||
is TxFeeState.MultipleFeeState -> {
|
||||
isClickable = true
|
||||
when (feeType) {
|
||||
FeeType.NORMAL -> {
|
||||
txFeeState.normalFee
|
||||
}
|
||||
FeeType.PRIORITY -> {
|
||||
txFeeState.priorityFee
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> uiState
|
||||
}
|
||||
return if (isFeeEnough) {
|
||||
updateState.copy(
|
||||
warnings = uiState.warnings.filterNot { it is SwapWarning.InsufficientFunds },
|
||||
)
|
||||
} else {
|
||||
updateState.copy(
|
||||
warnings = uiState.warnings.plus(SwapWarning.InsufficientFunds),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun getUpdatedFeeStateForEnoughFee(
|
||||
uiState: SwapStateHolder,
|
||||
fee: FeeState.Loaded,
|
||||
itemToSelect: Item<TxFee>,
|
||||
newSelectedItem: Item<TxFee>,
|
||||
newPermissionState: SwapPermissionState,
|
||||
isFeeEnough: Boolean,
|
||||
): SwapStateHolder {
|
||||
val newState = fee.state?.copy(
|
||||
selectedItem = newSelectedItem,
|
||||
items = selectNewItem(fee.state.items, itemToSelect),
|
||||
return FeeItemState.Content(
|
||||
feeType = feeType,
|
||||
title = stringReference("Fee"), // todo replace with string
|
||||
amountCrypto = fee.feeCryptoFormatted,
|
||||
symbolCrypto = fee.cryptoSymbol,
|
||||
amountFiatFormatted = fee.feeFiatFormatted,
|
||||
isClickable = isClickable,
|
||||
onClick = actions.onClickFee,
|
||||
)
|
||||
val newFeeState = if (isFeeEnough) {
|
||||
fee.copy(state = newState)
|
||||
} else {
|
||||
FeeState.NotEnoughFundsWarning(
|
||||
tangemFee = fee.tangemFee,
|
||||
state = newState,
|
||||
onSelectItem = fee.onSelectItem,
|
||||
)
|
||||
}
|
||||
return uiState.copy(
|
||||
fee = newFeeState,
|
||||
permissionState = newPermissionState,
|
||||
swapButton = uiState.swapButton.copy(
|
||||
enabled = isFeeEnough,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun getUpdatedFeeStateForNotEnoughFee(
|
||||
uiState: SwapStateHolder,
|
||||
fee: FeeState.NotEnoughFundsWarning,
|
||||
itemToSelect: Item<TxFee>,
|
||||
newSelectedItem: Item<TxFee>,
|
||||
newPermissionState: SwapPermissionState,
|
||||
isFeeEnough: Boolean,
|
||||
): SwapStateHolder {
|
||||
val newState = fee.state?.copy(
|
||||
selectedItem = newSelectedItem,
|
||||
items = selectNewItem(fee.state.items, itemToSelect),
|
||||
)
|
||||
val newFeeState = if (isFeeEnough) {
|
||||
FeeState.Loaded(
|
||||
tangemFee = fee.tangemFee,
|
||||
state = newState,
|
||||
onSelectItem = fee.onSelectItem,
|
||||
)
|
||||
} else {
|
||||
fee.copy(state = newState)
|
||||
}
|
||||
return uiState.copy(
|
||||
fee = newFeeState,
|
||||
permissionState = newPermissionState,
|
||||
swapButton = uiState.swapButton.copy(
|
||||
enabled = isFeeEnough,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeState(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
uiStateHolder: SwapStateHolder,
|
||||
onFeeSetup: (TxFee) -> Unit,
|
||||
): FeeState {
|
||||
val previousFeeState = when (val stateFee = uiStateHolder.fee) {
|
||||
is FeeState.Loaded -> stateFee.state
|
||||
is FeeState.NotEnoughFundsWarning -> stateFee.state
|
||||
else -> null
|
||||
}
|
||||
val permissionState = quoteModel.permissionState
|
||||
val feeState = if (permissionState is PermissionDataState.PermissionReadyForRequest) {
|
||||
permissionState.requestApproveData.fee
|
||||
} else {
|
||||
quoteModel.swapDataModel?.fee
|
||||
}
|
||||
val selectFeeState = createSelectFeeState(
|
||||
fee = feeState,
|
||||
previousState = previousFeeState,
|
||||
onFeeSetup = onFeeSetup,
|
||||
)
|
||||
return if (quoteModel.preparedSwapConfigState.isFeeEnough) {
|
||||
FeeState.Loaded(
|
||||
tangemFee = quoteModel.tangemFee,
|
||||
state = selectFeeState,
|
||||
onSelectItem = actions.onSelectItemFee,
|
||||
)
|
||||
} else {
|
||||
FeeState.NotEnoughFundsWarning(
|
||||
tangemFee = quoteModel.tangemFee,
|
||||
state = selectFeeState,
|
||||
onSelectItem = actions.onSelectItemFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectNewItem(items: ImmutableList<Item<TxFee>>, selectItem: Item<TxFee>): ImmutableList<Item<TxFee>> {
|
||||
return items.map {
|
||||
if (it.id == selectItem.id) {
|
||||
it.copy(isSelected = true)
|
||||
} else {
|
||||
it.copy(isSelected = false)
|
||||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
fun loadingPermissionState(uiState: SwapStateHolder): SwapStateHolder {
|
||||
|
|
@ -612,7 +499,6 @@ internal class StateBuilder(
|
|||
private fun convertPermissionState(
|
||||
lastPermissionState: SwapPermissionState,
|
||||
permissionDataState: PermissionDataState,
|
||||
feeState: FeeState,
|
||||
onGivePermissionClick: () -> Unit,
|
||||
onChangeApproveType: (ApproveType) -> Unit,
|
||||
): SwapPermissionState {
|
||||
|
|
@ -621,97 +507,33 @@ internal class StateBuilder(
|
|||
} else {
|
||||
ApproveType.UNLIMITED
|
||||
}
|
||||
val fee = when (feeState) {
|
||||
is FeeSelectState -> feeState.state?.selectedItem?.endText
|
||||
else -> null
|
||||
}
|
||||
return when (permissionDataState) {
|
||||
PermissionDataState.Empty -> SwapPermissionState.Empty
|
||||
PermissionDataState.PermissionFailed -> SwapPermissionState.Empty
|
||||
PermissionDataState.PermissionLoading -> SwapPermissionState.InProgress
|
||||
is PermissionDataState.PermissionReadyForRequest -> SwapPermissionState.ReadyForRequest(
|
||||
currency = permissionDataState.currency,
|
||||
amount = permissionDataState.amount,
|
||||
approveType = approveType,
|
||||
walletAddress = getShortAddressValue(permissionDataState.walletAddress),
|
||||
spenderAddress = getShortAddressValue(permissionDataState.spenderAddress),
|
||||
fee = fee ?: TextReference.Str(""),
|
||||
approveButton = ApprovePermissionButton(
|
||||
enabled = true,
|
||||
onClick = onGivePermissionClick,
|
||||
),
|
||||
cancelButton = CancelPermissionButton(
|
||||
enabled = true,
|
||||
),
|
||||
onChangeApproveType = onChangeApproveType,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSelectFeeState(
|
||||
fee: TxFeeState?,
|
||||
previousState: SelectableItemsState<TxFee>?,
|
||||
onFeeSetup: (TxFee) -> Unit,
|
||||
): SelectableItemsState<TxFee>? {
|
||||
if (fee == null) return null
|
||||
if (previousState == null) {
|
||||
onFeeSetup.invoke(fee.normalFee) // if there is no previous state, setup normal fee by default
|
||||
val selectedItemId = 0
|
||||
// by default preselect normal
|
||||
val preselectedItem = Item(
|
||||
id = selectedItemId,
|
||||
startText = TextReference.Res(R.string.send_network_fee_title),
|
||||
endText = TextReference.Str(fee.normalFee.feeCryptoFormatted + fee.normalFee.feeFiatFormatted),
|
||||
isSelected = true,
|
||||
data = fee.normalFee,
|
||||
)
|
||||
val feeItems = mutableListOf<Item<TxFee>>()
|
||||
val normalFeeItem = Item(
|
||||
id = selectedItemId,
|
||||
startText = TextReference.Res(R.string.send_fee_picker_normal),
|
||||
endText = TextReference.Str(fee.normalFee.feeCryptoFormatted + fee.normalFee.feeFiatFormatted),
|
||||
isSelected = true,
|
||||
data = fee.normalFee,
|
||||
)
|
||||
val priorityFeeItem = Item(
|
||||
id = 1,
|
||||
startText = TextReference.Res(R.string.send_fee_picker_priority),
|
||||
endText = TextReference.Str(fee.priorityFee.feeCryptoFormatted + fee.priorityFee.feeFiatFormatted),
|
||||
isSelected = false,
|
||||
data = fee.priorityFee,
|
||||
)
|
||||
feeItems.add(normalFeeItem)
|
||||
feeItems.add(priorityFeeItem)
|
||||
return SelectableItemsState(
|
||||
selectedItem = preselectedItem,
|
||||
items = feeItems.toImmutableList(),
|
||||
)
|
||||
} else {
|
||||
val normalFeeItem =
|
||||
requireNotNull(previousState.items.firstOrNull()) { "in previousState there are 2 items" }
|
||||
.copy(
|
||||
endText = TextReference.Str(fee.normalFee.feeCryptoFormatted + fee.normalFee.feeFiatFormatted),
|
||||
)
|
||||
val priorityFeeItem =
|
||||
requireNotNull(previousState.items.getOrNull(1)) { "in previousState there are 2 items" }
|
||||
.copy(
|
||||
endText = TextReference.Str(
|
||||
fee.priorityFee.feeCryptoFormatted + fee.priorityFee.feeFiatFormatted,
|
||||
),
|
||||
)
|
||||
val selectedEndText = if (normalFeeItem.isSelected) {
|
||||
onFeeSetup.invoke(fee.normalFee)
|
||||
normalFeeItem.endText
|
||||
} else {
|
||||
onFeeSetup.invoke(fee.priorityFee)
|
||||
priorityFeeItem.endText
|
||||
is PermissionDataState.PermissionReadyForRequest -> {
|
||||
val permissionFee = when (val fee = permissionDataState.requestApproveData.fee) {
|
||||
TxFeeState.Empty -> error("Fee shouldn't be empty")
|
||||
is TxFeeState.MultipleFeeState -> fee.priorityFee
|
||||
is TxFeeState.SingleFeeState -> fee.fee
|
||||
}
|
||||
SwapPermissionState.ReadyForRequest(
|
||||
currency = permissionDataState.currency,
|
||||
amount = permissionDataState.amount,
|
||||
approveType = approveType,
|
||||
walletAddress = getShortAddressValue(permissionDataState.walletAddress),
|
||||
spenderAddress = getShortAddressValue(permissionDataState.spenderAddress),
|
||||
fee = TextReference.Str("${permissionFee.feeCryptoFormatted} (${permissionFee.feeFiatFormatted})"),
|
||||
approveButton = ApprovePermissionButton(
|
||||
enabled = true,
|
||||
onClick = onGivePermissionClick,
|
||||
),
|
||||
cancelButton = CancelPermissionButton(
|
||||
enabled = true,
|
||||
),
|
||||
onChangeApproveType = onChangeApproveType,
|
||||
)
|
||||
}
|
||||
return previousState.copy(
|
||||
selectedItem = previousState.selectedItem.copy(
|
||||
endText = selectedEndText,
|
||||
),
|
||||
items = listOf(normalFeeItem, priorityFeeItem).toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -775,6 +597,70 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
fun showSelectFeeBottomSheet(
|
||||
uiState: SwapStateHolder,
|
||||
selectedFee: FeeType,
|
||||
txFeeState: TxFeeState.MultipleFeeState,
|
||||
onDismiss: () -> Unit,
|
||||
): SwapStateHolder {
|
||||
val config = ChooseFeeBottomSheetConfig(
|
||||
selectedFee = selectedFee,
|
||||
onSelectFeeType = {
|
||||
val selectedItem = when (it) {
|
||||
FeeType.NORMAL -> txFeeState.normalFee
|
||||
FeeType.PRIORITY -> txFeeState.priorityFee
|
||||
}
|
||||
actions.onSelectFeeType.invoke(selectedItem)
|
||||
},
|
||||
feeItems = txFeeState.toFeeItemState(),
|
||||
)
|
||||
return uiState.copy(
|
||||
bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = onDismiss,
|
||||
content = config,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun updateSelectedFee(uiState: SwapStateHolder, selectedFee: FeeType): SwapStateHolder {
|
||||
val config = uiState.bottomSheetConfig?.content as? ChooseFeeBottomSheetConfig
|
||||
return if (config != null) {
|
||||
uiState.copy(
|
||||
bottomSheetConfig = uiState.bottomSheetConfig.copy(
|
||||
content = config.copy(
|
||||
selectedFee = selectedFee,
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
uiState
|
||||
}
|
||||
}
|
||||
|
||||
private fun TxFeeState.MultipleFeeState.toFeeItemState(): ImmutableList<FeeItemState.Content> {
|
||||
return listOf(
|
||||
FeeItemState.Content(
|
||||
feeType = this.normalFee.feeType,
|
||||
title = stringReference("Fee"), // todo replace with string
|
||||
amountCrypto = this.normalFee.feeCryptoFormatted,
|
||||
symbolCrypto = this.normalFee.cryptoSymbol,
|
||||
amountFiatFormatted = this.normalFee.feeFiatFormatted,
|
||||
isClickable = true,
|
||||
onClick = {},
|
||||
),
|
||||
FeeItemState.Content(
|
||||
feeType = this.priorityFee.feeType,
|
||||
title = stringReference("Fee"), // todo replace with string
|
||||
amountCrypto = this.priorityFee.feeCryptoFormatted,
|
||||
symbolCrypto = this.priorityFee.cryptoSymbol,
|
||||
amountFiatFormatted = this.priorityFee.feeFiatFormatted,
|
||||
isClickable = true,
|
||||
onClick = {},
|
||||
),
|
||||
).toImmutableList()
|
||||
}
|
||||
|
||||
private fun Map.Entry<SwapProvider, SwapState>.convertToProviderState(
|
||||
onProviderSelect: (String) -> Unit,
|
||||
): ProviderState? {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.swap.models.SwapStateHolder
|
||||
import com.tangem.feature.swap.models.states.ChooseFeeBottomSheetConfig
|
||||
import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig
|
||||
import com.tangem.feature.swap.models.states.GivePermissionBottomSheetConfig
|
||||
|
||||
|
|
@ -32,6 +33,9 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) {
|
|||
is ChooseProviderBottomSheetConfig -> {
|
||||
ChooseProviderBottomSheet(config = config)
|
||||
}
|
||||
is ChooseFeeBottomSheetConfig -> {
|
||||
ChooseFeeBottomSheet(config = config)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,20 +21,15 @@ import com.tangem.core.ui.components.*
|
|||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.components.states.Item
|
||||
import com.tangem.core.ui.components.states.SelectableItemsState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.core.ui.extensions.getActiveIconResByCoinId
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.swap.domain.models.ui.FeeType
|
||||
import com.tangem.feature.swap.domain.models.ui.TxFee
|
||||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.models.states.FeeItemState
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
|
|
@ -77,7 +72,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi
|
|||
),
|
||||
)
|
||||
|
||||
FeeItem(feeState = state.fee, currency = state.networkCurrency)
|
||||
FeeItemBlock(state = state.fee)
|
||||
|
||||
if (state.warnings.isNotEmpty()) SwapWarnings(warnings = state.warnings)
|
||||
|
||||
|
|
@ -249,49 +244,6 @@ private fun SwapButton(state: SwapStateHolder, modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeeItem(feeState: FeeState, currency: String) {
|
||||
val titleString = stringResource(id = R.string.send_network_fee_title)
|
||||
val disclaimer = stringResource(id = R.string.swapping_tangem_fee_disclaimer, "${feeState.tangemFee}%")
|
||||
when (feeState) {
|
||||
is FeeState.Loaded -> {
|
||||
if (feeState.state != null) {
|
||||
SelectableInfoCard(
|
||||
state = feeState.state,
|
||||
disclaimer = disclaimer,
|
||||
onSelect = feeState.onSelectItem,
|
||||
)
|
||||
}
|
||||
}
|
||||
FeeState.Loading -> {
|
||||
SmallInfoCardWithDisclaimer(
|
||||
startText = titleString,
|
||||
endText = "",
|
||||
disclaimer = disclaimer,
|
||||
isLoading = true,
|
||||
)
|
||||
}
|
||||
is FeeState.NotEnoughFundsWarning -> {
|
||||
if (feeState.state != null) {
|
||||
SelectableInfoCardWithWarning(
|
||||
state = feeState.state,
|
||||
warningText = stringResource(
|
||||
id = R.string.swapping_not_enough_funds_for_fee,
|
||||
currency,
|
||||
currency,
|
||||
),
|
||||
disclaimer = disclaimer,
|
||||
onSelect = feeState.onSelectItem,
|
||||
)
|
||||
}
|
||||
}
|
||||
is FeeState.Empty -> {
|
||||
// show nothing
|
||||
// SmallInfoCard(startText = titleString, endText = "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwapWarnings(warnings: List<SwapWarning>) {
|
||||
Column(
|
||||
|
|
@ -400,58 +352,18 @@ private val receiveCard = SwapCardState.SwapCardData(
|
|||
isBalanceHidden = false,
|
||||
)
|
||||
|
||||
val stateSelectable = SelectableItemsState(
|
||||
selectedItem = Item(
|
||||
0,
|
||||
TextReference.Str("Balance"),
|
||||
TextReference.Str("0.4405434 BTC"),
|
||||
true,
|
||||
TxFee(
|
||||
feeValue = BigDecimal.ZERO,
|
||||
gasLimit = 0,
|
||||
feeFiatFormatted = "",
|
||||
feeCryptoFormatted = "",
|
||||
feeType = FeeType.NORMAL,
|
||||
),
|
||||
),
|
||||
items = listOf(
|
||||
Item(
|
||||
0,
|
||||
TextReference.Str("Normal"),
|
||||
TextReference.Str("0.4405434 BTC"),
|
||||
true,
|
||||
TxFee(
|
||||
feeValue = BigDecimal.ZERO,
|
||||
gasLimit = 0,
|
||||
feeFiatFormatted = "",
|
||||
feeCryptoFormatted = "",
|
||||
feeType = FeeType.NORMAL,
|
||||
),
|
||||
),
|
||||
Item(
|
||||
1,
|
||||
TextReference.Str("Priority"),
|
||||
TextReference.Str("0.46 BTC"),
|
||||
false,
|
||||
TxFee(
|
||||
feeValue = BigDecimal.ZERO,
|
||||
gasLimit = 0,
|
||||
feeFiatFormatted = "",
|
||||
feeCryptoFormatted = "",
|
||||
feeType = FeeType.NORMAL,
|
||||
),
|
||||
),
|
||||
).toImmutableList(),
|
||||
)
|
||||
|
||||
private val state = SwapStateHolder(
|
||||
networkId = "ethereum",
|
||||
sendCardData = sendCard,
|
||||
receiveCardData = receiveCard,
|
||||
fee = FeeState.Loaded(
|
||||
tangemFee = 0.0,
|
||||
state = stateSelectable,
|
||||
onSelectItem = {},
|
||||
fee = FeeItemState.Content(
|
||||
feeType = FeeType.NORMAL,
|
||||
title = stringReference("Fee"),
|
||||
amountCrypto = "100",
|
||||
symbolCrypto = "1000",
|
||||
amountFiatFormatted = "(100)",
|
||||
isClickable = true,
|
||||
onClick = {},
|
||||
),
|
||||
warnings = listOf(
|
||||
SwapWarning.PermissionNeeded(
|
||||
|
|
|
|||
|
|
@ -1,25 +1,29 @@
|
|||
package com.tangem.feature.swap.viewmodels
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
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.domain.SwapProvider
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData
|
||||
import com.tangem.feature.swap.domain.models.ui.SwapState
|
||||
import com.tangem.feature.swap.domain.models.ui.TokensDataStateExpress
|
||||
import com.tangem.feature.swap.domain.models.ui.TxFee
|
||||
|
||||
data class SwapProcessDataState(
|
||||
// Initial network id
|
||||
val networkId: String,
|
||||
@Deprecated("used in old swap mechanism")
|
||||
val fromCurrency: Currency? = null,
|
||||
@Deprecated("used in old swap mechanism")
|
||||
val toCurrency: Currency? = null,
|
||||
val fromCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
val toCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
// Amount from input
|
||||
val amount: String? = null,
|
||||
val approveDataModel: RequestApproveStateData? = null,
|
||||
val swapDataModel: SwapStateData? = null,
|
||||
val selectedFee: TxFee? = null,
|
||||
val swapDataModel: SwapDataModel? = null,
|
||||
val selectedFee: TxFee? = null, // todo
|
||||
val tokensDataState: TokensDataStateExpress? = null,
|
||||
val selectedProvider: SwapProvider? = null,
|
||||
val lastLoadedSwapStates: Map<SwapProvider, SwapState> = emptyMap(),
|
||||
)
|
||||
) {
|
||||
|
||||
fun getCurrentLoadedSwapState(): SwapState.QuotesLoadedState? {
|
||||
return lastLoadedSwapStates[selectedProvider] as? SwapState.QuotesLoadedState
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ 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.PermissionOptions
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapDataModel
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapProvider
|
||||
import com.tangem.feature.swap.domain.models.formatToUIRepresentation
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
|
|
@ -271,17 +272,14 @@ internal class SwapViewModel @Inject constructor(
|
|||
private fun setupLoadedState(provider: SwapProvider, state: SwapState, fromToken: CryptoCurrencyStatus) {
|
||||
when (state) {
|
||||
is SwapState.QuotesLoadedState -> {
|
||||
fillDataState(state.permissionState, state.swapDataModel)
|
||||
fillLoadedDataState(state, state.permissionState, state.swapDataModel)
|
||||
uiState = stateBuilder.createQuotesLoadedState(
|
||||
uiStateHolder = uiState,
|
||||
quoteModel = state,
|
||||
fromToken = fromToken.currency,
|
||||
swapProvider = provider,
|
||||
) { updatedFee ->
|
||||
dataState = dataState.copy(
|
||||
selectedFee = updatedFee,
|
||||
)
|
||||
}
|
||||
selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||
)
|
||||
}
|
||||
is SwapState.EmptyAmountState -> {
|
||||
uiState = stateBuilder.createQuotesEmptyAmountState(
|
||||
|
|
@ -305,7 +303,11 @@ internal class SwapViewModel @Inject constructor(
|
|||
return state.entries.first { it.key == selectedSwapProvider }.toPair()
|
||||
}
|
||||
|
||||
private fun fillDataState(permissionState: PermissionDataState, swapDataModel: SwapStateData?) {
|
||||
private fun fillLoadedDataState(
|
||||
state: SwapState.QuotesLoadedState,
|
||||
permissionState: PermissionDataState,
|
||||
swapDataModel: SwapDataModel?,
|
||||
) {
|
||||
dataState = if (permissionState is PermissionDataState.PermissionReadyForRequest) {
|
||||
dataState.copy(
|
||||
approveDataModel = permissionState.requestApproveData,
|
||||
|
|
@ -313,11 +315,24 @@ internal class SwapViewModel @Inject constructor(
|
|||
} else {
|
||||
dataState.copy(
|
||||
swapDataModel = swapDataModel,
|
||||
selectedFee = swapDataModel?.fee?.normalFee,
|
||||
selectedFee = selectDefaultFee(state),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectDefaultFee(state: SwapState.QuotesLoadedState): TxFee? {
|
||||
return dataState.selectedFee
|
||||
?: when (val txFee = state.txFee) {
|
||||
TxFeeState.Empty -> null
|
||||
is TxFeeState.MultipleFeeState -> {
|
||||
txFee.normalFee
|
||||
}
|
||||
is TxFeeState.SingleFeeState -> {
|
||||
txFee.fee
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSwapClick() {
|
||||
singleTaskScheduler.cancelTask()
|
||||
uiState = stateBuilder.createSwapInProgressState(uiState)
|
||||
|
|
@ -326,7 +341,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
swapInteractor.onSwap(
|
||||
exchangeProviderType = requireNotNull(dataState.selectedProvider?.type),
|
||||
networkId = dataState.networkId,
|
||||
swapStateData = requireNotNull(dataState.swapDataModel),
|
||||
swapData = requireNotNull(dataState.swapDataModel),
|
||||
currencyToSend = requireNotNull(dataState.fromCryptoCurrency?.currency),
|
||||
currencyToGet = requireNotNull(dataState.toCryptoCurrency?.currency),
|
||||
amountToSwap = requireNotNull(dataState.amount),
|
||||
|
|
@ -539,8 +554,8 @@ internal class SwapViewModel @Inject constructor(
|
|||
onAmountChanged = { onAmountChanged(it) },
|
||||
onSwapClick = {
|
||||
onSwapClick()
|
||||
val sendTokenSymbol = dataState.fromCurrency?.symbol
|
||||
val receiveTokenSymbol = dataState.toCurrency?.symbol
|
||||
val sendTokenSymbol = dataState.fromCryptoCurrency?.currency?.symbol
|
||||
val receiveTokenSymbol = dataState.toCryptoCurrency?.currency?.symbol
|
||||
if (sendTokenSymbol != null && receiveTokenSymbol != null) {
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.ButtonSwapClicked(
|
||||
|
|
@ -552,8 +567,8 @@ internal class SwapViewModel @Inject constructor(
|
|||
},
|
||||
onGivePermissionClick = {
|
||||
givePermissionsToSwap()
|
||||
val sendTokenSymbol = dataState.fromCurrency?.symbol
|
||||
val receiveTokenSymbol = dataState.toCurrency?.symbol
|
||||
val sendTokenSymbol = dataState.fromCryptoCurrency?.currency?.symbol
|
||||
val receiveTokenSymbol = dataState.toCryptoCurrency?.currency?.symbol
|
||||
if (sendTokenSymbol != null && receiveTokenSymbol != null) {
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.ButtonPermissionApproveClicked(
|
||||
|
|
@ -590,26 +605,36 @@ internal class SwapViewModel @Inject constructor(
|
|||
onChangeApproveType = { approveType ->
|
||||
uiState = stateBuilder.updateApproveType(uiState, approveType)
|
||||
},
|
||||
onSelectItemFee = { feeItem ->
|
||||
dataState = dataState.copy(selectedFee = feeItem.data)
|
||||
val spendAmount = dataState.amount?.let { amount ->
|
||||
val fromToken = dataState.fromCryptoCurrency ?: return@let null
|
||||
swapInteractor.getSwapAmountForToken(amount, fromToken.currency)
|
||||
} ?: dataState.approveDataModel?.fromTokenAmount
|
||||
spendAmount ?: return@UiActions
|
||||
val fromToken = dataState.fromCryptoCurrency ?: return@UiActions
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
val isFeeEnough = swapInteractor.checkFeeIsEnough(
|
||||
fee = feeItem.data.feeValue,
|
||||
spendAmount = spendAmount,
|
||||
networkId = dataState.networkId,
|
||||
fromToken = fromToken.currency,
|
||||
)
|
||||
uiState = stateBuilder.updateFeeSelectedItem(uiState, feeItem, isFeeEnough)
|
||||
onClickFee = {
|
||||
val selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL
|
||||
val txFeeState = dataState.getCurrentLoadedSwapState()?.txFee as? TxFeeState.MultipleFeeState
|
||||
?: return@UiActions
|
||||
uiState = stateBuilder.showSelectFeeBottomSheet(
|
||||
uiState = uiState,
|
||||
selectedFee = selectedFee,
|
||||
txFeeState = txFeeState,
|
||||
) {
|
||||
uiState = stateBuilder.dismissBottomSheet(uiState)
|
||||
}
|
||||
},
|
||||
onSelectFeeType = {
|
||||
val state = dataState.getCurrentLoadedSwapState() ?: return@UiActions
|
||||
val fromToken = dataState.fromCryptoCurrency ?: return@UiActions
|
||||
val amountToSwap = dataState.amount ?: return@UiActions
|
||||
val selectedProvider = dataState.selectedProvider ?: return@UiActions
|
||||
uiState = stateBuilder.updateSelectedFee(uiState, it.feeType)
|
||||
dataState = dataState.copy(selectedFee = it)
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
val updatedState = swapInteractor.updateQuotesStateWithSelectedFee(
|
||||
state = state,
|
||||
selectedFee = it.feeType,
|
||||
fromToken = fromToken,
|
||||
amountToSwap = amountToSwap,
|
||||
networkId = dataState.networkId,
|
||||
)
|
||||
setupLoadedState(selectedProvider, updatedState, fromToken)
|
||||
}
|
||||
},
|
||||
onClickFee = {},
|
||||
onSelectFeeType = {},
|
||||
onProviderClick = {
|
||||
uiState = stateBuilder.showSelectProviderBottomSheet(
|
||||
uiState = uiState,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
data class ProxyFees(
|
||||
val minFee: ProxyFee,
|
||||
val normalFee: ProxyFee,
|
||||
val priorityFee: ProxyFee,
|
||||
)
|
||||
sealed class ProxyFees {
|
||||
|
||||
data class MultipleFees(
|
||||
val minFee: ProxyFee,
|
||||
val normalFee: ProxyFee,
|
||||
val priorityFee: ProxyFee,
|
||||
) : ProxyFees()
|
||||
|
||||
data class SingleFee(
|
||||
val singleFee: ProxyFee,
|
||||
) : ProxyFees()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue