Updated on 2026-08-14
This commit is contained in:
parent
7e35a4de75
commit
c0471dbf9f
20 changed files with 315 additions and 133 deletions
|
|
@ -12,6 +12,7 @@ sealed class SwapPermissionState {
|
|||
object Empty : SwapPermissionState()
|
||||
|
||||
data class ReadyForRequest(
|
||||
val providerName: String,
|
||||
val currency: String,
|
||||
val amount: String,
|
||||
val walletAddress: String,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.feature.swap.models
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
|
|
@ -74,18 +73,21 @@ data class SwapButton(
|
|||
|
||||
sealed interface TransactionCardType {
|
||||
|
||||
val headerResId: Int
|
||||
val header: TextReference
|
||||
val isError: Boolean
|
||||
|
||||
data class Inputtable(
|
||||
val onAmountChanged: ((String) -> Unit),
|
||||
val onFocusChanged: ((Boolean) -> Unit),
|
||||
@StringRes override val headerResId: Int = R.string.swapping_from_title,
|
||||
override val isError: Boolean,
|
||||
override val header: TextReference = TextReference.Res(R.string.swapping_from_title),
|
||||
) : TransactionCardType
|
||||
|
||||
data class ReadOnly(
|
||||
val showWarning: Boolean = false,
|
||||
val onWarningClick: (() -> Unit)? = null,
|
||||
@StringRes override val headerResId: Int = R.string.swapping_to_title,
|
||||
override val isError: Boolean = false,
|
||||
override val header: TextReference = TextReference.Res(R.string.swapping_to_title),
|
||||
) : TransactionCardType
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +104,7 @@ data class LegalState(
|
|||
|
||||
sealed interface SwapWarning {
|
||||
data class PermissionNeeded(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
object InsufficientFunds : SwapWarning
|
||||
data object InsufficientFunds : SwapWarning
|
||||
data class NoAvailableTokensToSwap(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class GenericWarning(
|
||||
val title: TextReference? = null,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,11 @@ internal class StateBuilder(
|
|||
return SwapStateHolder(
|
||||
blockchainId = networkInfo.blockchainId,
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.Inputtable(actions.onAmountChanged, actions.onAmountSelected),
|
||||
type = TransactionCardType.Inputtable(
|
||||
onAmountChanged = actions.onAmountChanged,
|
||||
onFocusChanged = actions.onAmountSelected,
|
||||
isError = false,
|
||||
),
|
||||
amountEquivalent = null,
|
||||
amountTextFieldValue = null,
|
||||
token = null,
|
||||
|
|
@ -152,9 +156,13 @@ internal class StateBuilder(
|
|||
val canSelectReceiveToken = mainTokenId != toToken.id.value
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
val sendInput = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable).copy(
|
||||
isError = false,
|
||||
header = TextReference.Res(R.string.swapping_from_title),
|
||||
)
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable),
|
||||
type = sendInput,
|
||||
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
|
||||
amountEquivalent = null,
|
||||
token = uiStateHolder.sendCardData.token,
|
||||
|
|
@ -221,9 +229,19 @@ internal class StateBuilder(
|
|||
val feeState = createFeeState(quoteModel.txFee, selectedFeeType)
|
||||
val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus
|
||||
val toCurrencyStatus = quoteModel.toTokenInfo.cryptoCurrencyStatus
|
||||
val isInsufficientFunds = isInsufficientFundsCondition(quoteModel)
|
||||
val insufficientFundsHeader = if (isInsufficientFunds) {
|
||||
TextReference.Res(R.string.swapping_insufficient_funds)
|
||||
} else {
|
||||
TextReference.Res(R.string.swapping_from_title)
|
||||
}
|
||||
val sendInput = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable).copy(
|
||||
isError = isInsufficientFunds,
|
||||
header = insufficientFundsHeader,
|
||||
)
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable),
|
||||
type = sendInput,
|
||||
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
|
||||
amountEquivalent = getFormattedFiatAmount(quoteModel.fromTokenInfo.amountFiat),
|
||||
token = fromCurrencyStatus,
|
||||
|
|
@ -257,6 +275,7 @@ internal class StateBuilder(
|
|||
permissionState = convertPermissionState(
|
||||
lastPermissionState = uiStateHolder.permissionState,
|
||||
permissionDataState = quoteModel.permissionState,
|
||||
providerName = quoteModel.swapProvider.name,
|
||||
onGivePermissionClick = actions.onGivePermissionClick,
|
||||
onChangeApproveType = actions.onChangeApproveType,
|
||||
),
|
||||
|
|
@ -320,7 +339,7 @@ internal class StateBuilder(
|
|||
val warnings = mutableListOf<SwapWarning>()
|
||||
maybeAddDomainWarnings(quoteModel, warnings)
|
||||
maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings)
|
||||
maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken)
|
||||
maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken, quoteModel.swapProvider.name)
|
||||
maybeAddNetworkFeeCoverageWarning(quoteModel, warnings, selectedFeeType)
|
||||
maybeAddUnableCoverFeeWarning(quoteModel, fromToken, warnings)
|
||||
maybeAddInsufficientFundsWarning(quoteModel, warnings)
|
||||
|
|
@ -468,6 +487,7 @@ internal class StateBuilder(
|
|||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
fromToken: CryptoCurrency,
|
||||
providerName: String,
|
||||
) {
|
||||
if (!quoteModel.preparedSwapConfigState.isAllowedToSpend &&
|
||||
quoteModel.preparedSwapConfigState.feeState is SwapFeeState.Enough &&
|
||||
|
|
@ -475,7 +495,7 @@ internal class StateBuilder(
|
|||
) {
|
||||
warnings.add(
|
||||
SwapWarning.PermissionNeeded(
|
||||
createPermissionNotificationConfig(fromToken.symbol),
|
||||
createPermissionNotificationConfig(fromToken.symbol, providerName),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -493,8 +513,8 @@ internal class StateBuilder(
|
|||
warnings.add(
|
||||
SwapWarning.GeneralWarning(
|
||||
createNetworkFeeCoverageNotificationConfig(
|
||||
fee.feeCryptoFormatted,
|
||||
fee.feeFiatFormatted,
|
||||
fee.feeCryptoFormattedWithNative,
|
||||
fee.feeFiatFormattedWithNative,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -547,13 +567,16 @@ internal class StateBuilder(
|
|||
warnings: MutableList<SwapWarning>,
|
||||
) {
|
||||
// check isBalanceEnough, but for dex includeFeeInAmount always Excluded
|
||||
if (!quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||
quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included
|
||||
) {
|
||||
if (isInsufficientFundsCondition(quoteModel)) {
|
||||
warnings.add(SwapWarning.InsufficientFunds)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isInsufficientFundsCondition(quoteModel: SwapState.QuotesLoadedState): Boolean {
|
||||
return !quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||
quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included
|
||||
}
|
||||
|
||||
private fun getSwapButtonEnabled(quoteModel: SwapState.QuotesLoadedState): Boolean {
|
||||
val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value
|
||||
if (status is CryptoCurrencyStatus.NoAccount) {
|
||||
|
|
@ -964,9 +987,9 @@ internal class StateBuilder(
|
|||
return FeeItemState.Content(
|
||||
feeType = feeType,
|
||||
title = resourceReference(R.string.common_network_fee_title),
|
||||
amountCrypto = fee.feeCryptoFormatted,
|
||||
amountCrypto = fee.feeCryptoFormattedWithNative, // display fee with native as workaround for okx
|
||||
symbolCrypto = fee.cryptoSymbol,
|
||||
amountFiatFormatted = fee.feeFiatFormatted,
|
||||
amountFiatFormatted = fee.feeFiatFormattedWithNative, // display fee with native as workaround for okx
|
||||
isClickable = isClickable,
|
||||
onClick = actions.onClickFee,
|
||||
)
|
||||
|
|
@ -1017,7 +1040,7 @@ internal class StateBuilder(
|
|||
showStatusButton = shouldShowStatus,
|
||||
providerIcon = providerState.iconUrl,
|
||||
rate = providerState.subtitle,
|
||||
fee = stringReference("${fee.feeCryptoFormatted} (${fee.feeFiatFormatted})"),
|
||||
fee = stringReference("${fee.feeCryptoFormattedWithNative} (${fee.feeFiatFormattedWithNative})"),
|
||||
fromTokenAmount = stringReference(swapTransactionState.fromAmount.orEmpty()),
|
||||
toTokenAmount = stringReference(swapTransactionState.toAmount.orEmpty()),
|
||||
fromTokenFiatAmount = stringReference(fromFiatAmount),
|
||||
|
|
@ -1146,6 +1169,7 @@ internal class StateBuilder(
|
|||
private fun convertPermissionState(
|
||||
lastPermissionState: SwapPermissionState,
|
||||
permissionDataState: PermissionDataState,
|
||||
providerName: String,
|
||||
onGivePermissionClick: () -> Unit,
|
||||
onChangeApproveType: (ApproveType) -> Unit,
|
||||
): SwapPermissionState {
|
||||
|
|
@ -1165,6 +1189,7 @@ internal class StateBuilder(
|
|||
is TxFeeState.SingleFeeState -> fee.fee
|
||||
}
|
||||
SwapPermissionState.ReadyForRequest(
|
||||
providerName = providerName,
|
||||
currency = permissionDataState.currency,
|
||||
amount = permissionDataState.amount,
|
||||
approveType = approveType,
|
||||
|
|
@ -1358,18 +1383,18 @@ internal class StateBuilder(
|
|||
FeeItemState.Content(
|
||||
feeType = this.normalFee.feeType,
|
||||
title = resourceReference(R.string.common_network_fee_title),
|
||||
amountCrypto = this.normalFee.feeCryptoFormatted,
|
||||
amountCrypto = this.normalFee.feeCryptoFormattedWithNative,
|
||||
symbolCrypto = this.normalFee.cryptoSymbol,
|
||||
amountFiatFormatted = this.normalFee.feeFiatFormatted,
|
||||
amountFiatFormatted = this.normalFee.feeFiatFormattedWithNative,
|
||||
isClickable = true,
|
||||
onClick = {},
|
||||
),
|
||||
FeeItemState.Content(
|
||||
feeType = this.priorityFee.feeType,
|
||||
title = resourceReference(R.string.common_network_fee_title),
|
||||
amountCrypto = this.priorityFee.feeCryptoFormatted,
|
||||
amountCrypto = this.priorityFee.feeCryptoFormattedWithNative,
|
||||
symbolCrypto = this.priorityFee.cryptoSymbol,
|
||||
amountFiatFormatted = this.priorityFee.feeFiatFormatted,
|
||||
amountFiatFormatted = this.priorityFee.feeFiatFormattedWithNative,
|
||||
isClickable = true,
|
||||
onClick = {},
|
||||
),
|
||||
|
|
@ -1402,12 +1427,12 @@ internal class StateBuilder(
|
|||
}
|
||||
|
||||
// region warnings
|
||||
private fun createPermissionNotificationConfig(fromTokenSymbol: String): NotificationConfig {
|
||||
private fun createPermissionNotificationConfig(fromTokenSymbol: String, providerName: String): NotificationConfig {
|
||||
return NotificationConfig(
|
||||
title = resourceReference(R.string.express_provider_permission_needed),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.swapping_permission_subheader,
|
||||
formatArgs = wrappedList(fromTokenSymbol),
|
||||
formatArgs = wrappedList(providerName, fromTokenSymbol),
|
||||
),
|
||||
iconResId = R.drawable.ic_locked_24,
|
||||
)
|
||||
|
|
@ -1604,7 +1629,7 @@ internal class StateBuilder(
|
|||
id = this.providerId,
|
||||
name = this.name,
|
||||
iconUrl = this.imageLarge,
|
||||
type = this.type.toString(),
|
||||
type = this.type.providerName,
|
||||
selectionType = selectionType,
|
||||
alertText = alertText,
|
||||
onProviderClick = onProviderClick,
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ private fun SwapPermissionBottomSheetContent(content: GivePermissionBottomSheetC
|
|||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.swapping_permission_subheader,
|
||||
data.providerName,
|
||||
data.currency,
|
||||
),
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
|
|
@ -303,6 +304,7 @@ private fun Preview_AgreementBottomSheet() {
|
|||
|
||||
private val previewData = GivePermissionBottomSheetConfig(
|
||||
data = SwapPermissionState.ReadyForRequest(
|
||||
providerName = "1icnh",
|
||||
currency = "DAI",
|
||||
amount = "∞",
|
||||
walletAddress = "",
|
||||
|
|
|
|||
|
|
@ -434,7 +434,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U
|
|||
// region preview
|
||||
|
||||
private val sendCard = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.Inputtable({}, {}),
|
||||
type = TransactionCardType.Inputtable({}, {}, false),
|
||||
amountTextFieldValue = TextFieldValue(),
|
||||
amountEquivalent = "1 000 000",
|
||||
tokenIconUrl = "",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import coil.compose.SubcomposeAsyncImage
|
|||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.ImageBackgroundContrastChecker
|
||||
|
|
@ -185,10 +186,14 @@ private fun Header(type: TransactionCardType, balance: String, modifier: Modifie
|
|||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val title = type.headerResId
|
||||
val titleColor = if (type.isError) {
|
||||
TangemTheme.colors.text.warning
|
||||
} else {
|
||||
TangemTheme.colors.text.tertiary
|
||||
}
|
||||
Text(
|
||||
text = stringResource(id = title),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
text = type.header.resolveReference(),
|
||||
color = titleColor,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.subtitle2,
|
||||
modifier = Modifier
|
||||
|
|
@ -546,7 +551,7 @@ private fun Preview_TransactionCardWithoutPriceImpact_InDarkTheme() {
|
|||
@Composable
|
||||
private fun TransactionCardPreview() {
|
||||
TransactionCard(
|
||||
type = TransactionCardType.Inputtable({}, {}),
|
||||
type = TransactionCardType.Inputtable({}, {}, false),
|
||||
amountEquivalent = "1 000 000",
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "DAI",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue