Updated on 2026-08-14
This commit is contained in:
commit
fbb99bcda0
317 changed files with 3800 additions and 2894 deletions
|
|
@ -60,7 +60,6 @@ dependencies {
|
|||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.compose.constraintLayout)
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
|
||||
/** Api */
|
||||
implementation(projects.features.swap.api)
|
||||
|
|
@ -71,7 +70,6 @@ dependencies {
|
|||
|
||||
/** Other libraries */
|
||||
implementation(deps.compose.shimmer)
|
||||
implementation(deps.compose.accompanist.webView)
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ package com.tangem.feature.swap.models
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
|
@ -14,12 +14,15 @@ import com.tangem.feature.swap.domain.models.ui.PriceImpact
|
|||
import com.tangem.feature.swap.models.states.FeeItemState
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
import com.tangem.feature.swap.models.states.events.SwapEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal data class SwapStateHolder(
|
||||
val sendCardData: SwapCardState,
|
||||
val receiveCardData: SwapCardState,
|
||||
val blockchainId: String, // not the same as networkId, its local id in app
|
||||
val warnings: List<SwapWarning> = emptyList(),
|
||||
val notifications: ImmutableList<NotificationUM> = persistentListOf(),
|
||||
val isInsufficientFunds: Boolean,
|
||||
val event: StateEvent<SwapEvent> = consumedEvent(),
|
||||
val changeCardsButtonState: ChangeCardsButtonState = ChangeCardsButtonState.ENABLED,
|
||||
val providerState: ProviderState,
|
||||
|
|
@ -106,35 +109,6 @@ data class LegalState(
|
|||
val onClick: (String) -> Unit,
|
||||
)
|
||||
|
||||
sealed interface SwapWarning {
|
||||
data class PermissionNeeded(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data object InsufficientFunds : SwapWarning
|
||||
data class NoAvailableTokensToSwap(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class GenericWarning(
|
||||
val title: TextReference? = null,
|
||||
val message: TextReference? = null,
|
||||
val onClick: () -> Unit,
|
||||
) : SwapWarning
|
||||
|
||||
data class GeneralError(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class UnableToCoverFeeWarning(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class GeneralWarning(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class GeneralInformational(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class TransactionInProgressWarning(val title: TextReference, val description: TextReference) : SwapWarning
|
||||
data class NeedReserveToCreateAccount(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class ReduceAmount(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
|
||||
sealed interface Cardano : SwapWarning {
|
||||
val notificationConfig: NotificationConfig
|
||||
|
||||
data class MinAdaValueCharged(override val notificationConfig: NotificationConfig) : Cardano
|
||||
|
||||
data class InsufficientBalanceToTransferCoin(override val notificationConfig: NotificationConfig) : Cardano
|
||||
|
||||
data class InsufficientBalanceToTransferToken(override val notificationConfig: NotificationConfig) : Cardano
|
||||
}
|
||||
}
|
||||
|
||||
enum class ChangeCardsButtonState {
|
||||
ENABLED, DISABLED, UPDATE_IN_PROGRESS
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.ui.TxFee
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class UiActions(
|
||||
val onSearchEntered: (String) -> Unit,
|
||||
|
|
@ -15,8 +16,8 @@ data class UiActions(
|
|||
val onChangeCardsClicked: () -> Unit,
|
||||
val onBackClicked: () -> Unit,
|
||||
val onMaxAmountSelected: () -> Unit,
|
||||
val onReduceAmount: (SwapAmount) -> Unit,
|
||||
val onLeaveExistentialDeposit: (SwapAmount) -> Unit,
|
||||
val onReduceToAmount: (SwapAmount) -> Unit,
|
||||
val onReduceByAmount: (SwapAmount, reduceBy: BigDecimal) -> Unit,
|
||||
val openPermissionBottomSheet: () -> Unit,
|
||||
val onChangeApproveType: (ApproveType) -> Unit,
|
||||
// region new actions
|
||||
|
|
|
|||
|
|
@ -0,0 +1,205 @@
|
|||
package com.tangem.feature.swap.models.states
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.utils.getExpressErrorMessage
|
||||
import com.tangem.feature.swap.utils.getExpressErrorTitle
|
||||
|
||||
internal object SwapNotificationUM {
|
||||
|
||||
sealed class Error(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.ic_alert_24,
|
||||
buttonState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : NotificationUM.Error(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
buttonState = buttonState,
|
||||
onCloseClick = onCloseClick,
|
||||
) {
|
||||
data class GenericError(
|
||||
val title: TextReference = resourceReference(id = R.string.common_warning),
|
||||
val subtitle: TextReference?,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : Error(
|
||||
title = title,
|
||||
subtitle = subtitle ?: resourceReference(id = R.string.common_unknown_error),
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.common_ok),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
||||
data object ApprovalInProgressWarning : Error(
|
||||
title = resourceReference(R.string.warning_express_approval_in_progress_title),
|
||||
subtitle = resourceReference(R.string.warning_express_approval_in_progress_message),
|
||||
)
|
||||
|
||||
data class TransactionInProgressWarning(
|
||||
val currencySymbol: String,
|
||||
) : Error(
|
||||
title = resourceReference(R.string.warning_express_active_transaction_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.warning_express_active_transaction_message,
|
||||
formatArgs = wrappedList(currencySymbol),
|
||||
),
|
||||
)
|
||||
|
||||
data class UnableToCoverFeeWarning(
|
||||
val fromToken: CryptoCurrency,
|
||||
val currencyName: String,
|
||||
val currencySymbol: String,
|
||||
val feeCurrency: CryptoCurrency?,
|
||||
val onConfirmClick: (CryptoCurrency) -> Unit,
|
||||
) : Error(
|
||||
title = resourceReference(
|
||||
R.string.warning_express_not_enough_fee_for_token_tx_title,
|
||||
wrappedList(fromToken.network.name),
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
R.string.warning_express_not_enough_fee_for_token_tx_description,
|
||||
wrappedList(currencyName, currencySymbol),
|
||||
),
|
||||
iconResId = fromToken.networkIconResId,
|
||||
buttonState = feeCurrency?.let {
|
||||
NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.common_buy_currency, wrappedList(currencySymbol)),
|
||||
onClick = { onConfirmClick(it) },
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
data class MinimalAmountError(
|
||||
val amount: String,
|
||||
) : Error(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_express_too_minimal_amount_title,
|
||||
formatArgs = wrappedList(amount),
|
||||
),
|
||||
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
|
||||
)
|
||||
|
||||
data class MaximumAmountError(
|
||||
val amount: String,
|
||||
) : Error(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_express_too_maximum_amount_title,
|
||||
formatArgs = wrappedList(amount),
|
||||
),
|
||||
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Warning(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.img_attention_20,
|
||||
buttonsState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : NotificationUM.Warning(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
buttonsState = buttonsState,
|
||||
onCloseClick = onCloseClick,
|
||||
) {
|
||||
data class NoAvailableTokensToSwap(
|
||||
val tokenName: String,
|
||||
) : Warning(
|
||||
title = resourceReference(
|
||||
com.tangem.feature.swap.presentation.R.string.warning_express_no_exchangeable_coins_title,
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
id = com.tangem.feature.swap.presentation.R.string.warning_express_no_exchangeable_coins_description,
|
||||
formatArgs = wrappedList(tokenName),
|
||||
),
|
||||
)
|
||||
|
||||
data class NeedReserveToCreateAccount(
|
||||
val amount: String,
|
||||
val token: String,
|
||||
) : Warning(
|
||||
title = resourceReference(
|
||||
id = R.string.send_notification_invalid_reserve_amount_title,
|
||||
formatArgs = wrappedList("$amount $token"),
|
||||
),
|
||||
subtitle = resourceReference(R.string.send_notification_invalid_reserve_amount_text),
|
||||
)
|
||||
|
||||
data class ReduceAmount(
|
||||
val currencyName: String,
|
||||
val amount: String,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : Warning(
|
||||
title = resourceReference(R.string.send_notification_high_fee_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_notification_high_fee_text,
|
||||
wrappedList(currencyName, amount),
|
||||
),
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(
|
||||
R.string.xtz_withdrawal_message_reduce,
|
||||
wrappedList(amount),
|
||||
),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
||||
data class ExpressError(
|
||||
val expressDataError: ExpressDataError,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : Warning(
|
||||
title = getExpressErrorTitle(expressDataError),
|
||||
subtitle = getExpressErrorMessage(expressDataError),
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_button_refresh),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
||||
data class ExpressGeneralError(
|
||||
val code: Int,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : Warning(
|
||||
title = TextReference.Res(R.string.warning_express_refresh_required_title),
|
||||
subtitle = TextReference.Res(R.string.express_error_code, wrappedList(code)),
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = TextReference.Res(R.string.warning_button_refresh),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Info(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.ic_alert_circle_24,
|
||||
) : NotificationUM.Info(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
) {
|
||||
data class PermissionNeeded(
|
||||
val providerName: String,
|
||||
val fromTokenSymbol: String,
|
||||
) : Info(
|
||||
title = resourceReference(R.string.express_provider_permission_needed),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.give_permission_swap_subtitle,
|
||||
formatArgs = wrappedList(providerName, fromTokenSymbol),
|
||||
),
|
||||
iconResId = R.drawable.ic_locked_24,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package com.tangem.feature.swap.models.states
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
|
||||
internal data class WebViewBottomSheetConfig(val url: String) : TangemBottomSheetConfigContent
|
||||
|
|
@ -6,6 +6,4 @@ import com.tangem.common.ui.alerts.models.AlertUM
|
|||
@Immutable
|
||||
internal sealed class SwapEvent {
|
||||
data class ShowAlert(val alert: AlertUM) : SwapEvent()
|
||||
|
||||
data class ShowShareDialog(val txUrl: String) : SwapEvent()
|
||||
}
|
||||
|
|
@ -0,0 +1,345 @@
|
|||
package com.tangem.feature.swap.presentation
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addExistentialWarningNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountErrorNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
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.SwapFeeState
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.feature.swap.models.UiActions
|
||||
import com.tangem.feature.swap.models.states.FeeItemState
|
||||
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.lib.crypto.BlockchainUtils.getTezosThreshold
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTezos
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SwapNotificationsFactory(
|
||||
private val actions: UiActions,
|
||||
) {
|
||||
|
||||
fun getInitialErrorStateNotifications(code: Int, onRefreshClick: () -> Unit): ImmutableList<NotificationUM> {
|
||||
return persistentListOf(
|
||||
SwapNotificationUM.Warning.ExpressGeneralError(
|
||||
code = code,
|
||||
onConfirmClick = onRefreshClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getGeneralErrorStateNotifications(
|
||||
notifications: ImmutableList<NotificationUM>,
|
||||
message: TextReference?,
|
||||
onClick: () -> Unit,
|
||||
): ImmutableList<NotificationUM> {
|
||||
val updatedNotifications = notifications.toMutableList()
|
||||
updatedNotifications.add(
|
||||
SwapNotificationUM.Error.GenericError(
|
||||
subtitle = message,
|
||||
onConfirmClick = onClick,
|
||||
),
|
||||
)
|
||||
|
||||
return updatedNotifications.toPersistentList()
|
||||
}
|
||||
|
||||
fun getNotAvailableStateNotifications(fromCurrencyName: String): ImmutableList<NotificationUM> {
|
||||
return persistentListOf(
|
||||
SwapNotificationUM.Warning.NoAvailableTokensToSwap(fromCurrencyName),
|
||||
)
|
||||
}
|
||||
|
||||
fun getQuotesErrorStateNotifications(
|
||||
expressDataError: ExpressDataError,
|
||||
fromToken: CryptoCurrency,
|
||||
feeItem: FeeItemState,
|
||||
includeFeeInAmount: IncludeFeeInAmount,
|
||||
): ImmutableList<NotificationUM> {
|
||||
return buildList {
|
||||
add(getWarningForError(expressDataError, fromToken, actions.onRetryClick))
|
||||
if (includeFeeInAmount is IncludeFeeInAmount.Included && feeItem is FeeItemState.Content) {
|
||||
add(
|
||||
NotificationUM.Warning.FeeCoverageNotification(
|
||||
feeItem.amountCrypto,
|
||||
feeItem.amountFiatFormatted,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
||||
fun getApprovalInProgressStateNotification(
|
||||
notifications: ImmutableList<NotificationUM>,
|
||||
): ImmutableList<NotificationUM> {
|
||||
val updatedNotifications = notifications
|
||||
.filterNot { it is SwapNotificationUM.Info.PermissionNeeded }
|
||||
.toMutableList()
|
||||
|
||||
updatedNotifications.add(0, SwapNotificationUM.Error.ApprovalInProgressWarning)
|
||||
|
||||
return updatedNotifications.toPersistentList()
|
||||
}
|
||||
|
||||
fun getConfirmationStateNotifications(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
selectedFeeType: FeeType,
|
||||
providerName: String,
|
||||
): ImmutableList<NotificationUM> {
|
||||
val warnings = buildList {
|
||||
maybeAddDomainWarnings(quoteModel, feeCryptoCurrencyStatus, selectedFeeType)
|
||||
maybeAddNeedReserveToCreateAccountWarning(quoteModel)
|
||||
maybeAddPermissionNeededWarning(quoteModel, fromToken, providerName)
|
||||
maybeAddNetworkFeeCoverageWarning(quoteModel, selectedFeeType)
|
||||
maybeAddUnableCoverFeeWarning(quoteModel, fromToken)
|
||||
maybeAddTransactionInProgressWarning(quoteModel)
|
||||
}
|
||||
return warnings.toPersistentList()
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddTransactionInProgressWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
) {
|
||||
if (quoteModel.permissionState is PermissionDataState.PermissionLoading) {
|
||||
add(SwapNotificationUM.Error.ApprovalInProgressWarning)
|
||||
} else if (quoteModel.preparedSwapConfigState.hasOutgoingTransaction) {
|
||||
add(
|
||||
SwapNotificationUM.Error.TransactionInProgressWarning(
|
||||
currencySymbol = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.network.currencySymbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddDomainWarnings(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
selectedFeeType: FeeType,
|
||||
) {
|
||||
val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus
|
||||
val includeFeeInAmount = quoteModel.preparedSwapConfigState.includeFeeInAmount
|
||||
val amount = quoteModel.fromTokenInfo.tokenAmount
|
||||
val amountToRequest = if (includeFeeInAmount is IncludeFeeInAmount.Included) {
|
||||
includeFeeInAmount.amountSubtractFee
|
||||
} else {
|
||||
amount
|
||||
}
|
||||
val fee = when (val feeState = quoteModel.txFee) {
|
||||
TxFeeState.Empty -> null
|
||||
is TxFeeState.MultipleFeeState -> if (feeState.normalFee.feeType == selectedFeeType) {
|
||||
feeState.normalFee
|
||||
} else {
|
||||
feeState.priorityFee
|
||||
}
|
||||
is TxFeeState.SingleFeeState -> feeState.fee
|
||||
}
|
||||
val isCardano = BlockchainUtils.isCardano(fromCurrencyStatus.currency.network.id.value)
|
||||
// blockchain specific
|
||||
|
||||
addExistentialWarningNotification(
|
||||
existentialDeposit = quoteModel.currencyCheck?.existentialDeposit,
|
||||
feeAmount = fee?.feeValue.orZero(),
|
||||
sendingAmount = amount.value,
|
||||
cryptoCurrencyStatus = fromCurrencyStatus,
|
||||
onReduceClick = { reduceBy, reduceByDiff, _ ->
|
||||
actions.onReduceByAmount(
|
||||
amount.copy(value = amount.value.minus(reduceByDiff)),
|
||||
reduceBy,
|
||||
)
|
||||
},
|
||||
)
|
||||
addValidateTransactionNotifications(
|
||||
dustValue = quoteModel.currencyCheck?.dustValue.orZero(),
|
||||
validationError = quoteModel.validationResult,
|
||||
cryptoCurrency = fromCurrencyStatus.currency,
|
||||
minAdaValue = quoteModel.minAdaValue,
|
||||
onReduceClick = { reduceTo, _ ->
|
||||
actions.onReduceToAmount(amount.copy(value = reduceTo))
|
||||
},
|
||||
)
|
||||
if (!isCardano) {
|
||||
addDustWarningNotification(
|
||||
dustValue = quoteModel.currencyCheck?.dustValue,
|
||||
feeValue = fee?.feeValue.orZero(),
|
||||
sendingAmount = amountToRequest.value,
|
||||
cryptoCurrencyStatus = fromCurrencyStatus,
|
||||
feeCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
)
|
||||
}
|
||||
addReserveAmountErrorNotification(
|
||||
reserveAmount = quoteModel.currencyCheck?.reserveAmount,
|
||||
sendingAmount = amountToRequest.value,
|
||||
cryptoCurrency = fromCurrencyStatus.currency,
|
||||
isAccountFunded = false,
|
||||
)
|
||||
addReduceAmountNotification(
|
||||
cryptoCurrencyStatus = fromCurrencyStatus,
|
||||
fromAmount = quoteModel.fromTokenInfo.tokenAmount,
|
||||
onReduceByAmount = actions.onReduceByAmount,
|
||||
)
|
||||
addTransactionLimitErrorNotification(
|
||||
utxoLimit = quoteModel.currencyCheck?.utxoAmountLimit,
|
||||
cryptoCurrency = fromCurrencyStatus.currency,
|
||||
onReduceClick = { reduceTo, _ ->
|
||||
actions.onReduceToAmount(amountToRequest.copy(value = reduceTo))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddNeedReserveToCreateAccountWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
) {
|
||||
val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value
|
||||
if (status is CryptoCurrencyStatus.NoAccount) {
|
||||
val amount = quoteModel.toTokenInfo.tokenAmount.value
|
||||
val amountToCreateAccount = status.amountToCreateAccount
|
||||
val currencyTo = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency
|
||||
if (amount < amountToCreateAccount) {
|
||||
add(
|
||||
SwapNotificationUM.Warning.NeedReserveToCreateAccount(
|
||||
status.amountToCreateAccount.parseBigDecimal(currencyTo.decimals),
|
||||
currencyTo.symbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddPermissionNeededWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
providerName: String,
|
||||
) {
|
||||
if (!quoteModel.preparedSwapConfigState.isAllowedToSpend &&
|
||||
quoteModel.preparedSwapConfigState.feeState is SwapFeeState.Enough &&
|
||||
quoteModel.permissionState is PermissionDataState.PermissionReadyForRequest
|
||||
) {
|
||||
add(
|
||||
SwapNotificationUM.Info.PermissionNeeded(
|
||||
providerName = fromToken.symbol,
|
||||
fromTokenSymbol = providerName,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddNetworkFeeCoverageWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
selectedFeeType: FeeType,
|
||||
) {
|
||||
when (quoteModel.preparedSwapConfigState.includeFeeInAmount) {
|
||||
is IncludeFeeInAmount.Included -> {
|
||||
val fee = selectFeeByType(selectedFeeType, quoteModel.txFee) ?: return
|
||||
if (needShowNetworkFeeCoverageWarningShow(quoteModel)) {
|
||||
add(
|
||||
NotificationUM.Warning.FeeCoverageNotification(
|
||||
fee.feeCryptoFormattedWithNative,
|
||||
fee.feeFiatFormattedWithNative,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): TxFee? {
|
||||
return when (txFeeState) {
|
||||
TxFeeState.Empty -> null
|
||||
is TxFeeState.SingleFeeState -> txFeeState.fee
|
||||
is TxFeeState.MultipleFeeState -> when (feeType) {
|
||||
FeeType.NORMAL -> txFeeState.normalFee
|
||||
FeeType.PRIORITY -> txFeeState.priorityFee
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddUnableCoverFeeWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
) {
|
||||
val feeEnoughState = quoteModel.preparedSwapConfigState.feeState as? SwapFeeState.NotEnough ?: return
|
||||
val needShowCoverWarning = quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||
quoteModel.permissionState !is PermissionDataState.PermissionLoading &&
|
||||
feeEnoughState.feeCurrency != fromToken
|
||||
if (needShowCoverWarning) {
|
||||
add(
|
||||
SwapNotificationUM.Error.UnableToCoverFeeWarning(
|
||||
fromToken = fromToken,
|
||||
feeCurrency = feeEnoughState.feeCurrency,
|
||||
currencyName = feeEnoughState.currencyName ?: fromToken.network.name,
|
||||
currencySymbol = feeEnoughState.currencySymbol ?: fromToken.network.currencySymbol,
|
||||
onConfirmClick = actions.onBuyClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addReduceAmountNotification(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
fromAmount: SwapAmount,
|
||||
onReduceByAmount: (SwapAmount, BigDecimal) -> Unit,
|
||||
) {
|
||||
val isTezos = isTezos(cryptoCurrencyStatus.currency.network.id.value)
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
val threshold = getTezosThreshold()
|
||||
val isTotalBalance = fromAmount.value >= balance && balance > threshold
|
||||
if (isTezos && isTotalBalance) {
|
||||
add(
|
||||
SwapNotificationUM.Warning.ReduceAmount(
|
||||
currencyName = cryptoCurrencyStatus.currency.name,
|
||||
amount = threshold.toPlainString(),
|
||||
onConfirmClick = {
|
||||
val patchedAmount = fromAmount.copy(
|
||||
value = fromAmount.value - threshold,
|
||||
)
|
||||
onReduceByAmount(patchedAmount, threshold)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWarningForError(
|
||||
expressDataError: ExpressDataError,
|
||||
fromToken: CryptoCurrency,
|
||||
onRetryClick: () -> Unit,
|
||||
): NotificationUM {
|
||||
return when (expressDataError) {
|
||||
is ExpressDataError.ExchangeTooSmallAmountError -> SwapNotificationUM.Error.MinimalAmountError(
|
||||
expressDataError.amount.value.format {
|
||||
crypto(fromToken.symbol, fromToken.decimals)
|
||||
},
|
||||
)
|
||||
is ExpressDataError.ExchangeTooBigAmountError -> SwapNotificationUM.Error.MaximumAmountError(
|
||||
expressDataError.amount.value.format {
|
||||
crypto(fromToken.symbol, fromToken.decimals)
|
||||
},
|
||||
)
|
||||
else -> SwapNotificationUM.Warning.ExpressError(
|
||||
expressDataError,
|
||||
onConfirmClick = onRetryClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun needShowNetworkFeeCoverageWarningShow(quoteModel: SwapState.QuotesLoadedState): Boolean {
|
||||
return quoteModel.currencyCheck?.existentialDeposit == null
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.text.ClickableText
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
|
@ -17,10 +16,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
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.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.domain.models.ui.FeeType
|
||||
|
|
@ -69,7 +65,7 @@ private fun ChooseFeeBottomSheetContent(content: ChooseFeeBottomSheetConfig) {
|
|||
@Composable
|
||||
private fun FooterBlock(readMore: TextReference, readMoreUrl: String, onReadMoreClick: (String) -> Unit) {
|
||||
val linkText = readMore.resolveReference()
|
||||
val fullString = stringResource(R.string.common_fee_selector_footer, linkText)
|
||||
val fullString = stringResourceSafe(R.string.common_fee_selector_footer, linkText)
|
||||
val linkTextPosition = fullString.length - linkText.length
|
||||
val annotatedString = buildAnnotatedString {
|
||||
withStyle(SpanStyle(color = TangemTheme.colors.text.tertiary)) {
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig
|
||||
|
|
@ -43,7 +43,7 @@ fun ChooseProviderBottomSheet(config: TangemBottomSheetConfig) {
|
|||
private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetConfig) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
text = stringResource(R.string.express_choose_providers_subtitle),
|
||||
text = stringResourceSafe(R.string.express_choose_providers_subtitle),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier
|
||||
|
|
@ -89,7 +89,7 @@ private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetC
|
|||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.express_more_providers_soon),
|
||||
text = stringResourceSafe(R.string.express_more_providers_soon),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.icon.informative,
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -25,6 +24,7 @@ import com.tangem.core.ui.R
|
|||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.GRAY_SCALE_ALPHA
|
||||
|
|
@ -122,7 +122,7 @@ private fun ProviderContentState(
|
|||
Row {
|
||||
if (state.namePrefix == ProviderState.PrefixType.PROVIDED_BY) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.express_by_provider),
|
||||
text = stringResourceSafe(id = R.string.express_by_provider),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.padding(end = TangemTheme.dimens.spacing4),
|
||||
|
|
@ -262,7 +262,7 @@ private fun ProviderLoadingState(modifier: Modifier = Modifier) {
|
|||
Box(modifier = modifier.fillMaxWidth()) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.express_provider),
|
||||
text = stringResourceSafe(R.string.express_provider),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing12),
|
||||
|
|
@ -282,7 +282,7 @@ private fun ProviderLoadingState(modifier: Modifier = Modifier) {
|
|||
strokeWidth = TangemTheme.dimens.size2,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.express_fetch_best_rates),
|
||||
text = stringResourceSafe(R.string.express_fetch_best_rates),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing4),
|
||||
|
|
@ -357,7 +357,7 @@ private fun BestTradeItem(modifier: Modifier = Modifier) {
|
|||
),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.express_provider_best_rate),
|
||||
text = stringResourceSafe(R.string.express_provider_best_rate),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing6),
|
||||
|
|
@ -374,7 +374,7 @@ private fun PermissionBadgeItem(modifier: Modifier = Modifier) {
|
|||
),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.express_provider_permission_needed),
|
||||
text = stringResourceSafe(id = R.string.express_provider_permission_needed),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing6),
|
||||
|
|
@ -391,7 +391,7 @@ private fun RecommendedItem(modifier: Modifier = Modifier) {
|
|||
),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.express_provider_recommended),
|
||||
text = stringResourceSafe(id = R.string.express_provider_recommended),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing6),
|
||||
|
|
|
|||
|
|
@ -4,13 +4,16 @@ import androidx.compose.ui.text.TextRange
|
|||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.*
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.*
|
||||
import com.tangem.core.ui.format.bigdecimal.anyDecimals
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
@ -20,21 +23,24 @@ import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter
|
|||
import com.tangem.feature.swap.converters.TokensDataConverter
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
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.NetworkInfo
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapProvider
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.models.states.*
|
||||
import com.tangem.feature.swap.models.states.events.SwapEvent
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.presentation.SwapNotificationsFactory
|
||||
import com.tangem.feature.swap.utils.formatToUIRepresentation
|
||||
import com.tangem.feature.swap.utils.getExpressErrorMessage
|
||||
import com.tangem.feature.swap.utils.getExpressErrorTitle
|
||||
import com.tangem.feature.swap.viewmodels.SwapProcessDataState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import com.tangem.utils.StringsSigns.PERCENT
|
||||
import com.tangem.utils.StringsSigns.TILDE_SIGN
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
|
@ -60,6 +66,10 @@ internal class StateBuilder(
|
|||
appCurrencyProvider = appCurrencyProvider,
|
||||
)
|
||||
|
||||
private val notificationsFactory by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SwapNotificationsFactory(actions)
|
||||
}
|
||||
|
||||
fun createInitialLoadingState(
|
||||
initialCurrencyFrom: CryptoCurrency,
|
||||
initialCurrencyTo: CryptoCurrency?,
|
||||
|
|
@ -110,6 +120,7 @@ internal class StateBuilder(
|
|||
providerState = ProviderState.Empty(),
|
||||
shouldShowMaxAmount = false,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
isInsufficientFunds = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -141,18 +152,7 @@ internal class StateBuilder(
|
|||
),
|
||||
canSelectAnotherToken = true,
|
||||
),
|
||||
warnings = listOf(
|
||||
SwapWarning.NoAvailableTokensToSwap(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = resourceReference(R.string.warning_express_no_exchangeable_coins_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.warning_express_no_exchangeable_coins_description,
|
||||
formatArgs = wrappedList(fromToken.currency.name),
|
||||
),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
),
|
||||
),
|
||||
),
|
||||
notifications = notificationsFactory.getNotAvailableStateNotifications(fromToken.currency.name),
|
||||
fee = FeeItemState.Empty,
|
||||
swapButton = SwapButton(
|
||||
enabled = false,
|
||||
|
|
@ -211,7 +211,7 @@ internal class StateBuilder(
|
|||
networkIconRes = getActiveIconRes(toToken.network.id.value),
|
||||
isBalanceHidden = isBalanceHiddenProvider(),
|
||||
),
|
||||
warnings = emptyList(),
|
||||
notifications = persistentListOf(),
|
||||
fee = FeeItemState.Empty,
|
||||
swapButton = SwapButton(enabled = false, onClick = {}),
|
||||
providerState = ProviderState.Loading(),
|
||||
|
|
@ -235,6 +235,7 @@ internal class StateBuilder(
|
|||
uiStateHolder: SwapStateHolder,
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
swapProvider: SwapProvider,
|
||||
bestRatedProviderId: String,
|
||||
isNeedBestRateBadge: Boolean,
|
||||
|
|
@ -243,9 +244,10 @@ internal class StateBuilder(
|
|||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
val warnings = getWarningsForSuccessState(
|
||||
val notifications = notificationsFactory.getConfirmationStateNotifications(
|
||||
quoteModel = quoteModel,
|
||||
fromToken = fromToken,
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
selectedFeeType = selectedFeeType,
|
||||
providerName = swapProvider.name,
|
||||
)
|
||||
|
|
@ -304,7 +306,8 @@ internal class StateBuilder(
|
|||
balance = toCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
|
||||
isBalanceHidden = isBalanceHiddenProvider(),
|
||||
),
|
||||
warnings = warnings,
|
||||
isInsufficientFunds = isInsufficientFundsCondition(quoteModel),
|
||||
notifications = notifications,
|
||||
permissionState = convertPermissionState(
|
||||
lastPermissionState = uiStateHolder.permissionState,
|
||||
permissionDataState = quoteModel.permissionState,
|
||||
|
|
@ -314,7 +317,7 @@ internal class StateBuilder(
|
|||
),
|
||||
fee = feeState,
|
||||
swapButton = SwapButton(
|
||||
enabled = getSwapButtonEnabled(quoteModel),
|
||||
enabled = getSwapButtonEnabled(notifications),
|
||||
onClick = actions.onSwapClick,
|
||||
),
|
||||
changeCardsButtonState = if (isReverseSwapPossible) {
|
||||
|
|
@ -363,281 +366,17 @@ internal class StateBuilder(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private fun getWarningsForSuccessState(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
selectedFeeType: FeeType,
|
||||
providerName: String,
|
||||
): List<SwapWarning> {
|
||||
val warnings = mutableListOf<SwapWarning>()
|
||||
maybeAddDomainWarnings(quoteModel, warnings)
|
||||
maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings)
|
||||
maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken, providerName)
|
||||
maybeAddNetworkFeeCoverageWarning(quoteModel, warnings, selectedFeeType)
|
||||
maybeAddUnableCoverFeeWarning(quoteModel, fromToken, warnings)
|
||||
maybeAddInsufficientFundsWarning(quoteModel, warnings)
|
||||
maybeAddTransactionInProgressWarning(quoteModel, warnings)
|
||||
return warnings
|
||||
}
|
||||
|
||||
private fun maybeAddTransactionInProgressWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
) {
|
||||
if (quoteModel.permissionState is PermissionDataState.PermissionLoading) {
|
||||
warnings.add(
|
||||
SwapWarning.TransactionInProgressWarning(
|
||||
title = resourceReference(R.string.warning_express_approval_in_progress_title),
|
||||
description = resourceReference(R.string.warning_express_approval_in_progress_message),
|
||||
),
|
||||
)
|
||||
} else if (quoteModel.preparedSwapConfigState.hasOutgoingTransaction) {
|
||||
warnings.add(
|
||||
SwapWarning.TransactionInProgressWarning(
|
||||
title = resourceReference(R.string.warning_express_active_transaction_title),
|
||||
description = resourceReference(
|
||||
id = R.string.warning_express_active_transaction_message,
|
||||
formatArgs = wrappedList(
|
||||
quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.network.currencySymbol,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddDomainWarnings(quoteModel: SwapState.QuotesLoadedState, warnings: MutableList<SwapWarning>) {
|
||||
quoteModel.warnings.forEach {
|
||||
when (it) {
|
||||
is Warning.ExistentialDepositWarning -> {
|
||||
warnings.add(createLeaveExistentialDepositWarning(quoteModel, it))
|
||||
}
|
||||
is Warning.MinAmountWarning -> {
|
||||
warnings.add(
|
||||
SwapWarning.GeneralError(
|
||||
NotificationConfig(
|
||||
title = resourceReference(R.string.send_notification_invalid_amount_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.warning_express_dust_message,
|
||||
wrappedList(
|
||||
it.dustValue.toPlainString(),
|
||||
it.dustValue.toPlainString(),
|
||||
),
|
||||
),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
is Warning.ReduceAmountWarning -> {
|
||||
warnings.add(
|
||||
SwapWarning.ReduceAmount(
|
||||
notificationConfig = createReduceAmountNotificationConfig(
|
||||
currencyName = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.name,
|
||||
amount = it.tezosFeeThreshold.toPlainString(),
|
||||
onConfirmClick = {
|
||||
val fromAmount = quoteModel.fromTokenInfo.tokenAmount
|
||||
val patchedAmount = fromAmount.copy(
|
||||
value = fromAmount.value - it.tezosFeeThreshold,
|
||||
)
|
||||
actions.onReduceAmount(patchedAmount)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
Warning.Cardano.InsufficientBalanceToTransferCoin -> {
|
||||
warnings.add(createInsufficientBalanceToTransferCoin())
|
||||
}
|
||||
is Warning.Cardano.InsufficientBalanceToTransferToken -> {
|
||||
warnings.add(createInsufficientBalanceToTransferToken(tokenName = it.tokenName))
|
||||
}
|
||||
is Warning.Cardano.MinAdaValueCharged -> {
|
||||
warnings.add(createMinAdaValueCharged(minAdaValue = it.minAdaValue, tokenName = it.tokenName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddNeedReserveToCreateAccountWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
) {
|
||||
val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value
|
||||
if (status is CryptoCurrencyStatus.NoAccount) {
|
||||
val amount = quoteModel.toTokenInfo.tokenAmount.value
|
||||
val amountToCreateAccount = status.amountToCreateAccount
|
||||
|
||||
if (amount < amountToCreateAccount) {
|
||||
warnings.add(
|
||||
SwapWarning.NeedReserveToCreateAccount(
|
||||
notificationConfig = createActivateAccountNotificationConfig(
|
||||
status.amountToCreateAccount,
|
||||
quoteModel.toTokenInfo.cryptoCurrencyStatus.currency.symbol,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLeaveExistentialDepositWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
domainWarning: Warning.ExistentialDepositWarning,
|
||||
): SwapWarning {
|
||||
val fromCurrency = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency
|
||||
val deposit = domainWarning.existentialDeposit.format { crypto(fromCurrency).uncapped() }
|
||||
|
||||
return SwapWarning.GeneralError(
|
||||
NotificationConfig(
|
||||
title = resourceReference(R.string.send_notification_existential_deposit_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_notification_existential_deposit_text,
|
||||
wrappedList(deposit),
|
||||
),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.common_ok),
|
||||
onClick = {
|
||||
actions.onLeaveExistentialDeposit(
|
||||
SwapAmount(
|
||||
domainWarning.minAvailableAmount,
|
||||
fromCurrency.decimals,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun maybeAddPermissionNeededWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
fromToken: CryptoCurrency,
|
||||
providerName: String,
|
||||
) {
|
||||
if (!quoteModel.preparedSwapConfigState.isAllowedToSpend &&
|
||||
quoteModel.preparedSwapConfigState.feeState is SwapFeeState.Enough &&
|
||||
quoteModel.permissionState is PermissionDataState.PermissionReadyForRequest
|
||||
) {
|
||||
warnings.add(
|
||||
SwapWarning.PermissionNeeded(
|
||||
createPermissionNotificationConfig(fromToken.symbol, providerName),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddNetworkFeeCoverageWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
selectedFeeType: FeeType,
|
||||
) {
|
||||
when (quoteModel.preparedSwapConfigState.includeFeeInAmount) {
|
||||
is IncludeFeeInAmount.Included -> {
|
||||
val fee = selectFeeByType(selectedFeeType, quoteModel.txFee) ?: return
|
||||
if (needShowNetworkFeeCoverageWarningShow(quoteModel)) {
|
||||
warnings.add(
|
||||
SwapWarning.GeneralWarning(
|
||||
createNetworkFeeCoverageNotificationConfig(
|
||||
fee.feeCryptoFormattedWithNative,
|
||||
fee.feeFiatFormattedWithNative,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun needShowNetworkFeeCoverageWarningShow(quoteModel: SwapState.QuotesLoadedState): Boolean {
|
||||
return quoteModel.warnings.none { it is Warning.ExistentialDepositWarning }
|
||||
}
|
||||
|
||||
private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): TxFee? {
|
||||
return when (txFeeState) {
|
||||
TxFeeState.Empty -> null
|
||||
is TxFeeState.SingleFeeState -> txFeeState.fee
|
||||
is TxFeeState.MultipleFeeState -> when (feeType) {
|
||||
FeeType.NORMAL -> txFeeState.normalFee
|
||||
FeeType.PRIORITY -> txFeeState.priorityFee
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddUnableCoverFeeWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
) {
|
||||
val feeEnoughState = quoteModel.preparedSwapConfigState.feeState as? SwapFeeState.NotEnough ?: return
|
||||
val needShowCoverWarning = quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||
quoteModel.permissionState !is PermissionDataState.PermissionLoading &&
|
||||
feeEnoughState.feeCurrency != fromToken
|
||||
if (needShowCoverWarning) {
|
||||
warnings.add(
|
||||
SwapWarning.UnableToCoverFeeWarning(
|
||||
createUnableToCoverFeeNotificationConfig(
|
||||
fromToken = fromToken,
|
||||
feeCurrency = feeEnoughState.feeCurrency,
|
||||
currencyName = feeEnoughState.currencyName ?: fromToken.network.name,
|
||||
currencySymbol = feeEnoughState.currencySymbol ?: fromToken.network.currencySymbol,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddInsufficientFundsWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
) {
|
||||
// check isBalanceEnough, but for dex includeFeeInAmount always Excluded
|
||||
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) {
|
||||
val amount = quoteModel.toTokenInfo.tokenAmount.value
|
||||
val amountToCreateAccount = status.amountToCreateAccount
|
||||
|
||||
if (amount < amountToCreateAccount) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
val preparedSwapConfigState = quoteModel.preparedSwapConfigState
|
||||
// check has has outgoing transaction
|
||||
if (preparedSwapConfigState.hasOutgoingTransaction) return false
|
||||
|
||||
// check has MinAmountWarning warning
|
||||
val hasCriticalWarning = quoteModel.warnings.any {
|
||||
it is Warning.MinAmountWarning ||
|
||||
it is Warning.Cardano.InsufficientBalanceToTransferCoin ||
|
||||
it is Warning.Cardano.InsufficientBalanceToTransferToken ||
|
||||
it is Warning.ExistentialDepositWarning
|
||||
}
|
||||
|
||||
if (hasCriticalWarning) return false
|
||||
|
||||
return when (preparedSwapConfigState.includeFeeInAmount) {
|
||||
IncludeFeeInAmount.BalanceNotEnough -> false
|
||||
IncludeFeeInAmount.Excluded ->
|
||||
preparedSwapConfigState.isAllowedToSpend &&
|
||||
preparedSwapConfigState.isBalanceEnough &&
|
||||
preparedSwapConfigState.feeState is SwapFeeState.Enough
|
||||
is IncludeFeeInAmount.Included -> true
|
||||
private fun getSwapButtonEnabled(notifications: ImmutableList<NotificationUM>): Boolean {
|
||||
return notifications.none {
|
||||
it is SwapNotificationUM.Error || it is NotificationUM.Error ||
|
||||
it is SwapNotificationUM.Warning.ExpressError || it is SwapNotificationUM.Warning.ExpressGeneralError ||
|
||||
it is SwapNotificationUM.Warning.NoAvailableTokensToSwap ||
|
||||
it is SwapNotificationUM.Warning.NeedReserveToCreateAccount
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -653,15 +392,13 @@ internal class StateBuilder(
|
|||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
val warnings = mutableListOf<SwapWarning>()
|
||||
warnings.add(getWarningForError(expressDataError, fromToken.cryptoCurrencyStatus.currency))
|
||||
if (includeFeeInAmount is IncludeFeeInAmount.Included && uiStateHolder.fee is FeeItemState.Content) {
|
||||
val feeCoverageNotification = createNetworkFeeCoverageNotificationConfig(
|
||||
uiStateHolder.fee.amountCrypto,
|
||||
uiStateHolder.fee.amountFiatFormatted,
|
||||
)
|
||||
warnings.add(SwapWarning.GeneralWarning(feeCoverageNotification))
|
||||
}
|
||||
val notifications = notificationsFactory.getQuotesErrorStateNotifications(
|
||||
expressDataError = expressDataError,
|
||||
fromToken = fromToken.cryptoCurrencyStatus.currency,
|
||||
feeItem = uiStateHolder.fee,
|
||||
includeFeeInAmount = includeFeeInAmount,
|
||||
)
|
||||
|
||||
val providerState = getProviderStateForError(
|
||||
swapProvider = swapProvider,
|
||||
fromToken = fromToken.cryptoCurrencyStatus.currency,
|
||||
|
|
@ -700,7 +437,7 @@ internal class StateBuilder(
|
|||
balance = fromToken.cryptoCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
|
||||
),
|
||||
receiveCardData = receiveCardData,
|
||||
warnings = warnings,
|
||||
notifications = notifications,
|
||||
permissionState = GiveTxPermissionState.Empty,
|
||||
fee = FeeItemState.Empty,
|
||||
swapButton = SwapButton(
|
||||
|
|
@ -754,57 +491,6 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getWarningForError(expressDataError: ExpressDataError, fromToken: CryptoCurrency): SwapWarning {
|
||||
val providerErrorMessage = getExpressErrorMessage(expressDataError)
|
||||
val providerErrorTitle = getExpressErrorTitle(expressDataError)
|
||||
return when (expressDataError) {
|
||||
is ExpressDataError.ExchangeTooSmallAmountError -> SwapWarning.GeneralError(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_express_too_minimal_amount_title,
|
||||
formatArgs = wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)),
|
||||
),
|
||||
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
is ExpressDataError.ExchangeTooBigAmountError -> SwapWarning.GeneralError(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_express_too_maximum_amount_title,
|
||||
formatArgs = wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)),
|
||||
),
|
||||
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
is ExpressDataError.ProviderDifferentAmountError -> SwapWarning.GeneralError(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = resourceReference(id = R.string.common_error),
|
||||
subtitle = resourceReference(
|
||||
R.string.express_error_provider_amount_roundup,
|
||||
formatArgs = wrappedList(
|
||||
expressDataError.code,
|
||||
expressDataError.fromProviderAmount.format { simple(decimals = expressDataError.decimals) },
|
||||
),
|
||||
),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
else -> SwapWarning.GeneralWarning(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = providerErrorTitle,
|
||||
subtitle = providerErrorMessage,
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_button_refresh),
|
||||
onClick = actions.onRetryClick,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createQuotesEmptyAmountState(
|
||||
uiStateHolder: SwapStateHolder,
|
||||
emptyAmountState: SwapState.EmptyAmountState,
|
||||
|
|
@ -843,7 +529,8 @@ internal class StateBuilder(
|
|||
balance = toTokenStatus?.getFormattedAmount(isNeedSymbol = false) ?: DASH_SIGN,
|
||||
isBalanceHidden = isBalanceHiddenProvider(),
|
||||
),
|
||||
warnings = emptyList(),
|
||||
notifications = persistentListOf(),
|
||||
isInsufficientFunds = false,
|
||||
fee = FeeItemState.Empty,
|
||||
swapButton = SwapButton(
|
||||
enabled = false,
|
||||
|
|
@ -1002,19 +689,8 @@ internal class StateBuilder(
|
|||
|
||||
fun createInitialErrorState(uiState: SwapStateHolder, code: Int, onRefreshClick: () -> Unit): SwapStateHolder {
|
||||
return uiState.copy(
|
||||
warnings = listOf(
|
||||
SwapWarning.GeneralWarning(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = TextReference.Res(R.string.warning_express_refresh_required_title),
|
||||
subtitle = TextReference.Res(R.string.express_error_code, wrappedList(code)),
|
||||
iconResId = R.drawable.ic_alert_triangle_20,
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = TextReference.Res(R.string.warning_button_refresh),
|
||||
onClick = onRefreshClick,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
isInsufficientFunds = false,
|
||||
notifications = notificationsFactory.getInitialErrorStateNotifications(code, onRefreshClick),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1051,17 +727,9 @@ internal class StateBuilder(
|
|||
}
|
||||
|
||||
fun loadingPermissionState(uiState: SwapStateHolder): SwapStateHolder {
|
||||
val warnings = uiState.warnings.filterNot { it is SwapWarning.PermissionNeeded }.toMutableList()
|
||||
warnings.add(
|
||||
0,
|
||||
SwapWarning.TransactionInProgressWarning(
|
||||
title = resourceReference(R.string.warning_express_approval_in_progress_title),
|
||||
description = resourceReference(R.string.warning_express_approval_in_progress_message),
|
||||
),
|
||||
)
|
||||
return uiState.copy(
|
||||
permissionState = GiveTxPermissionState.InProgress,
|
||||
warnings = warnings,
|
||||
notifications = notificationsFactory.getApprovalInProgressStateNotification(uiState.notifications),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1211,17 +879,14 @@ internal class StateBuilder(
|
|||
|
||||
fun clearAlert(uiState: SwapStateHolder): SwapStateHolder = uiState.copy(event = consumedEvent())
|
||||
|
||||
fun addWarning(uiState: SwapStateHolder, message: TextReference?, onClick: () -> Unit): SwapStateHolder {
|
||||
val renewWarnings = uiState.warnings.toMutableList()
|
||||
renewWarnings.add(
|
||||
SwapWarning.GenericWarning(
|
||||
fun addNotification(uiState: SwapStateHolder, message: TextReference?, onClick: () -> Unit): SwapStateHolder {
|
||||
return uiState.copy(
|
||||
notifications = notificationsFactory.getGeneralErrorStateNotifications(
|
||||
notifications = uiState.notifications,
|
||||
message = message,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
return uiState.copy(
|
||||
warnings = renewWarnings,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertPermissionState(
|
||||
|
|
@ -1272,17 +937,6 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
fun showWebViewBottomSheet(uiState: SwapStateHolder, url: String, onDismiss: () -> Unit): SwapStateHolder {
|
||||
val config = WebViewBottomSheetConfig(url = url)
|
||||
return uiState.copy(
|
||||
bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = onDismiss,
|
||||
content = config,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun showPermissionBottomSheet(uiState: SwapStateHolder, onDismiss: () -> Unit): SwapStateHolder {
|
||||
val permissionState = uiState.permissionState
|
||||
if (permissionState is GiveTxPermissionState.ReadyForRequest) {
|
||||
|
|
@ -1368,21 +1022,6 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
fun updateSelectedProvider(uiState: SwapStateHolder, selectedProviderId: String): SwapStateHolder {
|
||||
val config = uiState.bottomSheetConfig?.content as? ChooseProviderBottomSheetConfig
|
||||
return if (config != null) {
|
||||
uiState.copy(
|
||||
bottomSheetConfig = uiState.bottomSheetConfig.copy(
|
||||
content = config.copy(
|
||||
selectedProviderId = selectedProviderId,
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
uiState
|
||||
}
|
||||
}
|
||||
|
||||
fun showSelectFeeBottomSheet(
|
||||
uiState: SwapStateHolder,
|
||||
selectedFee: FeeType,
|
||||
|
|
@ -1420,21 +1059,6 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
fun updateSelectedFeeBottomSheet(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(
|
||||
|
|
@ -1483,122 +1107,6 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
// region warnings
|
||||
private fun createPermissionNotificationConfig(fromTokenSymbol: String, providerName: String): NotificationConfig {
|
||||
return NotificationConfig(
|
||||
title = resourceReference(R.string.express_provider_permission_needed),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.give_permission_swap_subtitle,
|
||||
formatArgs = wrappedList(providerName, fromTokenSymbol),
|
||||
),
|
||||
iconResId = R.drawable.ic_locked_24,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createActivateAccountNotificationConfig(amount: BigDecimal, token: String): NotificationConfig {
|
||||
return NotificationConfig(
|
||||
title = resourceReference(
|
||||
id = R.string.send_notification_invalid_reserve_amount_title,
|
||||
formatArgs = wrappedList("$amount $token"),
|
||||
),
|
||||
subtitle = resourceReference(R.string.send_notification_invalid_reserve_amount_text),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createReduceAmountNotificationConfig(
|
||||
currencyName: String,
|
||||
amount: String,
|
||||
onConfirmClick: () -> Unit,
|
||||
): NotificationConfig {
|
||||
return NotificationConfig(
|
||||
title = resourceReference(R.string.send_notification_high_fee_title),
|
||||
subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(currencyName, amount)),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.xtz_withdrawal_message_reduce, wrappedList(amount)),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createUnableToCoverFeeNotificationConfig(
|
||||
fromToken: CryptoCurrency,
|
||||
feeCurrency: CryptoCurrency?,
|
||||
currencyName: String,
|
||||
currencySymbol: String,
|
||||
): NotificationConfig {
|
||||
val buttonState = feeCurrency?.let {
|
||||
NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.common_buy_currency, wrappedList(currencySymbol)),
|
||||
onClick = { actions.onBuyClick(it) },
|
||||
)
|
||||
}
|
||||
return NotificationConfig(
|
||||
title = resourceReference(
|
||||
R.string.warning_express_not_enough_fee_for_token_tx_title,
|
||||
wrappedList(fromToken.network.name),
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
R.string.warning_express_not_enough_fee_for_token_tx_description,
|
||||
wrappedList(currencyName, currencySymbol),
|
||||
),
|
||||
iconResId = fromToken.networkIconResId,
|
||||
buttonsState = buttonState,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createNetworkFeeCoverageNotificationConfig(
|
||||
cryptoAmount: String,
|
||||
fiatAmount: String,
|
||||
): NotificationConfig {
|
||||
return NotificationConfig(
|
||||
title = resourceReference(R.string.send_network_fee_warning_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.common_network_fee_warning_content,
|
||||
wrappedList(cryptoAmount, fiatAmount),
|
||||
),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMinAdaValueCharged(minAdaValue: String, tokenName: String): SwapWarning {
|
||||
return SwapWarning.Cardano.MinAdaValueCharged(
|
||||
NotificationConfig(
|
||||
title = resourceReference(id = R.string.cardano_coin_will_be_send_with_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_coin_will_be_send_with_token_description,
|
||||
formatArgs = wrappedList(minAdaValue, tokenName),
|
||||
),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createInsufficientBalanceToTransferCoin(): SwapWarning {
|
||||
return SwapWarning.Cardano.InsufficientBalanceToTransferCoin(
|
||||
NotificationConfig(
|
||||
title = resourceReference(id = R.string.cardano_max_amount_has_token_title),
|
||||
subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createInsufficientBalanceToTransferToken(tokenName: String): SwapWarning {
|
||||
return SwapWarning.Cardano.InsufficientBalanceToTransferToken(
|
||||
NotificationConfig(
|
||||
title = resourceReference(id = R.string.cardano_insufficient_balance_to_send_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_insufficient_balance_to_send_token_description,
|
||||
formatArgs = wrappedList(tokenName),
|
||||
),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
}
|
||||
// end region
|
||||
|
||||
private fun getShortAddressValue(fullAddress: String): String {
|
||||
check(fullAddress.length > ADDRESS_MIN_LENGTH) { "Invalid address" }
|
||||
val firstAddressPart = fullAddress.substring(startIndex = 0, endIndex = ADDRESS_FIRST_PART_LENGTH)
|
||||
|
|
@ -1712,14 +1220,6 @@ internal class StateBuilder(
|
|||
return amount.format { crypto(symbol, currency.decimals) }
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String {
|
||||
val fiatAmount = value.fiatAmount ?: return DASH_SIGN
|
||||
val appCurrency = appCurrencyProvider()
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
|
||||
}
|
||||
|
||||
private fun getFormattedFiatAmount(amount: BigDecimal?): String {
|
||||
val appCurrency = appCurrencyProvider()
|
||||
|
||||
|
|
@ -1735,10 +1235,6 @@ internal class StateBuilder(
|
|||
return this.divide(to, min(rateDecimals, MAX_DECIMALS_TO_SHOW), RoundingMode.HALF_UP)
|
||||
}
|
||||
|
||||
private fun toBigDecimalOrNull(text: String): BigDecimal? {
|
||||
return text.replace(",", ".").toBigDecimalOrNull()
|
||||
}
|
||||
|
||||
private fun getLocaleName(): String {
|
||||
return if (Locale.getDefault().language == "ru") {
|
||||
RU_LOCALE
|
||||
|
|
|
|||
|
|
@ -1,22 +1,19 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.common.ui.alerts.models.AlertUM
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.shareText
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.feature.swap.models.states.events.SwapEvent
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
|
||||
@Composable
|
||||
internal fun SwapEventEffect(event: StateEvent<SwapEvent>) {
|
||||
val context = LocalContext.current
|
||||
var alertConfig by remember { mutableStateOf<AlertUM?>(value = null) }
|
||||
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
|
@ -35,9 +32,6 @@ internal fun SwapEventEffect(event: StateEvent<SwapEvent>) {
|
|||
is SwapEvent.ShowAlert -> {
|
||||
alertConfig = value.alert
|
||||
}
|
||||
is SwapEvent.ShowShareDialog -> {
|
||||
context.shareText(value.txUrl)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -53,7 +47,7 @@ internal fun SwapAlert(state: AlertUM, onDismiss: () -> Unit) {
|
|||
},
|
||||
)
|
||||
val dismissButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_cancel),
|
||||
title = stringResourceSafe(id = R.string.common_cancel),
|
||||
onClick = onDismiss,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,16 +6,15 @@ 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.res.stringResource
|
||||
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.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.WebViewBottomSheetConfig
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
|
||||
@Composable
|
||||
|
|
@ -26,7 +25,7 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) {
|
|||
modifier = Modifier.systemBarsPadding(),
|
||||
topBar = {
|
||||
AppBarWithBackButton(
|
||||
text = stringResource(R.string.common_swap),
|
||||
text = stringResourceSafe(R.string.common_swap),
|
||||
onBackClick = stateHolder.onBackClicked,
|
||||
iconRes = R.drawable.ic_close_24,
|
||||
)
|
||||
|
|
@ -40,20 +39,13 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) {
|
|||
modifier = Modifier.padding(scaffoldPaddings),
|
||||
)
|
||||
|
||||
stateHolder.bottomSheetConfig?.let { config ->
|
||||
if (stateHolder.bottomSheetConfig != null) {
|
||||
val config = stateHolder.bottomSheetConfig
|
||||
|
||||
when (config.content) {
|
||||
is GiveTxPermissionBottomSheetConfig -> {
|
||||
GiveTxPermissionBottomSheet(config = config)
|
||||
}
|
||||
is ChooseProviderBottomSheetConfig -> {
|
||||
ChooseProviderBottomSheet(config = config)
|
||||
}
|
||||
is ChooseFeeBottomSheetConfig -> {
|
||||
ChooseFeeBottomSheet(config = config)
|
||||
}
|
||||
is WebViewBottomSheetConfig -> {
|
||||
WebViewBottomSheet(config = config)
|
||||
}
|
||||
is GiveTxPermissionBottomSheetConfig -> GiveTxPermissionBottomSheet(config = config)
|
||||
is ChooseProviderBottomSheetConfig -> ChooseProviderBottomSheet(config = config)
|
||||
is ChooseFeeBottomSheetConfig -> ChooseFeeBottomSheet(config = config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
|
|
@ -24,13 +23,10 @@ import androidx.compose.ui.text.withStyle
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.getActiveIconResByCoinId
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.domain.models.ui.FeeType
|
||||
|
|
@ -38,7 +34,9 @@ import com.tangem.feature.swap.domain.models.ui.PriceImpact
|
|||
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.models.states.SwapNotificationUM
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
|
|
@ -69,7 +67,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi
|
|||
|
||||
FeeItemBlock(state = state.fee)
|
||||
|
||||
if (state.warnings.isNotEmpty()) SwapWarnings(warnings = state.warnings)
|
||||
if (state.notifications.isNotEmpty()) SwapNotifications(notifications = state.notifications)
|
||||
|
||||
MainButton(state = state, onPermissionWarningClick = state.onShowPermissionBottomSheet)
|
||||
|
||||
|
|
@ -84,7 +82,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi
|
|||
|
||||
if (state.shouldShowMaxAmount && keyboard is Keyboard.Opened) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.send_max_amount_label),
|
||||
text = stringResourceSafe(id = R.string.send_max_amount_label),
|
||||
style = TangemTheme.typography.button,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier
|
||||
|
|
@ -206,7 +204,7 @@ private fun getAnnotatedStringForLegalsWithClick(
|
|||
return if (tos != null && policy != null) {
|
||||
val tosTitle = tos.title.resolveReference()
|
||||
val policyTitle = policy.title.resolveReference()
|
||||
val fullString = stringResource(id = R.string.express_legal_two_placeholders, tosTitle, policyTitle)
|
||||
val fullString = stringResourceSafe(id = R.string.express_legal_two_placeholders, tosTitle, policyTitle)
|
||||
val tosIndex = fullString.indexOf(tosTitle)
|
||||
val policyIndex = fullString.indexOf(policyTitle)
|
||||
val string = buildAnnotatedString {
|
||||
|
|
@ -238,7 +236,7 @@ private fun getAnnotatedStringForLegalsWithClick(
|
|||
val legal = requireNotNull(tos ?: policy) { "tos or policy must not be null" }
|
||||
val legalTitle = legal.title
|
||||
.resolveReference()
|
||||
val fullString = stringResource(id = R.string.express_legal_one_placeholder, legal)
|
||||
val fullString = stringResourceSafe(id = R.string.express_legal_one_placeholder, legal)
|
||||
val legalIndex = fullString.indexOf(legalTitle)
|
||||
val string = buildAnnotatedString {
|
||||
withStyle(SpanStyle(color = TangemTheme.colors.text.tertiary)) {
|
||||
|
|
@ -302,69 +300,27 @@ private fun SwapButton(state: SwapStateHolder, modifier: Modifier = Modifier) {
|
|||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
private fun SwapWarnings(warnings: List<SwapWarning>) {
|
||||
private fun SwapNotifications(notifications: List<NotificationUM>) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(color = TangemTheme.colors.background.secondary)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
warnings.forEach { warning ->
|
||||
when (warning) {
|
||||
is SwapWarning.PermissionNeeded -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
)
|
||||
}
|
||||
is SwapWarning.GenericWarning -> {
|
||||
val message = warning.message?.resolveReference()
|
||||
?: stringResource(id = R.string.common_unknown_error)
|
||||
notifications.forEach { notification ->
|
||||
when (notification) {
|
||||
is SwapNotificationUM.Error.GenericError -> {
|
||||
RefreshableWarningCard(
|
||||
title = stringResource(id = R.string.common_warning),
|
||||
description = message,
|
||||
onClick = warning.onClick,
|
||||
title = notification.title.resolveReference(),
|
||||
description = notification.config.subtitle.resolveReference(),
|
||||
onClick = notification.onConfirmClick,
|
||||
)
|
||||
}
|
||||
is SwapWarning.NoAvailableTokensToSwap -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
)
|
||||
}
|
||||
is SwapWarning.GeneralError -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
iconTint = TangemTheme.colors.icon.warning,
|
||||
)
|
||||
}
|
||||
is SwapWarning.UnableToCoverFeeWarning -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
)
|
||||
}
|
||||
is SwapWarning.GeneralWarning -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
)
|
||||
}
|
||||
is SwapWarning.GeneralInformational -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
iconTint = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
}
|
||||
is SwapWarning.NeedReserveToCreateAccount -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
)
|
||||
}
|
||||
is SwapWarning.ReduceAmount -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
)
|
||||
}
|
||||
is SwapWarning.TransactionInProgressWarning -> {
|
||||
is SwapNotificationUM.Error.ApprovalInProgressWarning,
|
||||
is SwapNotificationUM.Error.TransactionInProgressWarning,
|
||||
-> {
|
||||
CardWithIcon(
|
||||
title = warning.title.resolveReference(),
|
||||
description = warning.description.resolveReference(),
|
||||
title = notification.config.title?.resolveReference().orEmpty(),
|
||||
description = notification.config.subtitle.resolveReference(),
|
||||
icon = {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
|
|
@ -375,8 +331,20 @@ private fun SwapWarnings(warnings: List<SwapWarning>) {
|
|||
},
|
||||
)
|
||||
}
|
||||
is SwapWarning.Cardano -> Notification(config = warning.notificationConfig)
|
||||
SwapWarning.InsufficientFunds -> Unit
|
||||
else -> {
|
||||
Notification(
|
||||
config = notification.config,
|
||||
iconTint = when (notification) {
|
||||
is SwapNotificationUM.Error.UnableToCoverFeeWarning,
|
||||
is NotificationUM.Error.TokenExceedsBalance,
|
||||
is NotificationUM.Error.ExceedsBalance,
|
||||
is NotificationUM.Info,
|
||||
is NotificationUM.Warning,
|
||||
-> null
|
||||
is NotificationUM.Error -> TangemTheme.colors.icon.warning
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -387,18 +355,18 @@ private fun SwapWarnings(warnings: List<SwapWarning>) {
|
|||
private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> Unit) {
|
||||
// order is important
|
||||
when {
|
||||
state.warnings.any { it is SwapWarning.InsufficientFunds } -> {
|
||||
state.isInsufficientFunds -> {
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.swapping_insufficient_funds),
|
||||
text = stringResourceSafe(id = R.string.swapping_insufficient_funds),
|
||||
enabled = false,
|
||||
onClick = state.swapButton.onClick,
|
||||
)
|
||||
}
|
||||
state.warnings.any { it is SwapWarning.PermissionNeeded } -> {
|
||||
state.notifications.any { it is SwapNotificationUM.Info.PermissionNeeded } -> {
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.give_permission_title),
|
||||
text = stringResourceSafe(id = R.string.give_permission_title),
|
||||
enabled = true,
|
||||
onClick = onPermissionWarningClick,
|
||||
)
|
||||
|
|
@ -406,7 +374,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U
|
|||
else -> {
|
||||
PrimaryButtonIconEnd(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.swapping_swap_action),
|
||||
text = stringResourceSafe(id = R.string.swapping_swap_action),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
enabled = state.swapButton.enabled,
|
||||
onClick = state.swapButton.onClick,
|
||||
|
|
@ -459,21 +427,12 @@ private val state = SwapStateHolder(
|
|||
isClickable = true,
|
||||
onClick = {},
|
||||
),
|
||||
warnings = listOf(
|
||||
SwapWarning.PermissionNeeded(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = stringReference("Give Permission"),
|
||||
subtitle = stringReference("To continue swapping you need to give permission to Tangem"),
|
||||
iconResId = R.drawable.ic_locked_24,
|
||||
),
|
||||
),
|
||||
SwapWarning.NoAvailableTokensToSwap(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = stringReference("No tokens"),
|
||||
subtitle = stringReference("Swap tokens not available"),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
),
|
||||
notifications = persistentListOf(
|
||||
SwapNotificationUM.Info.PermissionNeeded(
|
||||
providerName = "Provider",
|
||||
fromTokenSymbol = "POL",
|
||||
),
|
||||
SwapNotificationUM.Warning.NoAvailableTokensToSwap("POLYGON"),
|
||||
),
|
||||
swapButton = SwapButton(enabled = true, onClick = {}),
|
||||
onRefresh = {},
|
||||
|
|
@ -484,6 +443,7 @@ private val state = SwapStateHolder(
|
|||
providerState = ProviderState.Loading(),
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
shouldShowMaxAmount = true,
|
||||
isInsufficientFunds = false,
|
||||
tosState = TosState(
|
||||
tosLink = LegalState(
|
||||
title = stringReference("Terms of Use"),
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
|
|
@ -27,6 +26,7 @@ import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
|||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
|
||||
|
|
@ -60,12 +60,12 @@ fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: () -> Unit)
|
|||
},
|
||||
topBar = {
|
||||
ExpandableSearchView(
|
||||
title = stringResource(R.string.swapping_token_list_title),
|
||||
title = stringResourceSafe(R.string.swapping_token_list_title),
|
||||
onBackClick = onBack,
|
||||
placeholderSearchText = stringResource(id = R.string.common_search_tokens),
|
||||
placeholderSearchText = stringResourceSafe(id = R.string.common_search_tokens),
|
||||
onSearchChange = state.onSearchEntered,
|
||||
onSearchDisplayClose = { state.onSearchEntered("") },
|
||||
subtitle = stringResource(id = R.string.express_exchange_token_list_subtitle),
|
||||
subtitle = stringResourceSafe(id = R.string.express_exchange_token_list_subtitle),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -92,7 +92,7 @@ private fun EmptyTokensList(modifier: Modifier = Modifier) {
|
|||
.padding(top = TangemTheme.dimens.spacing16)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing30)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
text = stringResource(id = R.string.exchange_tokens_empty_tokens),
|
||||
text = stringResourceSafe(id = R.string.exchange_tokens_empty_tokens),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
|
|
@ -113,7 +113,7 @@ private fun TokensNotFound(modifier: Modifier = Modifier) {
|
|||
.padding(top = TangemTheme.dimens.spacing32)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing30)
|
||||
.align(Alignment.TopCenter),
|
||||
text = stringResource(id = R.string.express_token_list_empty_search),
|
||||
text = stringResourceSafe(id = R.string.express_token_list_empty_search),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import androidx.compose.material.Scaffold
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
|
|
@ -19,6 +18,7 @@ import com.tangem.core.ui.components.inputrow.InputRowImage
|
|||
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -135,7 +135,7 @@ private fun SwapSuccessScreenButtons(
|
|||
if (txUrl.isNotBlank()) {
|
||||
Row {
|
||||
SecondaryButtonIconStart(
|
||||
text = stringResource(id = R.string.common_explore),
|
||||
text = stringResourceSafe(id = R.string.common_explore),
|
||||
iconResId = R.drawable.ic_web_24,
|
||||
onClick = onExploreClick,
|
||||
modifier = Modifier.weight(1f),
|
||||
|
|
@ -143,7 +143,7 @@ private fun SwapSuccessScreenButtons(
|
|||
if (showStatusButton) {
|
||||
SpacerW12()
|
||||
SecondaryButtonIconStart(
|
||||
text = stringResource(id = R.string.express_cex_status_button_title),
|
||||
text = stringResourceSafe(id = R.string.express_cex_status_button_title),
|
||||
iconResId = R.drawable.ic_arrow_top_right_24,
|
||||
onClick = onStatusClick,
|
||||
modifier = Modifier.weight(1f),
|
||||
|
|
@ -153,7 +153,7 @@ private fun SwapSuccessScreenButtons(
|
|||
SpacerH12()
|
||||
}
|
||||
PrimaryButton(
|
||||
text = stringResource(id = textRes),
|
||||
text = stringResourceSafe(id = textRes),
|
||||
enabled = true,
|
||||
onClick = onDoneClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import androidx.compose.ui.graphics.toArgb
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
|
|
@ -38,6 +37,7 @@ 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.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.ImageBackgroundContrastChecker
|
||||
|
|
@ -74,7 +74,7 @@ fun TransactionCard(
|
|||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
Header(balance = stringResource(R.string.common_balance, balance), type = type)
|
||||
Header(balance = stringResourceSafe(R.string.common_balance, balance), type = type)
|
||||
|
||||
Content(
|
||||
type = type,
|
||||
|
|
@ -134,7 +134,7 @@ fun TransactionCardEmpty(
|
|||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
Header(
|
||||
balance = stringResource(id = R.string.swapping_token_not_available),
|
||||
balance = stringResourceSafe(id = R.string.swapping_token_not_available),
|
||||
type = type,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.google.accompanist.web.WebView
|
||||
import com.google.accompanist.web.rememberWebViewState
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.models.states.WebViewBottomSheetConfig
|
||||
|
||||
@Composable
|
||||
fun WebViewBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemBottomSheet(
|
||||
config = config,
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
) { content: WebViewBottomSheetConfig ->
|
||||
WebViewBottomSheetContent(
|
||||
WebViewBottomSheetConfig(url = content.url),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WebViewBottomSheetContent(content: WebViewBottomSheetConfig) {
|
||||
val state = rememberWebViewState(content.url)
|
||||
val isInPreviewMode = LocalInspectionMode.current
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
WebView(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.secondary),
|
||||
captureBackPresses = false,
|
||||
onCreated = {
|
||||
if (!isInPreviewMode) {
|
||||
it.settings.apply {
|
||||
javaScriptEnabled = false
|
||||
allowFileAccess = false
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_WebViewBottomSheetContent() {
|
||||
TangemThemePreview {
|
||||
WebViewBottomSheetContent(
|
||||
content = WebViewBottomSheetConfig(
|
||||
url = "https://tangem.com/en/",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,13 +7,16 @@ 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
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class SwapProcessDataState(
|
||||
// Initial network id
|
||||
val fromCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
val toCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
val feePaidCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
// Amount from input
|
||||
val amount: String? = null,
|
||||
val reduceBalanceBy: BigDecimal = BigDecimal.ZERO,
|
||||
val approveDataModel: RequestApproveStateData? = null,
|
||||
val swapDataModel: SwapDataModel? = null,
|
||||
val selectedFee: TxFee? = null,
|
||||
|
|
|
|||
|
|
@ -25,10 +25,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
|||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -44,8 +41,8 @@ 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.SwapStateHolder
|
||||
import com.tangem.feature.swap.models.SwapWarning
|
||||
import com.tangem.feature.swap.models.UiActions
|
||||
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.router.SwapNavScreen
|
||||
import com.tangem.feature.swap.router.SwapRouter
|
||||
|
|
@ -81,6 +78,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
private val getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase,
|
||||
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
|
||||
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
|
||||
|
|
@ -142,6 +140,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
// shows currency order (direct - swap initial to selected, reversed = selected to initial)
|
||||
private var isOrderReversed = false
|
||||
private val lastAmount = mutableStateOf(INITIAL_AMOUNT)
|
||||
private val lastReducedBalanceBy = mutableStateOf(BigDecimal.ZERO)
|
||||
private var swapRouter: SwapRouter by Delegates.notNull()
|
||||
|
||||
private val isUserResolvableError: (SwapState) -> Boolean = {
|
||||
|
|
@ -310,6 +309,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
fromToken = fromCurrencyStatus,
|
||||
toToken = toCurrencyStatus,
|
||||
amount = lastAmount.value,
|
||||
reduceBalanceBy = lastReducedBalanceBy.value,
|
||||
toProvidersList = findSwapProviders(fromCurrencyStatus, toCurrencyStatus),
|
||||
)
|
||||
}
|
||||
|
|
@ -327,6 +327,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
fromToken: CryptoCurrencyStatus,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
amount: String,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
toProvidersList: List<SwapProvider>,
|
||||
isSilent: Boolean = false,
|
||||
) {
|
||||
|
|
@ -345,6 +346,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
amount = amount,
|
||||
reduceBalanceBy = reduceBalanceBy,
|
||||
toProvidersList = toProvidersList,
|
||||
),
|
||||
)
|
||||
|
|
@ -360,6 +362,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
toToken = toCurrency,
|
||||
amount = amount,
|
||||
isSilent = isSilent,
|
||||
reduceBalanceBy = dataState.reduceBalanceBy,
|
||||
toProvidersList = findSwapProviders(fromCurrency, toCurrency),
|
||||
)
|
||||
}
|
||||
|
|
@ -369,6 +372,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
fromToken: CryptoCurrencyStatus,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
amount: String,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
toProvidersList: List<SwapProvider>,
|
||||
): PeriodicTask<Map<SwapProvider, SwapState>> {
|
||||
return PeriodicTask(
|
||||
|
|
@ -378,6 +382,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
runCatching(dispatchers.io) {
|
||||
dataState = dataState.copy(
|
||||
amount = amount,
|
||||
reduceBalanceBy = reduceBalanceBy,
|
||||
swapDataModel = null,
|
||||
approveDataModel = null,
|
||||
)
|
||||
|
|
@ -386,6 +391,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
toToken = toToken,
|
||||
providers = toProvidersList,
|
||||
amountToSwap = amount,
|
||||
reduceBalanceBy = reduceBalanceBy,
|
||||
selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||
)
|
||||
}
|
||||
|
|
@ -408,7 +414,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
},
|
||||
onError = {
|
||||
Timber.e("Error when loading quotes: $it")
|
||||
uiState = stateBuilder.addWarning(uiState, null) { startLoadingQuotesFromLastState() }
|
||||
uiState = stateBuilder.addNotification(uiState, null) { startLoadingQuotesFromLastState() }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -423,13 +429,14 @@ internal class SwapViewModel @Inject constructor(
|
|||
uiStateHolder = uiState,
|
||||
quoteModel = state,
|
||||
fromToken = fromToken.currency,
|
||||
feeCryptoCurrencyStatus = dataState.feePaidCryptoCurrency,
|
||||
swapProvider = provider,
|
||||
bestRatedProviderId = bestRatedProviderId,
|
||||
isNeedBestRateBadge = dataState.lastLoadedSwapStates.consideredProvidersStates().size > 1,
|
||||
selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
)
|
||||
if (uiState.warnings.any { it is SwapWarning.UnableToCoverFeeWarning }) {
|
||||
if (uiState.notifications.any { it is SwapNotificationUM.Error.UnableToCoverFeeWarning }) {
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.NoticeNotEnoughFee(
|
||||
token = initialCurrencyFrom.symbol,
|
||||
|
|
@ -822,6 +829,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
amount = lastAmount.value,
|
||||
reduceBalanceBy = lastReducedBalanceBy.value,
|
||||
toProvidersList = findSwapProviders(fromToken, toToken),
|
||||
)
|
||||
swapRouter.openScreen(SwapNavScreen.Main)
|
||||
|
|
@ -846,6 +854,13 @@ internal class SwapViewModel @Inject constructor(
|
|||
.onEach {
|
||||
Timber.d("${coin.id} balance is ${it.value.amount}")
|
||||
|
||||
dataState = dataState.copy(
|
||||
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = it,
|
||||
).getOrNull() ?: it,
|
||||
)
|
||||
|
||||
uiState = if (isFromCurrency) {
|
||||
dataState = dataState.copy(fromCryptoCurrency = it)
|
||||
stateBuilder.updateSendCurrencyBalance(uiState, it)
|
||||
|
|
@ -884,6 +899,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
).getOrNull()
|
||||
val decimals = newFromToken.currency.decimals
|
||||
lastAmount.value = cutAmountWithDecimals(decimals, lastAmount.value)
|
||||
lastReducedBalanceBy.value = BigDecimal.ZERO
|
||||
uiState = stateBuilder.updateSwapAmount(
|
||||
uiState = uiState,
|
||||
amountFormatted = inputNumberFormatter.formatWithThousands(lastAmount.value, decimals),
|
||||
|
|
@ -895,13 +911,18 @@ internal class SwapViewModel @Inject constructor(
|
|||
fromToken = newFromToken,
|
||||
toToken = newToToken,
|
||||
amount = lastAmount.value,
|
||||
reduceBalanceBy = lastReducedBalanceBy.value,
|
||||
toProvidersList = findSwapProviders(newFromToken, newToToken),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAmountChanged(value: String) {
|
||||
private fun onAmountChanged(
|
||||
value: String,
|
||||
forceQuotesUpdate: Boolean = false,
|
||||
reduceBalanceBy: BigDecimal = BigDecimal.ZERO,
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
val fromToken = dataState.fromCryptoCurrency
|
||||
val toToken = dataState.toCryptoCurrency
|
||||
|
|
@ -913,6 +934,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
fromToken,
|
||||
).getOrNull()
|
||||
lastAmount.value = cutValue
|
||||
lastReducedBalanceBy.value = reduceBalanceBy
|
||||
uiState = stateBuilder.updateSwapAmount(
|
||||
uiState = uiState,
|
||||
amountFormatted = inputNumberFormatter.formatWithThousands(cutValue, decimals),
|
||||
|
|
@ -926,11 +948,12 @@ internal class SwapViewModel @Inject constructor(
|
|||
isAmountChangedByUser = true
|
||||
}
|
||||
|
||||
amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY) {
|
||||
amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY, forceUpdate = forceQuotesUpdate) {
|
||||
startLoadingQuotes(
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
amount = lastAmount.value,
|
||||
reduceBalanceBy = lastReducedBalanceBy.value,
|
||||
toProvidersList = findSwapProviders(fromToken, toToken),
|
||||
)
|
||||
}
|
||||
|
|
@ -946,12 +969,12 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onReduceAmountClicked(newAmount: SwapAmount) {
|
||||
onAmountChanged(newAmount.formatToUIRepresentation())
|
||||
}
|
||||
|
||||
private fun onLeaveExistentialDepositClicked(newAmount: SwapAmount) {
|
||||
onAmountChanged(newAmount.formatToUIRepresentation())
|
||||
private fun onReduceAmountClicked(newAmount: SwapAmount, reduceBalanceBy: BigDecimal = BigDecimal.ZERO) {
|
||||
onAmountChanged(
|
||||
value = newAmount.formatToUIRepresentation(),
|
||||
forceQuotesUpdate = true,
|
||||
reduceBalanceBy = reduceBalanceBy,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onAmountSelected(selected: Boolean) {
|
||||
|
|
@ -1012,8 +1035,8 @@ internal class SwapViewModel @Inject constructor(
|
|||
onSearchEntered("")
|
||||
},
|
||||
onMaxAmountSelected = ::onMaxAmountClicked,
|
||||
onReduceAmount = ::onReduceAmountClicked,
|
||||
onLeaveExistentialDeposit = ::onLeaveExistentialDepositClicked,
|
||||
onReduceToAmount = ::onReduceAmountClicked,
|
||||
onReduceByAmount = ::onReduceAmountClicked,
|
||||
openPermissionBottomSheet = {
|
||||
singleTaskScheduler.cancelTask()
|
||||
analyticsEventHandler.send(SwapEvents.ButtonGivePermissionClicked)
|
||||
|
|
@ -1040,20 +1063,10 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
},
|
||||
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.dismissBottomSheet(uiState)
|
||||
dataState = dataState.copy(selectedFee = it)
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
val updatedState = swapInteractor.updateQuotesStateWithSelectedFee(
|
||||
state = state,
|
||||
selectedFee = it.feeType,
|
||||
fromToken = fromToken,
|
||||
amountToSwap = amountToSwap,
|
||||
)
|
||||
setupLoadedState(selectedProvider, updatedState, fromToken)
|
||||
startLoadingQuotesFromLastState(false)
|
||||
}
|
||||
},
|
||||
onProviderClick = { providerId ->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue