Updated on 2026-08-14
This commit is contained in:
parent
b3bc56ca03
commit
52cc0be3cb
49 changed files with 1039 additions and 327 deletions
|
|
@ -44,12 +44,12 @@ class EstimateFeeForGaslessTxUseCase(
|
|||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
amount: BigDecimal,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
sendingTokenCurrencyStatus: CryptoCurrencyStatus,
|
||||
): Either<GetFeeError, TransactionFeeExtended> {
|
||||
return either {
|
||||
catch(
|
||||
block = {
|
||||
val network = cryptoCurrencyStatus.currency.network
|
||||
val network = sendingTokenCurrencyStatus.currency.network
|
||||
val nativeCurrency = currenciesRepository.getNetworkCoin(
|
||||
userWalletId = userWallet.walletId,
|
||||
networkId = network.id,
|
||||
|
|
@ -60,7 +60,7 @@ class EstimateFeeForGaslessTxUseCase(
|
|||
estimateFeeUseCase.invoke(
|
||||
userWallet = userWallet,
|
||||
amount = amount,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
cryptoCurrencyStatus = sendingTokenCurrencyStatus,
|
||||
).fold(
|
||||
ifLeft = { raise(it) },
|
||||
ifRight = { fee ->
|
||||
|
|
@ -77,7 +77,7 @@ class EstimateFeeForGaslessTxUseCase(
|
|||
val initialFee = tokenFeeCalculator.estimateInitialFee(
|
||||
userWallet = userWallet,
|
||||
amount = amount,
|
||||
tokenCurrencyStatus = cryptoCurrencyStatus,
|
||||
txTokenCurrencyStatus = sendingTokenCurrencyStatus,
|
||||
).bind()
|
||||
|
||||
selectFeePaymentStrategy(
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ import arrow.core.raise.catch
|
|||
import arrow.core.raise.either
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.domain.demo.DemoTransactionSender
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -19,10 +16,8 @@ import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
|||
import com.tangem.domain.transaction.GaslessTransactionRepository
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.error.GetFeeError.GaslessError
|
||||
import com.tangem.domain.transaction.error.mapToFeeError
|
||||
import com.tangem.domain.transaction.models.TransactionFeeExtended
|
||||
import com.tangem.domain.transaction.raiseIllegalStateError
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
@ -43,38 +38,23 @@ class EstimateFeeForTokenUseCase(
|
|||
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
tokenCurrencyStatus: CryptoCurrencyStatus,
|
||||
feeTokenCurrencyStatus: CryptoCurrencyStatus,
|
||||
sendingTokenCurrencyStatus: CryptoCurrencyStatus,
|
||||
amount: BigDecimal,
|
||||
): Either<GetFeeError, TransactionFeeExtended> {
|
||||
return either {
|
||||
catch(
|
||||
block = {
|
||||
val token = tokenCurrencyStatus.currency
|
||||
val token = feeTokenCurrencyStatus.currency
|
||||
if (!currencyChecksRepository.isNetworkSupportedForGaslessTx(token.network)) {
|
||||
raise(GetFeeError.GaslessError.NetworkIsNotSupported)
|
||||
raise(GaslessError.NetworkIsNotSupported)
|
||||
}
|
||||
|
||||
val amountData = amount.convertToSdkAmount(tokenCurrencyStatus)
|
||||
val result = if (userWallet is UserWallet.Cold &&
|
||||
demoConfig.isDemoCardId(userWallet.scanResponse.card.cardId)
|
||||
) {
|
||||
demoTransactionSender(userWallet, token).estimateFee(
|
||||
amount = amountData,
|
||||
destination = "",
|
||||
)
|
||||
} else {
|
||||
walletManagersFacade.estimateFee(
|
||||
amount = amountData,
|
||||
userWalletId = userWallet.walletId,
|
||||
network = token.network,
|
||||
)
|
||||
}
|
||||
|
||||
val initialTxFee = when (result) {
|
||||
is Result.Success -> result.data
|
||||
is Result.Failure -> raise(result.mapToFeeError())
|
||||
null -> raise(GetFeeError.UnknownError)
|
||||
}
|
||||
val initialTxFee = tokenFeeCalculator.estimateInitialFee(
|
||||
userWallet = userWallet,
|
||||
amount = amount,
|
||||
txTokenCurrencyStatus = sendingTokenCurrencyStatus,
|
||||
).bind()
|
||||
|
||||
val initialFeeEth = initialTxFee.normal as? Fee.Ethereum
|
||||
?: raiseIllegalStateError(
|
||||
|
|
@ -101,7 +81,7 @@ class EstimateFeeForTokenUseCase(
|
|||
|
||||
tokenFeeCalculator.calculateTokenFee(
|
||||
walletManager = walletManager,
|
||||
tokenForPayFeeStatus = tokenCurrencyStatus,
|
||||
tokenForPayFeeStatus = feeTokenCurrencyStatus,
|
||||
nativeCurrencyStatus = nativeCurrencyStatus,
|
||||
initialFee = initialFeeEth,
|
||||
).bind()
|
||||
|
|
@ -126,15 +106,4 @@ class EstimateFeeForTokenUseCase(
|
|||
?: raiseIllegalStateError("WalletManager type ${walletManager?.javaClass?.name} not supported")
|
||||
return ethereumWalletManager
|
||||
}
|
||||
|
||||
private suspend fun demoTransactionSender(
|
||||
userWallet: UserWallet,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): DemoTransactionSender {
|
||||
return DemoTransactionSender(
|
||||
walletManagersFacade
|
||||
.getOrCreateWalletManager(userWallet.walletId, cryptoCurrency.network)
|
||||
?: error("WalletManager is null"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -61,11 +61,11 @@ internal class TokenFeeCalculator(
|
|||
suspend fun estimateInitialFee(
|
||||
userWallet: UserWallet,
|
||||
amount: BigDecimal,
|
||||
tokenCurrencyStatus: CryptoCurrencyStatus,
|
||||
txTokenCurrencyStatus: CryptoCurrencyStatus,
|
||||
): Either<GetFeeError, TransactionFee> {
|
||||
return either {
|
||||
val network = tokenCurrencyStatus.currency.network
|
||||
val amountData = amount.convertToSdkAmount(tokenCurrencyStatus)
|
||||
val network = txTokenCurrencyStatus.currency.network
|
||||
val amountData = amount.convertToSdkAmount(txTokenCurrencyStatus)
|
||||
val result = if (userWallet is UserWallet.Cold &&
|
||||
demoConfig.isDemoCardId(userWallet.scanResponse.card.cardId)
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ class TokenFeeCalculatorTest {
|
|||
val result = tokenFeeCalculator.estimateInitialFee(
|
||||
userWallet = mockUserWallet,
|
||||
amount = amount,
|
||||
tokenCurrencyStatus = tokenStatus,
|
||||
txTokenCurrencyStatus = tokenStatus,
|
||||
)
|
||||
|
||||
// Then
|
||||
|
|
@ -154,7 +154,7 @@ class TokenFeeCalculatorTest {
|
|||
val result = tokenFeeCalculator.estimateInitialFee(
|
||||
userWallet = mockUserWallet,
|
||||
amount = amount,
|
||||
tokenCurrencyStatus = tokenStatus,
|
||||
txTokenCurrencyStatus = tokenStatus,
|
||||
)
|
||||
|
||||
// Then
|
||||
|
|
@ -173,7 +173,7 @@ class TokenFeeCalculatorTest {
|
|||
val result = tokenFeeCalculator.estimateInitialFee(
|
||||
userWallet = mockUserWallet,
|
||||
amount = amount,
|
||||
tokenCurrencyStatus = tokenStatus,
|
||||
txTokenCurrencyStatus = tokenStatus,
|
||||
)
|
||||
|
||||
// Then
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ class GetExplorerTransactionUrlUseCase(
|
|||
return either {
|
||||
catch(
|
||||
block = {
|
||||
if (txHash.isEmpty()) {
|
||||
raise(TxStatusError.EmptyUrlError)
|
||||
}
|
||||
|
||||
repository.getTxExploreUrl(txHash, networkId).ifEmpty {
|
||||
raise(TxStatusError.EmptyUrlError)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ sealed class CommonSendAnalyticEvents(
|
|||
|
||||
enum class CommonSendSource(val analyticsName: String) {
|
||||
Send("Send"),
|
||||
Swap("Swap"),
|
||||
SendWithSwap("Send&Swap"),
|
||||
WalletConnect("WalletConnect"),
|
||||
NFT("NFT"),
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ sealed class FeeSelectorParams {
|
|||
override val feeDisplaySource: FeeDisplaySource,
|
||||
override val analyticsCategoryName: String,
|
||||
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
val bottomSheetShown: (Boolean) -> Unit = {},
|
||||
) : FeeSelectorParams()
|
||||
|
||||
data class FeeSelectorDetailsParams(
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
|||
import com.tangem.features.send.v2.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun FeeBlock(feeSelectorUM: FeeSelectorUM) {
|
||||
fun FeeBlockSuccess(feeSelectorUM: FeeSelectorUM) {
|
||||
if (feeSelectorUM !is FeeSelectorUM.Content) return
|
||||
val feeExtraInfo = feeSelectorUM.feeExtraInfo
|
||||
val feeFiatRateUM = feeSelectorUM.feeFiatRateUM
|
||||
|
|
@ -65,6 +65,10 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor(
|
|||
)
|
||||
|
||||
init {
|
||||
bottomSheetSlot.subscribe {
|
||||
params.bottomSheetShown(it.child != null)
|
||||
}
|
||||
|
||||
model.uiState
|
||||
.onEach { onResult(it) }
|
||||
.launchIn(componentScope)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class FeeSelectorTokenSelectedTransformer(
|
|||
override fun transform(prevState: FeeSelectorUM): FeeSelectorUM {
|
||||
return if (prevState is FeeSelectorUM.Content) {
|
||||
prevState.copy(
|
||||
isPrimaryButtonEnabled = false,
|
||||
selectedFeeItem = FeeItem.Loading,
|
||||
feeItems = persistentListOf(FeeItem.Loading),
|
||||
feeExtraInfo = prevState.feeExtraInfo.copy(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import com.tangem.core.ui.utils.DateTimeFormatters
|
|||
import com.tangem.core.ui.utils.toPx
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.send.v2.common.ui.FeeBlock
|
||||
import com.tangem.features.send.v2.common.ui.FeeBlockSuccess
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
|
|
@ -106,7 +106,7 @@ private fun SuccessContent(
|
|||
onClick = {},
|
||||
)
|
||||
destinationBlockComponent.Content(modifier = Modifier)
|
||||
FeeBlock(feeSelectorUM = sendUM.feeSelectorUM)
|
||||
FeeBlockSuccess(feeSelectorUM = sendUM.feeSelectorUM)
|
||||
SpacerH(16.dp)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import com.tangem.core.ui.utils.toPx
|
|||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.features.nft.component.NFTDetailsBlockComponent
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.send.v2.common.ui.FeeBlock
|
||||
import com.tangem.features.send.v2.common.ui.FeeBlockSuccess
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM
|
||||
|
|
@ -109,7 +109,7 @@ private fun SuccessContent(
|
|||
}
|
||||
nftDetailsBlockComponent.Content(modifier = Modifier)
|
||||
destinationBlockComponent.Content(modifier = Modifier)
|
||||
FeeBlock(feeSelectorUM = nftSendUM.feeSelectorUM)
|
||||
FeeBlockSuccess(feeSelectorUM = nftSendUM.feeSelectorUM)
|
||||
SpacerH(16.dp)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,13 +263,14 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
estimateFeeForTokenUseCase(
|
||||
amount = amountValue,
|
||||
userWallet = params.userWallet,
|
||||
tokenCurrencyStatus = maybeToken,
|
||||
feeTokenCurrencyStatus = maybeToken,
|
||||
sendingTokenCurrencyStatus = primaryCurrencyStatus,
|
||||
)
|
||||
} else {
|
||||
estimateFeeForGaslessTxUseCase(
|
||||
amount = amountValue,
|
||||
userWallet = params.userWallet,
|
||||
cryptoCurrencyStatus = primaryCurrencyStatus,
|
||||
sendingTokenCurrencyStatus = primaryCurrencyStatus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.models.TransactionFeeExtended
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.IncludeFeeInAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.PermissionOptions
|
||||
|
|
@ -37,7 +41,7 @@ interface SwapInteractor {
|
|||
* @param providers list of providers to find quote
|
||||
* @param amountToSwap amount you want to swap
|
||||
* @param reduceBalanceBy amount to reduce from balance (used for fee calculation)
|
||||
* @param selectedFee selected fee to swap
|
||||
* @param txFeeSealedState selected fee to swap
|
||||
* @return
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -50,7 +54,7 @@ interface SwapInteractor {
|
|||
providers: List<SwapProvider>,
|
||||
amountToSwap: String,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
selectedFee: FeeType = FeeType.NORMAL,
|
||||
txFeeSealedState: TxFeeSealedState,
|
||||
): Map<SwapProvider, SwapState>
|
||||
|
||||
/**
|
||||
|
|
@ -132,6 +136,50 @@ interface SwapInteractor {
|
|||
averageDuration: Int? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* Loads fee for swap transaction
|
||||
*
|
||||
* @param fromToken token from which want to swap
|
||||
* @param fromAccount account from which swap will be made
|
||||
* @param toToken token that receive after swap
|
||||
* @param toAccount account to which receive token after swap
|
||||
* @param amount amount you want to swap
|
||||
* @param reduceBalanceBy amount to reduce from balance (used for fee calculation)
|
||||
* @param selectedFeeToken selected token to pay fee or null to pay fee with coin
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun loadFeeForSwapTransaction(
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
amount: String,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
provider: SwapProvider,
|
||||
selectedFeeToken: CryptoCurrencyStatus?,
|
||||
): Either<GetFeeError, TransactionFeeExtended>
|
||||
|
||||
/**
|
||||
* Loads fee for swap transaction
|
||||
*
|
||||
* @param fromToken token from which want to swap
|
||||
* @param fromAccount account from which swap will be made
|
||||
* @param toToken token that receive after swap
|
||||
* @param toAccount account to which receive token after swap
|
||||
* @param amount amount you want to swap
|
||||
* @param reduceBalanceBy amount to reduce from balance (used for fee calculation)
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun loadFeeForSwapTransaction(
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
amount: String,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
provider: SwapProvider,
|
||||
): Either<GetFeeError, TransactionFee>
|
||||
|
||||
interface Factory {
|
||||
fun create(selectedWalletId: UserWalletId): SwapInteractor
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,12 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel
|
||||
import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo
|
||||
import com.tangem.feature.swap.domain.models.domain.PairsWithProviders
|
||||
import com.tangem.feature.swap.domain.models.domain.QuoteModel
|
||||
import com.tangem.feature.swap.domain.models.domain.RateType
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapDataModel
|
||||
import java.math.BigDecimal
|
||||
|
||||
interface SwapRepository {
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.transaction.Fee
|
|||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||
import com.tangem.feature.swap.domain.TransactionFeeResult
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
|
|
@ -91,11 +92,11 @@ data class RequestApproveStateData(
|
|||
|
||||
sealed class TxFeeState {
|
||||
data class MultipleFeeState(
|
||||
val normalFee: TxFee,
|
||||
val priorityFee: TxFee,
|
||||
val normalFee: TxFee.Legacy,
|
||||
val priorityFee: TxFee.Legacy,
|
||||
) : TxFeeState() {
|
||||
|
||||
fun getFeeByType(feeType: FeeType): TxFee {
|
||||
fun getFeeByType(feeType: FeeType): TxFee.Legacy {
|
||||
return when (feeType) {
|
||||
FeeType.NORMAL -> normalFee
|
||||
FeeType.PRIORITY -> priorityFee
|
||||
|
|
@ -104,23 +105,33 @@ sealed class TxFeeState {
|
|||
}
|
||||
|
||||
data class SingleFeeState(
|
||||
val fee: TxFee,
|
||||
val fee: TxFee.Legacy,
|
||||
) : TxFeeState()
|
||||
|
||||
data object Empty : TxFeeState()
|
||||
}
|
||||
|
||||
data class TxFee(
|
||||
val feeValue: BigDecimal,
|
||||
val feeFiatFormatted: String,
|
||||
val feeCryptoFormatted: String,
|
||||
val feeIncludeOtherNativeFee: BigDecimal,
|
||||
val feeFiatFormattedWithNative: String,
|
||||
val feeCryptoFormattedWithNative: String,
|
||||
val cryptoSymbol: String,
|
||||
val feeType: FeeType,
|
||||
val fee: Fee,
|
||||
)
|
||||
sealed class TxFee {
|
||||
abstract val fee: Fee
|
||||
|
||||
data class FeeComponent(
|
||||
override val fee: Fee,
|
||||
val transactionFeeResult: TransactionFeeResult,
|
||||
val selectedToken: CryptoCurrencyStatus?,
|
||||
) : TxFee()
|
||||
|
||||
data class Legacy(
|
||||
val feeValue: BigDecimal,
|
||||
val feeFiatFormatted: String,
|
||||
val feeCryptoFormatted: String,
|
||||
val feeIncludeOtherNativeFee: BigDecimal,
|
||||
val feeFiatFormattedWithNative: String,
|
||||
val feeCryptoFormattedWithNative: String,
|
||||
val cryptoSymbol: String,
|
||||
val feeType: FeeType,
|
||||
override val fee: Fee,
|
||||
) : TxFee()
|
||||
}
|
||||
|
||||
enum class FeeType {
|
||||
NORMAL, PRIORITY
|
||||
|
|
@ -58,6 +58,8 @@ dependencies {
|
|||
implementation(projects.features.swap.domain.models)
|
||||
implementation(projects.features.wallet.api)
|
||||
implementation(projects.features.swap.api)
|
||||
implementation(projects.features.sendV2.api)
|
||||
implementation(projects.features.sendV2.impl)
|
||||
|
||||
/** AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
|
|
@ -87,6 +89,10 @@ dependencies {
|
|||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.decompose.ext.compose)
|
||||
|
||||
/** Tangem libs */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
|
|
|||
|
|
@ -3,17 +3,31 @@ package com.tangem.feature.swap
|
|||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.arkivanov.essenty.lifecycle.subscribe
|
||||
import com.tangem.common.ui.swapStoriesScreen.SwapStoriesScreen
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent
|
||||
import com.tangem.feature.swap.model.SwapModel
|
||||
import com.tangem.feature.swap.models.SwapCardState.SwapCardData
|
||||
import com.tangem.feature.swap.router.SwapNavScreen
|
||||
import com.tangem.feature.swap.ui.SwapScreen
|
||||
import com.tangem.feature.swap.ui.SwapSelectTokenScreen
|
||||
import com.tangem.feature.swap.ui.SwapSuccessScreen
|
||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.swap.SwapComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -22,7 +36,9 @@ import dagger.assisted.AssistedInject
|
|||
@Suppress("UnusedPrivateMember")
|
||||
internal class DefaultSwapComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: SwapComponent.Params,
|
||||
@Assisted private val params: SwapComponent.Params,
|
||||
private val swapFeeSelectorBlockComponentFactory: SwapFeeSelectorBlockComponent.Factory,
|
||||
private val sendFeatureToggles: SendFeatureToggles,
|
||||
) : SwapComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: SwapModel = getOrCreateModel(params)
|
||||
|
|
@ -34,8 +50,71 @@ internal class DefaultSwapComponent @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
val slotNavigation = SlotNavigation<FeeSelectorConfig>()
|
||||
val childSlot = childSlot(
|
||||
source = slotNavigation,
|
||||
serializer = null,
|
||||
childFactory = { config, context ->
|
||||
createSwapFeeSelectorBlockComponent(
|
||||
context = childByContext(context),
|
||||
config = config,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
private fun createSwapFeeSelectorBlockComponent(
|
||||
context: AppComponentContext,
|
||||
config: FeeSelectorConfig,
|
||||
): SwapFeeSelectorBlockComponent {
|
||||
return swapFeeSelectorBlockComponentFactory.create(
|
||||
context = context,
|
||||
params = SwapFeeSelectorBlockComponent.Params(
|
||||
repository = model.feeSelectorRepository,
|
||||
userWalletId = params.userWalletId,
|
||||
sendingCryptoCurrencyStatus = config.sendingCurrencyStatus,
|
||||
feeCryptoCurrencyStatus = config.feeCurrencyStatus,
|
||||
analyticsParams = SwapFeeSelectorBlockComponent.AnalyticsParams(
|
||||
analyticsCategoryName = CommonSendAnalyticEvents.SEND_CATEGORY,
|
||||
analyticsSendSource = CommonSendAnalyticEvents.CommonSendSource.Swap,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
data class FeeSelectorConfig(
|
||||
val sendingCurrencyStatus: CryptoCurrencyStatus,
|
||||
val feeCurrencyStatus: CryptoCurrencyStatus,
|
||||
)
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
if (sendFeatureToggles.isGaslessTransactionsEnabled) {
|
||||
val sendCardData = model.uiState.sendCardData as? SwapCardData
|
||||
val feePaidCryptoCurrency = model.dataState.feePaidCryptoCurrency
|
||||
LaunchedEffect(sendCardData?.token?.currency, feePaidCryptoCurrency?.currency) {
|
||||
val sendingCryptoCurrencyStatus = sendCardData?.token ?: run {
|
||||
slotNavigation.dismiss()
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
val feeCurrencyStatus = feePaidCryptoCurrency ?: run {
|
||||
slotNavigation.dismiss()
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
slotNavigation.activate(
|
||||
FeeSelectorConfig(
|
||||
sendingCurrencyStatus = sendingCryptoCurrencyStatus,
|
||||
feeCurrencyStatus = feeCurrencyStatus,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val feeSelectorChildStackState by childSlot.subscribeAsState()
|
||||
val feeSelectorBlockComponent = feeSelectorChildStackState.child?.instance
|
||||
|
||||
Crossfade(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.secondary),
|
||||
targetState = model.currentScreen,
|
||||
|
|
@ -47,16 +126,30 @@ internal class DefaultSwapComponent @AssistedInject constructor(
|
|||
if (storiesConfig != null) {
|
||||
SwapStoriesScreen(config = storiesConfig)
|
||||
} else {
|
||||
SwapScreen(stateHolder = model.uiState)
|
||||
SwapScreen(
|
||||
stateHolder = model.uiState,
|
||||
feeSelectorBlockComponent = feeSelectorBlockComponent,
|
||||
)
|
||||
}
|
||||
}
|
||||
SwapNavScreen.Main -> SwapScreen(stateHolder = model.uiState)
|
||||
SwapNavScreen.Main -> SwapScreen(
|
||||
stateHolder = model.uiState,
|
||||
feeSelectorBlockComponent = feeSelectorBlockComponent,
|
||||
)
|
||||
SwapNavScreen.Success -> {
|
||||
val successState = model.uiState.successState
|
||||
val feeSelectorState by model.feeSelectorRepository.state.collectAsStateWithLifecycle()
|
||||
if (successState != null) {
|
||||
SwapSuccessScreen(state = successState, model.uiState.onBackClicked)
|
||||
SwapSuccessScreen(
|
||||
state = successState,
|
||||
feeSelectorUM = feeSelectorState,
|
||||
onBack = model.uiState.onBackClicked,
|
||||
)
|
||||
} else {
|
||||
SwapScreen(stateHolder = model.uiState)
|
||||
SwapScreen(
|
||||
stateHolder = model.uiState,
|
||||
feeSelectorBlockComponent = feeSelectorBlockComponent,
|
||||
)
|
||||
}
|
||||
}
|
||||
SwapNavScreen.SelectToken -> {
|
||||
|
|
@ -64,7 +157,10 @@ internal class DefaultSwapComponent @AssistedInject constructor(
|
|||
if (tokenState != null) {
|
||||
SwapSelectTokenScreen(state = tokenState, onBack = model.uiState.onBackClicked)
|
||||
} else {
|
||||
SwapScreen(stateHolder = model.uiState)
|
||||
SwapScreen(
|
||||
stateHolder = model.uiState,
|
||||
feeSelectorBlockComponent = feeSelectorBlockComponent,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
package com.tangem.feature.swap.component
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.models.TransactionFeeExtended
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
class SwapFeeSelectorBlockComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: Params,
|
||||
feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
|
||||
) : AppComponentContext by appComponentContext, ComposableContentComponent {
|
||||
|
||||
private val feeSelectorBlockComponent =
|
||||
feeSelectorBlockComponentFactory.create(
|
||||
context = child("swapFeeSelectorBlock"),
|
||||
params = FeeSelectorParams.FeeSelectorBlockParams(
|
||||
state = params.repository.state.value,
|
||||
userWalletId = params.userWalletId,
|
||||
onLoadFee = params.repository::loadFee,
|
||||
onLoadFeeExtended = if (params.repository is ModelRepositoryExtended) {
|
||||
params.repository::loadFeeExtended
|
||||
} else {
|
||||
null
|
||||
},
|
||||
feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen,
|
||||
feeStateConfiguration = FeeSelectorParams.FeeStateConfiguration.ExcludeLow,
|
||||
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
|
||||
cryptoCurrencyStatus = params.sendingCryptoCurrencyStatus,
|
||||
analyticsCategoryName = params.analyticsParams.analyticsCategoryName,
|
||||
analyticsSendSource = params.analyticsParams.analyticsSendSource,
|
||||
bottomSheetShown = params.repository::choosingInProgress,
|
||||
),
|
||||
onResult = params.repository::onResult,
|
||||
)
|
||||
|
||||
init {
|
||||
params.repository.state
|
||||
.onEach(feeSelectorBlockComponent::updateState)
|
||||
.launchIn(componentScope)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
feeSelectorBlockComponent.Content(modifier = modifier)
|
||||
}
|
||||
|
||||
interface ModelRepository {
|
||||
val state: StateFlow<FeeSelectorUM>
|
||||
get() = MutableStateFlow<FeeSelectorUM>(FeeSelectorUM.Loading)
|
||||
|
||||
fun onResult(newState: FeeSelectorUM)
|
||||
|
||||
suspend fun loadFee(): Either<GetFeeError, TransactionFee>
|
||||
|
||||
fun choosingInProgress(updatedState: Boolean)
|
||||
}
|
||||
|
||||
interface ModelRepositoryExtended : ModelRepository {
|
||||
suspend fun loadFeeExtended(
|
||||
selectedToken: CryptoCurrencyStatus? = null,
|
||||
): Either<GetFeeError, TransactionFeeExtended>
|
||||
}
|
||||
|
||||
class AnalyticsParams(
|
||||
val analyticsCategoryName: String,
|
||||
val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
)
|
||||
|
||||
class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val sendingCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val analyticsParams: AnalyticsParams,
|
||||
val repository: ModelRepository,
|
||||
)
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : ComponentFactory<Params, SwapFeeSelectorBlockComponent>
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.setValue
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState.InProgress.getApproveTypeOrNull
|
||||
|
|
@ -52,17 +53,23 @@ import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
|||
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.models.TransactionFeeExtended
|
||||
import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.swap.analytics.StoriesEvents
|
||||
import com.tangem.feature.swap.analytics.SwapEvents
|
||||
import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.feature.swap.domain.TransactionFeeResult
|
||||
import com.tangem.feature.swap.domain.TxFeeSealedState
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.ExpressException
|
||||
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 com.tangem.feature.swap.models.SwapCardState
|
||||
import com.tangem.feature.swap.models.SwapStateHolder
|
||||
import com.tangem.feature.swap.models.UiActions
|
||||
import com.tangem.feature.swap.models.market.SwapMarketsListBatchFlowManager
|
||||
|
|
@ -73,6 +80,9 @@ import com.tangem.feature.swap.router.SwapNavScreen
|
|||
import com.tangem.feature.swap.router.SwapRouter
|
||||
import com.tangem.feature.swap.ui.StateBuilder
|
||||
import com.tangem.feature.swap.utils.formatToUIRepresentation
|
||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
|
||||
import com.tangem.features.swap.SwapComponent
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
|
|
@ -124,6 +134,8 @@ internal class SwapModel @Inject constructor(
|
|||
private val getTangemPayCurrencyStatusUseCase: GetTangemPayCurrencyStatusUseCase,
|
||||
private val tangemPayWithdrawUseCase: TangemPayWithdrawUseCase,
|
||||
private val iGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork,
|
||||
private val feeSelectorReloadTrigger: FeeSelectorReloadTrigger,
|
||||
private val sendFeatureToggles: SendFeatureToggles,
|
||||
private val getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
) : Model() {
|
||||
|
|
@ -169,7 +181,7 @@ internal class SwapModel @Inject constructor(
|
|||
private val searchDebouncer = Debouncer()
|
||||
private val singleTaskScheduler = SingleTaskScheduler<Map<SwapProvider, SwapState>>()
|
||||
|
||||
private var dataState by mutableStateOf(SwapProcessDataState())
|
||||
var dataState by mutableStateOf(SwapProcessDataState())
|
||||
|
||||
var uiState: SwapStateHolder by mutableStateOf(
|
||||
stateBuilder.createInitialLoadingState(
|
||||
|
|
@ -180,6 +192,8 @@ internal class SwapModel @Inject constructor(
|
|||
)
|
||||
private set
|
||||
|
||||
val feeSelectorRepository = FeeSelectorRepository()
|
||||
|
||||
// shows currency order (direct - swap initial to selected, reversed = selected to initial)
|
||||
private var isOrderReversed = false
|
||||
private val lastAmount = mutableStateOf(INITIAL_AMOUNT)
|
||||
|
|
@ -359,6 +373,7 @@ internal class SwapModel @Inject constructor(
|
|||
analyticsEventHandler.send(SwapEvents.ChooseTokenScreenOpened(hasAvailableTokens = isAnyAvailableTokens))
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun initTokens(isReverseFromTo: Boolean) {
|
||||
modelScope.launch(dispatchers.main) {
|
||||
runCatching(dispatchers.io) {
|
||||
|
|
@ -394,6 +409,21 @@ internal class SwapModel @Inject constructor(
|
|||
isReverseFromTo = isReverseFromTo,
|
||||
)
|
||||
|
||||
val fromCryptoCurrency = if (isOrderReversed) {
|
||||
dataState.toCryptoCurrency
|
||||
} else {
|
||||
dataState.fromCryptoCurrency
|
||||
}
|
||||
|
||||
fromCryptoCurrency?.let { cryptoCurrency ->
|
||||
dataState = dataState.copy(
|
||||
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrency,
|
||||
).getOrNull(),
|
||||
)
|
||||
}
|
||||
|
||||
(dataState.fromCryptoCurrency?.currency as? CryptoCurrency.Coin)?.let { coin ->
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -524,6 +554,7 @@ internal class SwapModel @Inject constructor(
|
|||
reduceBalanceBy: BigDecimal,
|
||||
toProvidersList: List<SwapProvider>,
|
||||
isSilent: Boolean = false,
|
||||
updateFeeBlock: Boolean = true,
|
||||
) {
|
||||
singleTaskScheduler.cancelTask()
|
||||
if (!isSilent) {
|
||||
|
|
@ -535,6 +566,7 @@ internal class SwapModel @Inject constructor(
|
|||
toAccount = toAccount,
|
||||
mainTokenId = initialCurrencyFrom.id.value,
|
||||
)
|
||||
feeSelectorRepository.state.value = FeeSelectorUM.Loading
|
||||
}
|
||||
singleTaskScheduler.scheduleTask(
|
||||
modelScope,
|
||||
|
|
@ -546,11 +578,12 @@ internal class SwapModel @Inject constructor(
|
|||
amount = amount,
|
||||
reduceBalanceBy = reduceBalanceBy,
|
||||
toProvidersList = toProvidersList,
|
||||
updateFeeBlock = updateFeeBlock,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun startLoadingQuotesFromLastState(isSilent: Boolean = false) {
|
||||
private fun startLoadingQuotesFromLastState(isSilent: Boolean = false, updateFeeBlock: Boolean = true) {
|
||||
val fromCurrency = dataState.fromCryptoCurrency
|
||||
val toCurrency = dataState.toCryptoCurrency
|
||||
val amount = dataState.amount
|
||||
|
|
@ -564,6 +597,7 @@ internal class SwapModel @Inject constructor(
|
|||
isSilent = isSilent,
|
||||
reduceBalanceBy = dataState.reduceBalanceBy,
|
||||
toProvidersList = findSwapProviders(fromCurrency, toCurrency),
|
||||
updateFeeBlock = updateFeeBlock,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -576,6 +610,7 @@ internal class SwapModel @Inject constructor(
|
|||
amount: String,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
toProvidersList: List<SwapProvider>,
|
||||
updateFeeBlock: Boolean = true,
|
||||
): PeriodicTask<Map<SwapProvider, SwapState>> {
|
||||
return PeriodicTask(
|
||||
delay = UPDATE_DELAY,
|
||||
|
|
@ -596,7 +631,7 @@ internal class SwapModel @Inject constructor(
|
|||
providers = toProvidersList,
|
||||
amountToSwap = amount,
|
||||
reduceBalanceBy = reduceBalanceBy,
|
||||
selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||
txFeeSealedState = getSelectedFeeState(),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -612,12 +647,17 @@ internal class SwapModel @Inject constructor(
|
|||
tokenSwapInfoForProviders = successStates.entries
|
||||
.associate { it.key.providerId to it.value.toTokenInfo },
|
||||
)
|
||||
if (updateFeeBlock) {
|
||||
modelScope.launch { feeSelectorReloadTrigger.triggerUpdate() }
|
||||
}
|
||||
} else {
|
||||
feeSelectorRepository.state.value = FeeSelectorUM.Error(GetFeeError.UnknownError)
|
||||
Timber.e("Accidentally empty quotes list")
|
||||
}
|
||||
},
|
||||
onError = { error ->
|
||||
Timber.e("Error when loading quotes: $error")
|
||||
feeSelectorRepository.state.value = FeeSelectorUM.Error(GetFeeError.UnknownError)
|
||||
uiState = stateBuilder.addNotification(uiState, null) { startLoadingQuotesFromLastState() }
|
||||
},
|
||||
)
|
||||
|
|
@ -657,7 +697,7 @@ internal class SwapModel @Inject constructor(
|
|||
swapProvider = provider,
|
||||
bestRatedProviderId = bestRatedProviderId,
|
||||
isNeedBestRateBadge = dataState.lastLoadedSwapStates.consideredProvidersStates().size > 1,
|
||||
selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||
selectedFeeType = (getSelectedFee() as? TxFee.Legacy)?.feeType ?: FeeType.NORMAL,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
|
||||
hideFee = tangemPayInput?.isWithdrawal == true,
|
||||
|
|
@ -799,8 +839,8 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateOrSelectFee(state: SwapState.QuotesLoadedState): TxFee? {
|
||||
val selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL
|
||||
private fun updateOrSelectFee(state: SwapState.QuotesLoadedState): TxFee.Legacy? {
|
||||
val selectedFeeType = (getSelectedFee() as? TxFee.Legacy)?.feeType ?: FeeType.NORMAL
|
||||
return when (val txFee = state.txFee) {
|
||||
TxFeeState.Empty -> null
|
||||
is TxFeeState.MultipleFeeState -> {
|
||||
|
|
@ -827,7 +867,7 @@ internal class SwapModel @Inject constructor(
|
|||
return
|
||||
}
|
||||
val fromCurrency = requireNotNull(dataState.fromCryptoCurrency)
|
||||
val fee = dataState.selectedFee
|
||||
val fee = getSelectedFee()
|
||||
|
||||
if (fee == null && tangemPayInput?.isWithdrawal != true) {
|
||||
makeDefaultAlert(resourceReference(R.string.swapping_fee_estimation_error_text))
|
||||
|
|
@ -855,7 +895,10 @@ internal class SwapModel @Inject constructor(
|
|||
makeDefaultAlert(resourceReference(R.string.swapping_fee_estimation_error_text))
|
||||
return@onSuccess
|
||||
}
|
||||
sendSuccessSwapEvent(fromCurrency.currency, fee.feeType)
|
||||
sendSuccessSwapEvent(
|
||||
fromCurrency.currency,
|
||||
(getSelectedFee() as? TxFee.Legacy)?.feeType ?: FeeType.NORMAL,
|
||||
)
|
||||
val url = getExplorerTransactionUrlUseCase(
|
||||
txHash = swapTransactionState.txHash,
|
||||
networkId = fromCurrency.currency.network.id,
|
||||
|
|
@ -967,7 +1010,7 @@ internal class SwapModel @Inject constructor(
|
|||
|
||||
private fun sendSuccessEvent() {
|
||||
val provider = dataState.selectedProvider ?: return
|
||||
val fee = dataState.selectedFee?.feeType ?: return
|
||||
val fee = (getSelectedFee() as? TxFee.Legacy)?.feeType ?: FeeType.NORMAL
|
||||
val fromCurrency = dataState.fromCryptoCurrency?.currency ?: return
|
||||
val toCurrency = dataState.toCryptoCurrency?.currency ?: return
|
||||
val fromDerivationIndex = dataState.fromAccount?.derivationIndex?.value
|
||||
|
|
@ -1027,6 +1070,7 @@ internal class SwapModel @Inject constructor(
|
|||
}.onSuccess { swapTransactionState ->
|
||||
when (swapTransactionState) {
|
||||
is SwapTransactionState.TxSent -> {
|
||||
// TODO [REDACTED_TASK_KEY] gasless analytics
|
||||
sendApproveSuccessEvent(fromToken, feeForPermission.feeType, approveType)
|
||||
updateWalletBalance()
|
||||
uiState = stateBuilder.loadingPermissionState(uiState)
|
||||
|
|
@ -1242,12 +1286,14 @@ internal class SwapModel @Inject constructor(
|
|||
.onEach { (account, currencyStatus) ->
|
||||
Timber.d("${coin.id} balance is ${currencyStatus.value.amount ?: "null"}")
|
||||
|
||||
dataState = dataState.copy(
|
||||
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = currencyStatus,
|
||||
).getOrNull() ?: currencyStatus,
|
||||
)
|
||||
if (isFromCurrency) {
|
||||
dataState = dataState.copy(
|
||||
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = currencyStatus,
|
||||
).getOrNull() ?: currencyStatus,
|
||||
)
|
||||
}
|
||||
|
||||
uiState = when {
|
||||
isFromCurrency && currencyStatus.currency.id == dataState.fromCryptoCurrency?.currency?.id -> {
|
||||
|
|
@ -1280,12 +1326,14 @@ internal class SwapModel @Inject constructor(
|
|||
.onEach { status ->
|
||||
Timber.d("${coin.id} balance is ${status.value.amount ?: "null"}")
|
||||
|
||||
dataState = dataState.copy(
|
||||
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = status,
|
||||
).getOrNull() ?: status,
|
||||
)
|
||||
if (isFromCurrency) {
|
||||
dataState = dataState.copy(
|
||||
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = status,
|
||||
).getOrNull() ?: status,
|
||||
)
|
||||
}
|
||||
|
||||
uiState = when {
|
||||
isFromCurrency && status.currency.id == dataState.fromCryptoCurrency?.currency?.id -> {
|
||||
|
|
@ -1492,7 +1540,7 @@ internal class SwapModel @Inject constructor(
|
|||
uiState = stateBuilder.updateApproveType(uiState, approveType)
|
||||
},
|
||||
onClickFee = {
|
||||
val selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL
|
||||
val selectedFee = (getSelectedFee() as? TxFee.Legacy)?.feeType ?: FeeType.NORMAL
|
||||
val txFeeState =
|
||||
dataState.getCurrentLoadedSwapState()?.txFee as? TxFeeState.MultipleFeeState ?: return@UiActions
|
||||
uiState = stateBuilder.showSelectFeeBottomSheet(
|
||||
|
|
@ -1503,9 +1551,9 @@ internal class SwapModel @Inject constructor(
|
|||
uiState = stateBuilder.dismissBottomSheet(uiState)
|
||||
}
|
||||
},
|
||||
onSelectFeeType = { feeType ->
|
||||
onSelectFeeType = { txFee ->
|
||||
uiState = stateBuilder.dismissBottomSheet(uiState)
|
||||
dataState = dataState.copy(selectedFee = feeType)
|
||||
dataState = dataState.copy(selectedFee = txFee)
|
||||
modelScope.launch(dispatchers.io) {
|
||||
startLoadingQuotesFromLastState(false)
|
||||
}
|
||||
|
|
@ -1527,6 +1575,10 @@ internal class SwapModel @Inject constructor(
|
|||
val swapState = dataState.lastLoadedSwapStates[provider]
|
||||
val fromToken = dataState.fromCryptoCurrency
|
||||
if (provider != null && swapState != null && fromToken != null) {
|
||||
modelScope.launch {
|
||||
feeSelectorRepository.state.value = FeeSelectorUM.Loading
|
||||
feeSelectorReloadTrigger.triggerUpdate()
|
||||
}
|
||||
analyticsEventHandler.send(SwapEvents.ProviderChosen(provider))
|
||||
uiState = stateBuilder.dismissBottomSheet(uiState)
|
||||
setupLoadedState(
|
||||
|
|
@ -1860,7 +1912,11 @@ internal class SwapModel @Inject constructor(
|
|||
destinationAddress = transaction?.txTo.orEmpty(),
|
||||
tokenSymbol = fromCurrencyStatus.currency.symbol,
|
||||
amount = dataState.amount.orEmpty(),
|
||||
fee = dataState.selectedFee?.feeCryptoFormatted.orEmpty(),
|
||||
fee = when (val fee = getSelectedFee()) {
|
||||
is TxFee.FeeComponent -> fee.fee.amount.value?.toString()
|
||||
is TxFee.Legacy -> fee.feeCryptoFormatted
|
||||
null -> ""
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -1900,6 +1956,137 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getSelectedFeeState(): TxFeeSealedState {
|
||||
if (!sendFeatureToggles.isGaslessTransactionsEnabled) {
|
||||
return TxFeeSealedState.Legacy(
|
||||
txFeeState = TxFeeState.Empty,
|
||||
selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||
)
|
||||
}
|
||||
|
||||
val feeStateUM = feeSelectorRepository.state.value as? FeeSelectorUM.Content
|
||||
?: return TxFeeSealedState.Legacy(
|
||||
txFeeState = TxFeeState.Empty,
|
||||
selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||
)
|
||||
|
||||
val transactionFeeExtended = feeStateUM.feeExtraInfo.transactionFeeExtended
|
||||
return TxFeeSealedState.Component(
|
||||
txFee = TxFee.FeeComponent(
|
||||
transactionFeeResult = transactionFeeExtended?.let { TransactionFeeResult.from(it) }
|
||||
?: TransactionFeeResult.from(feeStateUM.fees),
|
||||
fee = feeStateUM.selectedFeeItem.fee,
|
||||
selectedToken = feeStateUM.feeExtraInfo.feeCryptoCurrencyStatus,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSelectedFee(): TxFee? {
|
||||
if (!sendFeatureToggles.isGaslessTransactionsEnabled) {
|
||||
return dataState.selectedFee
|
||||
}
|
||||
|
||||
val feeStateUM = feeSelectorRepository.state.value as? FeeSelectorUM.Content ?: return null
|
||||
val transactionFeeExtended = feeStateUM.feeExtraInfo.transactionFeeExtended
|
||||
|
||||
return TxFee.FeeComponent(
|
||||
transactionFeeResult = transactionFeeExtended?.let { TransactionFeeResult.from(it) }
|
||||
?: TransactionFeeResult.from(feeStateUM.fees),
|
||||
fee = feeStateUM.selectedFeeItem.fee,
|
||||
selectedToken = feeStateUM.feeExtraInfo.feeCryptoCurrencyStatus,
|
||||
)
|
||||
}
|
||||
|
||||
inner class FeeSelectorRepository : SwapFeeSelectorBlockComponent.ModelRepositoryExtended {
|
||||
|
||||
override val state = MutableStateFlow<FeeSelectorUM>(FeeSelectorUM.Loading)
|
||||
|
||||
override suspend fun loadFeeExtended(
|
||||
selectedToken: CryptoCurrencyStatus?,
|
||||
): Either<GetFeeError, TransactionFeeExtended> {
|
||||
val sendCardData =
|
||||
uiState.sendCardData as? SwapCardState.SwapCardData ?: return Either.Left(GetFeeError.UnknownError)
|
||||
val receiveCardData =
|
||||
uiState.receiveCardData as? SwapCardState.SwapCardData ?: return Either.Left(GetFeeError.UnknownError)
|
||||
val fromToken = sendCardData.token ?: return Either.Left(GetFeeError.UnknownError)
|
||||
val toToken = receiveCardData.token ?: return Either.Left(GetFeeError.UnknownError)
|
||||
val selectedProvider = dataState.selectedProvider ?: return Either.Left(GetFeeError.UnknownError)
|
||||
|
||||
if (dataState.lastLoadedSwapStates[selectedProvider] !is SwapState.QuotesLoadedState) {
|
||||
return Either.Left(GetFeeError.UnknownError)
|
||||
}
|
||||
|
||||
return swapInteractor.loadFeeForSwapTransaction(
|
||||
fromToken = fromToken,
|
||||
fromAccount = dataState.fromAccount,
|
||||
toToken = toToken,
|
||||
toAccount = dataState.toAccount,
|
||||
provider = selectedProvider,
|
||||
amount = lastAmount.value,
|
||||
reduceBalanceBy = lastReducedBalanceBy.value,
|
||||
selectedFeeToken = selectedToken,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResult(newState: FeeSelectorUM) {
|
||||
state.value = newState
|
||||
|
||||
// If fee currency is same as from currency, we need to reload quotes to update fee info
|
||||
if (newState is FeeSelectorUM.Content &&
|
||||
dataState.fromCryptoCurrency?.currency?.id == newState.feeExtraInfo.feeCryptoCurrencyStatus.currency.id
|
||||
) {
|
||||
// block swap button until fee is loaded
|
||||
uiState = uiState.copy(
|
||||
swapButton = uiState.swapButton.copy(
|
||||
isEnabled = false,
|
||||
),
|
||||
)
|
||||
modelScope.launch {
|
||||
startLoadingQuotesFromLastState(
|
||||
isSilent = true,
|
||||
updateFeeBlock = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadFee(): Either<GetFeeError, TransactionFee> {
|
||||
val sendCardData =
|
||||
uiState.sendCardData as? SwapCardState.SwapCardData ?: return Either.Left(GetFeeError.UnknownError)
|
||||
val receiveCardData =
|
||||
uiState.receiveCardData as? SwapCardState.SwapCardData ?: return Either.Left(GetFeeError.UnknownError)
|
||||
val fromToken = sendCardData.token ?: return Either.Left(GetFeeError.UnknownError)
|
||||
val toToken = receiveCardData.token ?: return Either.Left(GetFeeError.UnknownError)
|
||||
val selectedProvider = dataState.selectedProvider ?: return Either.Left(GetFeeError.UnknownError)
|
||||
|
||||
if (dataState.lastLoadedSwapStates[selectedProvider] !is SwapState.QuotesLoadedState) {
|
||||
return Either.Left(GetFeeError.UnknownError)
|
||||
}
|
||||
|
||||
return swapInteractor.loadFeeForSwapTransaction(
|
||||
fromToken = fromToken,
|
||||
fromAccount = dataState.fromAccount,
|
||||
toToken = toToken,
|
||||
toAccount = dataState.toAccount,
|
||||
provider = selectedProvider,
|
||||
amount = lastAmount.value,
|
||||
reduceBalanceBy = lastReducedBalanceBy.value,
|
||||
)
|
||||
}
|
||||
|
||||
override fun choosingInProgress(updatedState: Boolean) {
|
||||
// We shouldn't load quotes while user is choosing fee
|
||||
if (updatedState) {
|
||||
singleTaskScheduler.cancelTask()
|
||||
} else {
|
||||
startLoadingQuotesFromLastState(
|
||||
isSilent = true,
|
||||
updateFeeBlock = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val INITIAL_AMOUNT = ""
|
||||
const val UPDATE_DELAY = 10000L
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
|||
import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType
|
||||
import com.tangem.feature.swap.domain.models.domain.IncludeFeeInAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapFeeState
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
|
|
@ -164,7 +165,7 @@ internal class SwapNotificationsFactory(
|
|||
|
||||
addExistentialWarningNotification(
|
||||
existentialDeposit = quoteModel.currencyCheck?.existentialDeposit,
|
||||
feeAmount = fee?.feeValue.orZero(),
|
||||
feeAmount = fee?.fee?.amount?.value.orZero(),
|
||||
sendingAmount = amountToRequest.value,
|
||||
cryptoCurrencyStatus = fromCurrencyStatus,
|
||||
onReduceClick = { reduceBy, reduceByDiff, _ ->
|
||||
|
|
@ -187,7 +188,7 @@ internal class SwapNotificationsFactory(
|
|||
if (!isCardano) {
|
||||
addDustWarningNotification(
|
||||
dustValue = quoteModel.currencyCheck?.dustValue,
|
||||
feeValue = fee?.feeValue.orZero(),
|
||||
feeValue = fee?.fee?.amount?.value.orZero(),
|
||||
sendingAmount = amountToRequest.value,
|
||||
cryptoCurrencyStatus = fromCurrencyStatus,
|
||||
feeCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
|
|
@ -275,7 +276,7 @@ internal class SwapNotificationsFactory(
|
|||
}
|
||||
}
|
||||
|
||||
private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): TxFee? {
|
||||
private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): TxFee.Legacy? {
|
||||
return when (txFeeState) {
|
||||
TxFeeState.Empty -> null
|
||||
is TxFeeState.SingleFeeState -> txFeeState.fee
|
||||
|
|
@ -296,7 +297,9 @@ internal class SwapNotificationsFactory(
|
|||
val shouldShowCoverWarning = quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||
quoteModel.permissionState !is PermissionDataState.PermissionLoading &&
|
||||
feeEnoughState.feeCurrency != fromToken
|
||||
val isGaslessAvailable = iGaslessFeeSupportedForNetwork(fromToken.network)
|
||||
|
||||
val isGaslessAvailable = iGaslessFeeSupportedForNetwork(fromToken.network) &&
|
||||
quoteModel.swapProvider.type == ExchangeProviderType.CEX
|
||||
if (shouldShowCoverWarning && !isGaslessAvailable) {
|
||||
add(
|
||||
SwapNotificationUM.Error.UnableToCoverFeeWarning(
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ data class SwapProcessDataState(
|
|||
val reduceBalanceBy: BigDecimal = BigDecimal.ZERO,
|
||||
val approveDataModel: RequestApproveStateData? = null,
|
||||
val swapDataModel: SwapDataModel? = null,
|
||||
val selectedFee: TxFee? = null,
|
||||
val selectedFee: TxFee.Legacy? = null,
|
||||
val tokensDataState: TokensDataStateExpress? = null,
|
||||
val selectedProvider: SwapProvider? = null,
|
||||
val lastLoadedSwapStates: Map<SwapProvider, SwapState> = emptyMap(),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
data class SwapSuccessStateHolder(
|
||||
val timestamp: Long,
|
||||
val txUrl: String,
|
||||
val fee: TextReference,
|
||||
val fee: TextReference?,
|
||||
val rate: TextReference,
|
||||
val shouldShowStatusButton: Boolean,
|
||||
val providerName: TextReference,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ data class UiActions(
|
|||
val onStoriesClose: (Int) -> Unit,
|
||||
val onRetryClick: () -> Unit,
|
||||
val onClickFee: () -> Unit,
|
||||
val onSelectFeeType: (TxFee) -> Unit,
|
||||
val onSelectFeeType: (TxFee.Legacy) -> Unit,
|
||||
val onProviderClick: (String) -> Unit,
|
||||
val onProviderSelect: (String) -> Unit,
|
||||
val onBuyClick: (CryptoCurrency) -> Unit,
|
||||
|
|
|
|||
|
|
@ -826,7 +826,6 @@ internal class StateBuilder(
|
|||
onStatusClick: () -> Unit,
|
||||
txUrl: String,
|
||||
): SwapStateHolder {
|
||||
val fee = requireNotNull(dataState.selectedFee)
|
||||
val fromCryptoCurrency = requireNotNull(dataState.fromCryptoCurrency)
|
||||
val toCryptoCurrency = requireNotNull(dataState.toCryptoCurrency)
|
||||
val fromAmount = swapTransactionState.fromAmountValue ?: BigDecimal.ZERO
|
||||
|
|
@ -846,7 +845,9 @@ internal class StateBuilder(
|
|||
shouldShowStatusButton = shouldShowStatus,
|
||||
providerIcon = providerState.iconUrl,
|
||||
rate = providerState.subtitle,
|
||||
fee = stringReference("${fee.feeCryptoFormattedWithNative} (${fee.feeFiatFormattedWithNative})"),
|
||||
fee = dataState.selectedFee?.let { fee ->
|
||||
stringReference("${fee.feeCryptoFormattedWithNative} (${fee.feeFiatFormattedWithNative})")
|
||||
},
|
||||
fromTitle = getFromCardAccountTitle(fromAccount = dataState.fromAccount),
|
||||
toTitle = getToCardAccountTitle(toAccount = dataState.toAccount),
|
||||
fromTokenAmount = stringReference(swapTransactionState.fromAmount.orEmpty()),
|
||||
|
|
|
|||
|
|
@ -1,24 +1,27 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import com.tangem.common.ui.bottomsheet.permission.GiveTxPermissionBottomSheet
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent
|
||||
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.presentation.R
|
||||
|
||||
@Composable
|
||||
internal fun SwapScreen(stateHolder: SwapStateHolder) {
|
||||
internal fun SwapScreen(stateHolder: SwapStateHolder, feeSelectorBlockComponent: SwapFeeSelectorBlockComponent?) {
|
||||
BackHandler(onBack = stateHolder.onBackClicked)
|
||||
|
||||
Scaffold(
|
||||
|
|
@ -36,6 +39,17 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) {
|
|||
|
||||
SwapScreenContent(
|
||||
state = stateHolder,
|
||||
feeBlock = if (feeSelectorBlockComponent != null) {
|
||||
@Composable { modifier: Modifier ->
|
||||
feeSelectorBlockComponent.Content(
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
modifier = Modifier.padding(scaffoldPaddings),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,11 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modifier) {
|
||||
internal fun SwapScreenContent(
|
||||
state: SwapStateHolder,
|
||||
modifier: Modifier = Modifier,
|
||||
feeBlock: @Composable ((Modifier) -> Unit)? = null,
|
||||
) {
|
||||
val keyboard by keyboardAsState()
|
||||
|
||||
Box(
|
||||
|
|
@ -74,7 +78,11 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi
|
|||
|
||||
ProviderItemBlock(state = state.providerState)
|
||||
|
||||
FeeItemBlock(state = state.fee)
|
||||
if (feeBlock != null) {
|
||||
feeBlock(Modifier.fillMaxWidth())
|
||||
} else {
|
||||
FeeItemBlock(state = state.fee)
|
||||
}
|
||||
|
||||
if (state.notifications.isNotEmpty()) SwapNotifications(notifications = state.notifications)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,14 +29,16 @@ import com.tangem.core.ui.utils.toTimeFormat
|
|||
import com.tangem.feature.swap.models.SwapSuccessStateHolder
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.preview.SwapSuccessStatePreview
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.common.ui.FeeBlockSuccess
|
||||
|
||||
@Composable
|
||||
fun SwapSuccessScreen(state: SwapSuccessStateHolder, onBack: () -> Unit) {
|
||||
fun SwapSuccessScreen(state: SwapSuccessStateHolder, feeSelectorUM: FeeSelectorUM?, onBack: () -> Unit) {
|
||||
Scaffold(
|
||||
modifier = Modifier.systemBarsPadding(),
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
content = { padding ->
|
||||
SwapSuccessScreenContent(padding = padding, state = state)
|
||||
SwapSuccessScreenContent(padding = padding, feeSelectorUM = feeSelectorUM, state = state)
|
||||
},
|
||||
topBar = {
|
||||
AppBarWithBackButton(
|
||||
|
|
@ -58,7 +60,11 @@ fun SwapSuccessScreen(state: SwapSuccessStateHolder, onBack: () -> Unit) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun SwapSuccessScreenContent(state: SwapSuccessStateHolder, padding: PaddingValues) {
|
||||
private fun SwapSuccessScreenContent(
|
||||
state: SwapSuccessStateHolder,
|
||||
feeSelectorUM: FeeSelectorUM?,
|
||||
padding: PaddingValues,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -101,7 +107,10 @@ private fun SwapSuccessScreenContent(state: SwapSuccessStateHolder, padding: Pad
|
|||
.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
SpacerH16()
|
||||
if (state.fee != TextReference.EMPTY) {
|
||||
|
||||
if (feeSelectorUM != null) {
|
||||
FeeBlockSuccess(feeSelectorUM)
|
||||
} else if (state.fee != null && state.fee != TextReference.EMPTY) {
|
||||
InputRowDefault(
|
||||
title = TextReference.Res(R.string.common_network_fee_title),
|
||||
text = state.fee,
|
||||
|
|
@ -205,7 +214,7 @@ private fun SwapSuccessScreenButtons(
|
|||
@Composable
|
||||
private fun Preview_Success() {
|
||||
TangemThemePreview {
|
||||
SwapSuccessScreen(SwapSuccessStatePreview.state) {}
|
||||
SwapSuccessScreen(SwapSuccessStatePreview.state, null) {}
|
||||
}
|
||||
}
|
||||
// endregion preview
|
||||
Loading…
Add table
Add a link
Reference in a new issue