Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-20 11:56:20 +05:00
parent f5eaca92f6
commit dea7034aad
26 changed files with 349 additions and 716 deletions

View file

@ -1,56 +0,0 @@
package com.tangem.common.ui.alerts
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
import com.tangem.common.ui.alerts.models.AlertTransactionErrorUM
import com.tangem.common.ui.alerts.models.AlertUM
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.utils.converter.Converter
class TransactionErrorAlertConverter(
private val popBackStack: () -> Unit,
private val onFailedTxEmailClick: (String) -> Unit,
) : Converter<SendTransactionError, AlertUM?> {
override fun convert(value: SendTransactionError): AlertUM? {
return when (value) {
is SendTransactionError.DemoCardError -> AlertDemoModeUM(
onConfirmClick = popBackStack,
)
is SendTransactionError.TangemSdkError -> AlertTransactionErrorUM(
code = value.code.toString(),
cause = null,
causeTextReference = resourceReference(value.messageRes, wrappedList(value.args)),
onConfirmClick = { onFailedTxEmailClick(value.code.toString()) },
)
is SendTransactionError.BlockchainSdkError -> AlertTransactionErrorUM(
code = value.code.toString(),
cause = value.message,
onConfirmClick = { onFailedTxEmailClick("${value.code}: ${value.message.orEmpty()}") },
)
is SendTransactionError.DataError -> AlertTransactionErrorUM(
code = "",
cause = value.message,
onConfirmClick = { onFailedTxEmailClick(value.message.orEmpty()) },
)
is SendTransactionError.NetworkError -> AlertTransactionErrorUM(
code = value.code.orEmpty(),
cause = value.message.orEmpty(),
onConfirmClick = { onFailedTxEmailClick(value.message.orEmpty()) },
)
is SendTransactionError.UnknownError -> AlertTransactionErrorUM(
code = "",
cause = value.ex?.localizedMessage,
onConfirmClick = { onFailedTxEmailClick(value.ex?.localizedMessage.orEmpty()) },
)
is SendTransactionError.CreateAccountUnderfunded -> AlertTransactionErrorUM(
code = "",
cause = null,
causeTextReference = resourceReference(R.string.no_account_polkadot, wrappedList(value.amount)),
onConfirmClick = popBackStack,
)
else -> null
}
}
}

View file

@ -0,0 +1,83 @@
package com.tangem.common.ui.alerts
import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.domain.transaction.error.SendTransactionError
import javax.inject.Inject
class TransactionErrorDialogFactory @Inject constructor() {
fun create(
error: SendTransactionError,
popBackStack: () -> Unit,
onFailedTxEmailClick: (String) -> Unit,
): DialogMessage? {
return when (error) {
is SendTransactionError.DemoCardError -> demoModeDialog(popBackStack)
is SendTransactionError.TangemSdkError -> transactionErrorDialog(
causeTextReference = resourceReference(error.messageRes, wrappedList(error.args)),
code = error.code.toString(),
onConfirmClick = { onFailedTxEmailClick(error.code.toString()) },
)
is SendTransactionError.BlockchainSdkError -> transactionErrorDialog(
cause = error.message,
code = error.code.toString(),
onConfirmClick = { onFailedTxEmailClick("${error.code}: ${error.message.orEmpty()}") },
)
is SendTransactionError.DataError -> transactionErrorDialog(
cause = error.message,
code = "",
onConfirmClick = { onFailedTxEmailClick(error.message.orEmpty()) },
)
is SendTransactionError.NetworkError -> transactionErrorDialog(
cause = error.message.orEmpty(),
code = error.code.orEmpty(),
onConfirmClick = { onFailedTxEmailClick(error.message.orEmpty()) },
)
is SendTransactionError.UnknownError -> transactionErrorDialog(
cause = error.ex?.localizedMessage,
code = "",
onConfirmClick = { onFailedTxEmailClick(error.ex?.localizedMessage.orEmpty()) },
)
is SendTransactionError.CreateAccountUnderfunded -> transactionErrorDialog(
causeTextReference = resourceReference(
R.string.no_account_polkadot,
wrappedList(error.amount),
),
code = "",
onConfirmClick = popBackStack,
)
else -> null
}
}
private fun demoModeDialog(onConfirmClick: () -> Unit): DialogMessage = DialogMessage(
title = resourceReference(id = R.string.warning_demo_mode_title),
message = resourceReference(id = R.string.warning_demo_mode_message),
firstAction = EventMessageAction(
title = resourceReference(id = R.string.common_ok),
onClick = onConfirmClick,
),
)
private fun transactionErrorDialog(
cause: String? = null,
causeTextReference: TextReference? = null,
code: String,
onConfirmClick: () -> Unit,
): DialogMessage = DialogMessage(
title = resourceReference(id = R.string.send_alert_transaction_failed_title),
message = resourceReference(
id = R.string.send_alert_transaction_failed_text,
formatArgs = wrappedList(causeTextReference ?: cause.orEmpty(), code),
),
firstAction = EventMessageAction(
title = resourceReference(id = R.string.common_support),
onClick = onConfirmClick,
),
)
}

View file

@ -1,13 +0,0 @@
package com.tangem.common.ui.alerts.models
import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
data class AlertDemoModeUM(
override val onConfirmClick: () -> Unit,
) : AlertUM {
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
override val title: TextReference = resourceReference(id = R.string.warning_demo_mode_title)
override val message: TextReference = resourceReference(id = R.string.warning_demo_mode_message)
}

View file

@ -1,21 +0,0 @@
package com.tangem.common.ui.alerts.models
import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
data class AlertTransactionErrorUM(
val code: String,
val cause: String?,
val causeTextReference: TextReference? = null,
override val onConfirmClick: () -> Unit,
) : AlertUM {
override val title: TextReference = resourceReference(id = R.string.send_alert_transaction_failed_title)
override val message: TextReference = resourceReference(
id = R.string.send_alert_transaction_failed_text,
formatArgs = wrappedList(causeTextReference ?: cause.orEmpty(), code),
)
override val confirmButtonText: TextReference =
resourceReference(id = R.string.common_support)
}

View file

@ -1,12 +0,0 @@
package com.tangem.common.ui.alerts.models
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
@Immutable
interface AlertUM {
val title: TextReference?
val message: TextReference
val confirmButtonText: TextReference
val onConfirmClick: (() -> Unit)?
}

View file

@ -1,15 +1,14 @@
package com.tangem.features.onboarding.v2.common.ui
import com.tangem.common.ui.alerts.models.AlertUM
import com.tangem.core.ui.extensions.TextReference
internal data class OnboardingDialogUM(
override val title: TextReference,
override val message: TextReference,
val title: TextReference,
val message: TextReference,
val dismissButtonText: TextReference,
override val confirmButtonText: TextReference,
val confirmButtonText: TextReference,
val dismissWarningColor: Boolean = false,
override val onConfirmClick: () -> Unit,
val onConfirmClick: () -> Unit,
val onDismissButtonClick: () -> Unit,
val onDismiss: () -> Unit,
) : AlertUM
)

View file

@ -3,7 +3,6 @@ package com.tangem.features.onramp.selecttoken.model
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
@ -13,8 +12,8 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.demo.IsDemoCardUseCase
@ -159,18 +158,9 @@ internal class OnrampOperationModel @Inject constructor(
private fun showErrorIfDemoModeOrElse(action: () -> Unit) {
if (selectedUserWallet is UserWallet.Cold && isDemoCardUseCase(cardId = selectedUserWallet.cardId)) {
val alertUM = AlertDemoModeUM(onConfirmClick = {})
val message = DialogMessage(
title = alertUM.title,
message = alertUM.message,
firstActionBuilder = {
EventMessageAction(
title = alertUM.confirmButtonText,
onClick = alertUM.onConfirmClick,
)
},
secondActionBuilder = { cancelAction() },
title = resourceReference(id = R.string.warning_demo_mode_title),
message = resourceReference(id = R.string.warning_demo_mode_message),
)
messageSender.send(message)

View file

@ -1,7 +1,6 @@
package com.tangem.features.send.v2.common
import com.tangem.common.ui.alerts.TransactionErrorAlertConverter
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
import com.tangem.common.ui.alerts.TransactionErrorDialogFactory
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.resourceReference
@ -14,6 +13,7 @@ import javax.inject.Inject
@ModelScoped
internal class SendConfirmAlertFactory @Inject constructor(
private val messageSender: UiMessageSender,
private val transactionErrorDialogFactory: TransactionErrorDialogFactory,
) {
fun getGenericErrorState(onFailedTxEmailClick: () -> Unit, popBack: () -> Unit = {}) {
@ -31,34 +31,16 @@ internal class SendConfirmAlertFactory @Inject constructor(
}
fun getSendTransactionErrorState(
error: SendTransactionError?,
error: SendTransactionError,
popBack: () -> Unit,
onFailedTxEmailClick: (String) -> Unit,
) {
val transactionErrorAlertConverter = TransactionErrorAlertConverter(
val errorDialog = transactionErrorDialogFactory.create(
error = error,
popBackStack = popBack,
onFailedTxEmailClick = onFailedTxEmailClick,
)
) ?: return
val errorAlert = error?.let { transactionErrorAlertConverter.convert(error) } ?: return
val onConfirmClick = errorAlert.onConfirmClick ?: return
messageSender.send(
DialogMessage(
title = errorAlert.title,
message = errorAlert.message,
firstActionBuilder = {
EventMessageAction(
title = errorAlert.confirmButtonText,
onClick = onConfirmClick,
)
},
secondActionBuilder = if (errorAlert !is AlertDemoModeUM) {
{ cancelAction() }
} else {
null
},
),
)
messageSender.send(errorDialog)
}
}

View file

@ -67,7 +67,6 @@ import com.tangem.features.staking.impl.navigation.InnerStakingRouter
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
import com.tangem.features.staking.impl.presentation.state.events.StakingAlertUM
import com.tangem.features.staking.impl.presentation.state.events.StakingEvent
import com.tangem.features.staking.impl.presentation.state.events.StakingEventFactory
import com.tangem.features.staking.impl.presentation.state.helpers.StakingBalanceUpdater
import com.tangem.features.staking.impl.presentation.state.helpers.StakingFeeLoader
@ -249,7 +248,7 @@ internal class StakingModel @Inject constructor(
private val stakingEventFactory: StakingEventFactory
get() = StakingEventFactory(
stateController = stateController,
messageSender = messageSender,
popBackStack = ::onBackClick,
onFailedTxEmailClick = ::onFailedTxEmailClick,
)
@ -503,11 +502,7 @@ internal class StakingModel @Inject constructor(
cryptoCurrencyStatus = cryptoCurrencyStatus,
),
)
stateController.updateEvent(
StakingEvent.ShowAlert(
StakingAlertUM.FeeIncreased(stateController::dismissAlert),
),
)
messageSender.send(StakingAlertUM.feeIncreased {})
updateNotifications()
},
onTransactionExpired = {
@ -571,9 +566,7 @@ internal class StakingModel @Inject constructor(
override fun onAmountEnterClick() {
if (integration.preferredTargets.isEmpty()) {
stateController.updateEvent(
StakingEvent.ShowAlert(StakingAlertUM.NoAvailableValidators),
)
messageSender.send(StakingAlertUM.noAvailableValidators())
} else {
if (uiState.value.actionType is StakingActionCommonType.Enter) {
stateController.updateAll(
@ -1047,11 +1040,7 @@ internal class StakingModel @Inject constructor(
}
override fun showPrimaryClickAlert() {
stateController.updateEvent(
StakingEvent.ShowAlert(
StakingAlertUM.StakeMoreClickUnavailable(cryptoCurrencyStatus.currency),
),
)
messageSender.send(StakingAlertUM.stakeMoreClickUnavailable(cryptoCurrencyStatus.currency))
}
override fun onOpenLearnMoreAboutApproveClick() {

View file

@ -3,12 +3,9 @@ package com.tangem.features.staking.impl.presentation.state
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.presentation.state.events.StakingEvent
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
import com.tangem.features.staking.impl.presentation.state.transformers.SetButtonsStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.SetTitleTransformer
@ -72,16 +69,6 @@ internal class StakingStateController @Inject constructor(
mutableUiState.update(function = titleTransformer::transform)
}
fun updateEvent(event: StakingEvent?) {
mutableUiState.update {
it.copy(event = event?.let { triggeredEvent(event, ::dismissAlert) } ?: consumedEvent())
}
}
fun dismissAlert() {
mutableUiState.update { it.copy(event = consumedEvent()) }
}
private fun getInitialState(): StakingUiState {
return StakingUiState(
title = TextReference.EMPTY,
@ -98,7 +85,6 @@ internal class StakingStateController @Inject constructor(
rewardsValidatorsState = StakingStates.RewardsValidatorsState.Empty(),
confirmationState = StakingStates.ConfirmationState.Empty(),
isBalanceHidden = false,
event = consumedEvent(),
bottomSheetConfig = null,
actionType = StakingActionCommonType.Enter(skipEnterAmount = false),
buttonsState = NavigationButtonsState.Empty,

View file

@ -6,14 +6,12 @@ import com.tangem.common.ui.navigationButtons.NavigationButtonsState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.models.staking.PendingAction
import com.tangem.domain.staking.model.StakingTarget
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
import com.tangem.features.staking.impl.presentation.state.events.StakingEvent
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
import kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal
@ -40,7 +38,6 @@ internal data class StakingUiState(
val bottomSheetConfig: TangemBottomSheetConfig?,
val actionType: StakingActionCommonType,
val buttonsState: NavigationButtonsState,
val event: StateEvent<StakingEvent>,
val balanceState: BalanceState?,
val showColdWalletInteractionIcon: Boolean,
val shouldShowHoldToConfirmButton: Boolean,

View file

@ -1,85 +1,74 @@
package com.tangem.features.staking.impl.presentation.state.events
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.alerts.models.AlertUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.staking.impl.R
@Immutable
internal sealed class StakingAlertUM : AlertUM {
internal object StakingAlertUM {
data class GenericError(
override val onConfirmClick: () -> Unit,
) : StakingAlertUM() {
override val title: TextReference = resourceReference(R.string.common_error)
override val message: TextReference = resourceReference(R.string.common_unknown_error)
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_support)
}
fun genericError(onConfirmClick: () -> Unit): DialogMessage = DialogMessage(
title = resourceReference(R.string.common_error),
message = resourceReference(R.string.common_unknown_error),
firstAction = EventMessageAction(
title = resourceReference(id = R.string.common_support),
onClick = onConfirmClick,
),
)
data class StakingError(
val code: String,
override val onConfirmClick: () -> Unit,
) : StakingAlertUM() {
override val title: TextReference = resourceReference(R.string.common_error)
override val message: TextReference = resourceReference(R.string.generic_error_code, wrappedList(code))
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_support)
}
fun stakingError(code: String, onConfirmClick: () -> Unit): DialogMessage = DialogMessage(
title = resourceReference(R.string.common_error),
message = resourceReference(R.string.generic_error_code, wrappedList(code)),
firstAction = EventMessageAction(
title = resourceReference(id = R.string.common_support),
onClick = onConfirmClick,
),
)
data object NoAvailableValidators : StakingAlertUM() {
override val title = resourceReference(R.string.common_error)
override val message = resourceReference(R.string.staking_no_validators_error_message)
override val confirmButtonText = resourceReference(R.string.common_ok)
override val onConfirmClick = null
}
fun noAvailableValidators(): DialogMessage = DialogMessage(
title = resourceReference(R.string.common_error),
message = resourceReference(R.string.staking_no_validators_error_message),
)
data class FeeIncreased(
override val onConfirmClick: () -> Unit,
) : StakingAlertUM() {
override val title: TextReference? = null
override val message: TextReference = resourceReference(id = R.string.send_notification_high_fee_title)
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
}
fun feeIncreased(onConfirmClick: () -> Unit): DialogMessage = DialogMessage(
title = null,
message = resourceReference(id = R.string.send_notification_high_fee_title),
firstAction = EventMessageAction(
title = resourceReference(id = R.string.common_ok),
onClick = onConfirmClick,
),
)
data object ValidatorsUnavailable : StakingAlertUM() {
override val onConfirmClick: (() -> Unit)? = null
override val title: TextReference = resourceReference(id = R.string.staking_error_no_validators_title)
override val message: TextReference = resourceReference(id = R.string.staking_error_no_validators_message)
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
}
fun validatorsUnavailable(): DialogMessage = DialogMessage(
title = resourceReference(id = R.string.staking_error_no_validators_title),
message = resourceReference(id = R.string.staking_error_no_validators_message),
)
data class StakeMoreClickUnavailable(
val cryptoCurrency: CryptoCurrency,
) : StakingAlertUM() {
override val onConfirmClick: (() -> Unit)? = null
override val title: TextReference? = null
override val message: TextReference = resourceReference(
fun stakeMoreClickUnavailable(cryptoCurrency: CryptoCurrency): DialogMessage = DialogMessage(
title = null,
message = resourceReference(
id = R.string.staking_stake_more_button_unavailability_reason,
wrappedList(cryptoCurrency.name, cryptoCurrency.symbol),
)
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
}
),
)
data class RewardsMinimumRequirementsError(
val cryptoCurrencyName: String,
val cryptoAmountValue: String,
) : StakingAlertUM() {
override val onConfirmClick: (() -> Unit)? = null
override val title: TextReference? = null
override val message: TextReference = resourceReference(
id = R.string.staking_details_min_rewards_notification,
formatArgs = wrappedList(cryptoCurrencyName, cryptoAmountValue),
fun rewardsMinimumRequirementsError(cryptoCurrencyName: String, cryptoAmountValue: String): DialogMessage =
DialogMessage(
title = null,
message = resourceReference(
id = R.string.staking_details_min_rewards_notification,
formatArgs = wrappedList(cryptoCurrencyName, cryptoAmountValue),
),
)
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
}
data class NetworkFeeUpdated(
override val onConfirmClick: () -> Unit,
) : StakingAlertUM() {
override val title: TextReference = resourceReference(R.string.staking_alert_network_fee_updated_title)
override val message: TextReference = resourceReference(R.string.staking_alert_network_fee_updated_message)
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
}
fun networkFeeUpdated(onConfirmClick: () -> Unit): DialogMessage = DialogMessage(
title = resourceReference(R.string.staking_alert_network_fee_updated_title),
message = resourceReference(R.string.staking_alert_network_fee_updated_message),
firstAction = EventMessageAction(
title = resourceReference(id = R.string.common_ok),
onClick = onConfirmClick,
),
)
}

View file

@ -1,13 +0,0 @@
package com.tangem.features.staking.impl.presentation.state.events
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.alerts.models.AlertUM
import com.tangem.core.ui.extensions.TextReference
@Immutable
internal sealed class StakingEvent {
data class ShowSnackBar(val text: TextReference) : StakingEvent()
data class ShowAlert(val alert: AlertUM) : StakingEvent()
}

View file

@ -1,69 +1,63 @@
package com.tangem.features.staking.impl.presentation.state.events
import com.tangem.common.ui.alerts.TransactionErrorAlertConverter
import com.tangem.common.ui.alerts.TransactionErrorDialogFactory
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.features.staking.impl.presentation.state.StakingStateController
internal class StakingEventFactory(
private val stateController: StakingStateController,
private val messageSender: UiMessageSender,
private val popBackStack: () -> Unit,
private val onFailedTxEmailClick: (String) -> Unit,
private val transactionErrorDialogFactory: TransactionErrorDialogFactory = TransactionErrorDialogFactory(),
) {
fun createGenericErrorAlert(error: String) {
val alert = StakingEvent.ShowAlert(
StakingAlertUM.GenericError(
messageSender.send(
StakingAlertUM.genericError(
onConfirmClick = { onFailedTxEmailClick(error) },
),
)
stateController.updateEvent(alert)
}
fun createSendTransactionErrorAlert(error: SendTransactionError?) {
val alert = error?.let {
TransactionErrorAlertConverter(
transactionErrorDialogFactory.create(
error = error,
popBackStack = popBackStack,
onFailedTxEmailClick = onFailedTxEmailClick,
).convert(error)
}?.let {
StakingEvent.ShowAlert(it)
)
}
stateController.updateEvent(alert)
alert?.let { messageSender.send(it) }
}
fun createStakingErrorAlert(error: StakingError) {
val alert = StakingEvent.ShowAlert(
StakingAlertUM.StakingError(
messageSender.send(
StakingAlertUM.stakingError(
code = error.toString(),
onConfirmClick = { onFailedTxEmailClick(error.toString()) },
),
)
stateController.updateEvent(alert)
}
fun createStakingValidatorsUnavailableAlert() {
val alert = StakingEvent.ShowAlert(alert = StakingAlertUM.ValidatorsUnavailable)
stateController.updateEvent(alert)
messageSender.send(StakingAlertUM.validatorsUnavailable())
}
fun createStakingRewardsMinimumRequirementsErrorAlert(cryptoCurrencyName: String, cryptoAmountValue: String) {
stateController.updateEvent(
StakingEvent.ShowAlert(
alert = StakingAlertUM.RewardsMinimumRequirementsError(
cryptoCurrencyName = cryptoCurrencyName,
cryptoAmountValue = cryptoAmountValue,
),
messageSender.send(
StakingAlertUM.rewardsMinimumRequirementsError(
cryptoCurrencyName = cryptoCurrencyName,
cryptoAmountValue = cryptoAmountValue,
),
)
}
fun createNetworkFeeUpdatedAlert(onConfirm: () -> Unit) {
val alert = StakingEvent.ShowAlert(
alert = StakingAlertUM.NetworkFeeUpdated(
messageSender.send(
StakingAlertUM.networkFeeUpdated(
onConfirmClick = onConfirm,
),
)
stateController.updateEvent(alert)
}
}

View file

@ -1,80 +0,0 @@
package com.tangem.features.staking.impl.presentation.ui
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
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.stringResourceSafe
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.events.StakingEvent
@Composable
internal fun StakingEventEffect(event: StateEvent<StakingEvent>, snackbarHostState: SnackbarHostState) {
val resources = LocalContext.current.resources
var alertConfig by remember { mutableStateOf<AlertUM?>(value = null) }
val keyboardController = LocalSoftwareKeyboardController.current
LaunchedEffect(key1 = alertConfig) {
keyboardController?.hide()
}
alertConfig?.let {
StakingAlert(state = it, onDismiss = { alertConfig = null })
}
EventEffect(
event = event,
onTrigger = { value ->
when (value) {
is StakingEvent.ShowSnackBar -> {
snackbarHostState.showSnackbar(message = value.text.resolveReference(resources))
}
is StakingEvent.ShowAlert -> {
alertConfig = value.alert
}
}
},
)
}
@Composable
internal fun StakingAlert(state: AlertUM, onDismiss: () -> Unit) {
val confirmButton: DialogButtonUM
val dismissButton: DialogButtonUM?
val onActionClick = state.onConfirmClick
if (onActionClick != null) {
confirmButton = DialogButtonUM(
title = state.confirmButtonText.resolveReference(),
onClick = {
onActionClick()
onDismiss()
},
)
dismissButton = DialogButtonUM(
title = stringResourceSafe(id = R.string.common_cancel),
onClick = onDismiss,
)
} else {
confirmButton = DialogButtonUM(
title = state.confirmButtonText.resolveReference(),
onClick = onDismiss,
)
dismissButton = null
}
BasicDialog(
message = state.message.resolveReference(),
confirmButton = confirmButton,
onDismissDialog = onDismiss,
title = state.title?.resolveReference(),
dismissButton = dismissButton,
)
}

View file

@ -7,7 +7,6 @@ import androidx.compose.animation.core.tween
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -39,7 +38,6 @@ import kotlinx.coroutines.flow.withIndex
@Composable
internal fun StakingScreen(uiState: StakingUiState) {
val snackbarHostState = remember { SnackbarHostState() }
val confirmationState = uiState.confirmationState as? StakingStates.ConfirmationState.Data
BackHandler(onBack = uiState.clickIntents::onPrevClick)
@ -71,11 +69,6 @@ internal fun StakingScreen(uiState: StakingUiState) {
)
StakingBottomSheet(bottomSheetConfig = uiState.bottomSheetConfig)
}
StakingEventEffect(
event = uiState.event,
snackbarHostState = snackbarHostState,
)
}
@Composable

View file

@ -1,7 +1,6 @@
package com.tangem.features.swap.v2.impl.common
import com.tangem.common.ui.alerts.TransactionErrorAlertConverter
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
import com.tangem.common.ui.alerts.TransactionErrorDialogFactory
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.resourceReference
@ -25,6 +24,7 @@ internal class SwapAlertFactory @Inject constructor(
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val transactionErrorDialogFactory: TransactionErrorDialogFactory,
) {
fun getGenericErrorState(expressError: ExpressError, onFailedTxEmailClick: () -> Unit, popBack: () -> Unit = {}) {
uiMessageSender.send(
@ -44,36 +44,21 @@ internal class SwapAlertFactory @Inject constructor(
)
}
@Suppress("CanBeNonNullable")
fun getSendTransactionErrorState(
error: SendTransactionError?,
popBack: () -> Unit,
onFailedTxEmailClick: (String) -> Unit,
) {
val transactionErrorAlertConverter = TransactionErrorAlertConverter(
if (error == null) return
val errorDialog = transactionErrorDialogFactory.create(
error = error,
popBackStack = popBack,
onFailedTxEmailClick = onFailedTxEmailClick,
)
) ?: return
val errorAlert = error?.let { transactionErrorAlertConverter.convert(error) } ?: return
val onConfirmClick = errorAlert.onConfirmClick ?: return
uiMessageSender.send(
DialogMessage.Companion(
title = errorAlert.title,
message = errorAlert.message,
firstActionBuilder = {
EventMessageAction(
title = errorAlert.confirmButtonText,
onClick = onConfirmClick,
)
},
secondActionBuilder = if (errorAlert !is AlertDemoModeUM) {
{ cancelAction() }
} else {
null
},
),
)
uiMessageSender.send(errorDialog)
}
suspend fun onFailedTxEmailClick(

View file

@ -1,7 +1,7 @@
package com.tangem.feature.swap.converters
import com.tangem.common.ui.alerts.TransactionErrorAlertConverter
import com.tangem.common.ui.alerts.models.AlertUM
import com.tangem.common.ui.alerts.TransactionErrorDialogFactory
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.feature.swap.domain.models.ui.SwapTransactionState
import com.tangem.feature.swap.models.SwapAlertUM
@ -11,24 +11,25 @@ import com.tangem.utils.converter.Converter
internal class SwapTransactionErrorStateConverter(
private val onDismiss: () -> Unit,
private val onSupportClick: (String) -> Unit,
) : Converter<SwapTransactionState.Error, AlertUM?> {
override fun convert(value: SwapTransactionState.Error): AlertUM? {
private val transactionErrorDialogFactory: TransactionErrorDialogFactory = TransactionErrorDialogFactory(),
) : Converter<SwapTransactionState.Error, DialogMessage?> {
override fun convert(value: SwapTransactionState.Error): DialogMessage? {
return when (value) {
is SwapTransactionState.Error.TransactionError -> {
when (val error = value.error) {
is SendTransactionError.UserCancelledError -> return null
null -> SwapAlertUM.GenericError(onDismiss)
else -> TransactionErrorAlertConverter(onDismiss, onSupportClick).convert(error)
null -> SwapAlertUM.genericError(onDismiss)
else -> transactionErrorDialogFactory.create(error, onDismiss, onSupportClick)
}
}
is SwapTransactionState.Error.ExpressError -> {
SwapAlertUM.ExpressErrorAlert(
SwapAlertUM.expressErrorAlert(
message = getExpressErrorMessage(value.error),
onConfirmClick = { onSupportClick(value.error.code.toString()) },
)
}
SwapTransactionState.Error.UnknownError -> SwapAlertUM.GenericError(onDismiss)
is SwapTransactionState.Error.TangemPayWithdrawalError -> SwapAlertUM.GenericError(
SwapTransactionState.Error.UnknownError -> SwapAlertUM.genericError(onDismiss)
is SwapTransactionState.Error.TangemPayWithdrawalError -> SwapAlertUM.genericError(
onConfirmClick = { onSupportClick(value.txId) },
)
}

View file

@ -23,11 +23,21 @@ import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.toWrappedList
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.utils.InputNumberFormatter
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter
import com.tangem.feature.swap.models.SwapAlertUM
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
@ -165,6 +175,7 @@ internal class SwapModel @Inject constructor(
private val getTangemPayCustomerIdUseCase: GetTangemPayCustomerIdUseCase,
private val appsFlyerStore: AppsFlyerStore,
private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles,
private val messageSender: UiMessageSender,
) : Model() {
private val params = paramsContainer.require<SwapComponent.Params>()
@ -349,7 +360,8 @@ internal class SwapModel @Inject constructor(
}
if (fromAccountStatus == null) {
uiState = stateBuilder.addAlert(uiState = uiState, onDismiss = swapRouter::back)
showAlert()
swapRouter.back()
} else {
fromAccountCurrencyStatus = fromAccountStatus
toAccountCurrencyStatus = toAccountStatus
@ -367,7 +379,8 @@ internal class SwapModel @Inject constructor(
}
if (fromStatus == null) {
uiState = stateBuilder.addAlert(uiState = uiState, onDismiss = swapRouter::back)
showAlert()
swapRouter.back()
} else {
initialFromStatus = fromStatus
initialToStatus = toStatus
@ -1032,7 +1045,7 @@ internal class SwapModel @Inject constructor(
val fee = getSelectedFee()
if (fee == null && tangemPayInput?.isWithdrawal != true) {
makeDefaultAlert(resourceReference(R.string.swapping_fee_estimation_error_text))
showAlert(resourceReference(R.string.swapping_fee_estimation_error_text))
modelScope.launch {
delay(SWAP_IN_PROGRESS_DELAY)
startLoadingQuotesFromLastState()
@ -1058,7 +1071,7 @@ internal class SwapModel @Inject constructor(
when (swapTransactionState) {
is SwapTransactionState.TxSent -> {
if (fee == null) {
makeDefaultAlert(resourceReference(R.string.swapping_fee_estimation_error_text))
showAlert(resourceReference(R.string.swapping_fee_estimation_error_text))
return@onSuccess
}
sendSuccessSwapEvent(
@ -1102,21 +1115,11 @@ internal class SwapModel @Inject constructor(
swapRouter.openScreen(SwapNavScreen.Success)
}
SwapTransactionState.DemoMode -> {
uiState = stateBuilder.createDemoModeAlert(
uiState = uiState,
onDismiss = { uiState = stateBuilder.clearAlert(uiState) },
isReverseSwapPossible = isReverseSwapPossible(),
)
showDemoModeAlert()
}
is SwapTransactionState.Error -> {
startLoadingQuotesFromLastState()
uiState = stateBuilder.createErrorTransactionAlert(
uiState = uiState,
error = swapTransactionState,
onDismiss = { uiState = stateBuilder.clearAlert(uiState) },
onSupportClick = ::onFailedTxEmailClick,
isReverseSwapPossible = isReverseSwapPossible(),
)
showTransactionErrorAlert(swapTransactionState)
}
is SwapTransactionState.TangemPayWithdrawalData -> {
processTangemPayWithdrawal(swapTransactionState = swapTransactionState)
@ -1125,7 +1128,7 @@ internal class SwapModel @Inject constructor(
}.onFailure { error ->
Timber.e(error)
startLoadingQuotesFromLastState()
makeDefaultAlert()
showAlert()
}
}
}
@ -1217,7 +1220,7 @@ internal class SwapModel @Inject constructor(
}
val feeForPermission = when (val fee = approveDataModel.fee) {
TxFeeState.Empty -> {
makeDefaultAlert(resourceReference(R.string.swapping_fee_estimation_error_text))
showAlert(resourceReference(R.string.swapping_fee_estimation_error_text))
Timber.e("Fee should not be Empty")
return@launch
}
@ -1247,29 +1250,19 @@ internal class SwapModel @Inject constructor(
startLoadingQuotesFromLastState(isSilent = true)
}
is SwapTransactionState.Error -> {
uiState = stateBuilder.createErrorTransactionAlert(
uiState = uiState,
error = swapTransactionState,
onDismiss = { uiState = stateBuilder.clearAlert(uiState) },
onSupportClick = ::onFailedTxEmailClick,
isReverseSwapPossible = isReverseSwapPossible(),
)
showTransactionErrorAlert(swapTransactionState)
}
SwapTransactionState.DemoMode -> {
uiState = stateBuilder.createDemoModeAlert(
uiState = uiState,
onDismiss = { uiState = stateBuilder.clearAlert(uiState) },
isReverseSwapPossible = isReverseSwapPossible(),
)
showDemoModeAlert()
}
is SwapTransactionState.TangemPayWithdrawalData -> {
processTangemPayWithdrawal(swapTransactionState = swapTransactionState)
}
}
}.onFailure { makeDefaultAlert() }
}.onFailure { showAlert() }
}.onFailure { error ->
Timber.e(error.message.orEmpty())
makeDefaultAlert()
showAlert()
}
}
}
@ -1652,12 +1645,94 @@ internal class SwapModel @Inject constructor(
return inputNumberFormatter.getValidatedNumberWithFixedDecimals(amount, maxDecimals)
}
private fun makeDefaultAlert() {
uiState = stateBuilder.addAlert(uiState)
private fun showAlert(message: TextReference = resourceReference(R.string.common_unknown_error)) {
messageSender.send(SwapAlertUM.genericError(onConfirmClick = { }, message = message))
}
private fun makeDefaultAlert(message: TextReference) {
uiState = stateBuilder.addAlert(uiState, message)
private fun showDemoModeAlert() {
messageSender.send(
DialogMessage(
title = resourceReference(id = R.string.warning_demo_mode_title),
message = resourceReference(id = R.string.warning_demo_mode_message),
firstAction = EventMessageAction(
title = resourceReference(id = R.string.common_ok),
onClick = {},
),
),
)
}
private fun showTransactionErrorAlert(
error: SwapTransactionState.Error,
onSupportClick: (String) -> Unit = ::onFailedTxEmailClick,
) {
val errorAlert = SwapTransactionErrorStateConverter(
onDismiss = {},
onSupportClick = onSupportClick,
).convert(error)
errorAlert?.let { messageSender.send(it) }
}
private fun onTangemPaySupportClick(txId: String) {
modelScope.launch {
val metaInfo = getWalletMetaInfoUseCase(userWallet.walletId).getOrNull() ?: return@launch
val customerId = getTangemPayCustomerIdUseCase(userWallet.walletId).getOrNull().orEmpty()
val email = FeedbackEmailType.Visa.Withdrawal(
walletMetaInfo = metaInfo,
customerId = customerId,
providerName = dataState.selectedProvider?.name.orEmpty(),
txId = txId,
)
sendFeedbackEmailUseCase(email)
}
}
private fun showSwapInfoAlert(isPriceImpact: Boolean, token: String, provider: SwapProvider) {
messageSender.send(
SwapAlertUM.informationAlert(
message = buildSwapInfoMessage(isPriceImpact, token, provider),
onConfirmClick = {},
),
)
}
private fun buildSwapInfoMessage(isPriceImpact: Boolean, token: String, provider: SwapProvider): TextReference {
val slippage = provider.slippage?.let { "${it.parseBigDecimal(1)}%" }
val messages = buildList {
when (provider.type) {
ExchangeProviderType.CEX -> {
if (slippage != null) {
add(
resourceReference(
id = R.string.swapping_alert_cex_description_with_slippage,
formatArgs = wrappedList(token, slippage),
),
)
} else {
add(resourceReference(R.string.swapping_alert_cex_description, wrappedList(token)))
}
}
ExchangeProviderType.DEX,
ExchangeProviderType.DEX_BRIDGE,
-> {
if (isPriceImpact) {
add(resourceReference(R.string.swapping_high_price_impact_description))
add(stringReference("\n\n"))
}
if (slippage != null) {
add(
resourceReference(
id = R.string.swapping_alert_dex_description_with_slippage,
formatArgs = wrappedList(slippage),
),
)
} else {
add(resourceReference(R.string.swapping_alert_dex_description, wrappedList(token)))
}
}
}
}
return combinedReference(messages.toWrappedList())
}
@Suppress("LongMethod", "CyclomaticComplexMethod")
@ -1777,14 +1852,7 @@ internal class SwapModel @Inject constructor(
val selectedProvider = dataState.selectedProvider ?: return@UiActions
val currencySymbol = dataState.toCryptoCurrency?.currency?.symbol ?: return@UiActions
val isPriceImpact = uiState.priceImpact is PriceImpact.Value
uiState = stateBuilder.createAlert(
uiState = uiState,
isPriceImpact = isPriceImpact,
token = currencySymbol,
provider = selectedProvider,
isReverseSwapPossible = isReverseSwapPossible(),
onDismiss = { uiState = stateBuilder.clearAlert(uiState) },
)
showSwapInfoAlert(isPriceImpact, currencySymbol, selectedProvider)
},
onLinkClick = urlOpener::openUrl,
onSelectTokenClick = {
@ -2090,31 +2158,12 @@ internal class SwapModel @Inject constructor(
}
private fun onTangemPayWithdrawalError(txId: String?) {
uiState = stateBuilder.createErrorTransactionAlert(
uiState = uiState,
showTransactionErrorAlert(
error = SwapTransactionState.Error.TangemPayWithdrawalError(txId.orEmpty()),
onDismiss = { uiState = stateBuilder.clearAlert(uiState) },
onSupportClick = {
val customerId = getTangemPayCustomerIdUseCase(userWallet.walletId).getOrNull() ?: "Unknown"
onTangemPaySupportClick(customerId = customerId, txId = txId)
},
isReverseSwapPossible = isReverseSwapPossible(),
onSupportClick = ::onTangemPaySupportClick,
)
}
private fun onTangemPaySupportClick(customerId: String, txId: String?) {
modelScope.launch {
val metaInfo = getWalletMetaInfoUseCase(userWallet.walletId).getOrNull() ?: return@launch
val email = FeedbackEmailType.Visa.Withdrawal(
walletMetaInfo = metaInfo,
customerId = customerId,
providerName = dataState.selectedProvider?.name.orEmpty(),
txId = txId.orEmpty(),
)
sendFeedbackEmailUseCase(email)
}
}
private fun onFailedTxEmailClick(errorMessage: String) {
modelScope.launch {
val transaction = dataState.swapDataModel?.transaction

View file

@ -1,38 +1,43 @@
package com.tangem.feature.swap.models
import com.tangem.common.ui.alerts.models.AlertUM
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
sealed class SwapAlertUM : AlertUM {
internal object SwapAlertUM {
data class GenericError(
override val onConfirmClick: (() -> Unit),
override val message: TextReference = resourceReference(R.string.common_unknown_error),
) : SwapAlertUM() {
override val title: TextReference? = null
override val confirmButtonText: TextReference =
resourceReference(id = R.string.common_support)
}
fun genericError(
onConfirmClick: () -> Unit,
message: TextReference = resourceReference(R.string.common_unknown_error),
): DialogMessage = DialogMessage(
title = null,
message = message,
firstAction = EventMessageAction(
title = resourceReference(id = R.string.common_support),
onClick = onConfirmClick,
),
)
data class ExpressErrorAlert(
override val message: TextReference = resourceReference(R.string.common_unknown_error),
override val onConfirmClick: (() -> Unit),
) : SwapAlertUM() {
override val title: TextReference? = null
override val confirmButtonText: TextReference =
resourceReference(id = R.string.common_support)
}
fun expressErrorAlert(
message: TextReference = resourceReference(R.string.common_unknown_error),
onConfirmClick: () -> Unit,
): DialogMessage = DialogMessage(
title = null,
message = message,
firstAction = EventMessageAction(
title = resourceReference(id = R.string.common_support),
onClick = onConfirmClick,
),
)
data class InformationAlert(
override val message: TextReference,
override val onConfirmClick: (() -> Unit),
) : SwapAlertUM() {
override val title: TextReference = resourceReference(
R.string.swapping_alert_title,
)
override val confirmButtonText: TextReference =
resourceReference(id = R.string.common_ok)
}
fun informationAlert(message: TextReference, onConfirmClick: () -> Unit): DialogMessage = DialogMessage(
title = resourceReference(R.string.swapping_alert_title),
message = message,
firstAction = EventMessageAction(
title = resourceReference(id = R.string.common_ok),
onClick = onConfirmClick,
),
)
}

View file

@ -7,14 +7,11 @@ import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.common.ui.swapStoriesScreen.SwapStoriesUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.currency.CryptoCurrencyStatus
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
@ -24,7 +21,6 @@ internal data class SwapStateHolder(
val blockchainId: String, // not the same as networkId, its local id in app
val notifications: ImmutableList<NotificationUM> = persistentListOf(),
val isInsufficientFunds: Boolean,
val event: StateEvent<SwapEvent> = consumedEvent(),
val changeCardsButtonState: ChangeCardsButtonState,
val providerState: ProviderState,

View file

@ -1,9 +0,0 @@
package com.tangem.feature.swap.models.states.events
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.alerts.models.AlertUM
@Immutable
internal sealed class SwapEvent {
data class ShowAlert(val alert: AlertUM) : SwapEvent()
}

View file

@ -5,21 +5,17 @@ import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.common.ui.account.CryptoPortfolioIconConverter
import com.tangem.common.ui.account.toUM
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.common.ui.swapStoriesScreen.SwapStoriesFactory
import com.tangem.common.ui.userwallet.ext.walletInterationIcon
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
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.anyDecimals
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
@ -29,7 +25,6 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isHotWallet
import com.tangem.domain.promo.models.StoryContent
import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork
import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter
import com.tangem.feature.swap.converters.TokensDataConverter
import com.tangem.feature.swap.converters.TokensDataConverterV2
import com.tangem.feature.swap.domain.models.ExpressDataError
@ -43,12 +38,10 @@ import com.tangem.feature.swap.model.SwapNotificationsFactory
import com.tangem.feature.swap.model.SwapProcessDataState
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.utils.formatToUIRepresentation
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
@ -972,117 +965,6 @@ internal class StateBuilder(
)
}
fun createErrorTransactionAlert(
uiState: SwapStateHolder,
error: SwapTransactionState.Error,
onDismiss: () -> Unit,
onSupportClick: (String) -> Unit,
isReverseSwapPossible: Boolean,
): SwapStateHolder {
val errorAlert = SwapTransactionErrorStateConverter(
onSupportClick = onSupportClick,
onDismiss = onDismiss,
).convert(error)
return uiState.copy(
event = errorAlert?.let {
triggeredEvent(
data = SwapEvent.ShowAlert(errorAlert),
onConsume = onDismiss,
)
} ?: consumedEvent(),
changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible),
)
}
fun createDemoModeAlert(
uiState: SwapStateHolder,
onDismiss: () -> Unit,
isReverseSwapPossible: Boolean,
): SwapStateHolder {
return uiState.copy(
event = triggeredEvent(
data = SwapEvent.ShowAlert(AlertDemoModeUM(onDismiss)),
onConsume = onDismiss,
),
changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible),
)
}
@Suppress("LongParameterList")
fun createAlert(
uiState: SwapStateHolder,
isPriceImpact: Boolean,
token: String,
provider: SwapProvider,
onDismiss: () -> Unit,
isReverseSwapPossible: Boolean,
): SwapStateHolder {
val slippage = provider.slippage?.let { "${it.parseBigDecimal(1)}$PERCENT" }
val combinedMessage = buildList {
when (provider.type) {
ExchangeProviderType.CEX -> {
if (slippage != null) {
add(
resourceReference(
id = R.string.swapping_alert_cex_description_with_slippage,
formatArgs = wrappedList(token, slippage),
),
)
} else {
add(resourceReference(R.string.swapping_alert_cex_description, wrappedList(token)))
}
}
ExchangeProviderType.DEX,
ExchangeProviderType.DEX_BRIDGE,
-> {
if (isPriceImpact) {
add(resourceReference(R.string.swapping_high_price_impact_description))
add(stringReference("\n\n"))
}
if (slippage != null) {
add(
resourceReference(
id = R.string.swapping_alert_dex_description_with_slippage,
formatArgs = wrappedList(slippage),
),
)
} else {
add(resourceReference(R.string.swapping_alert_dex_description, wrappedList(token)))
}
}
}
}
return uiState.copy(
event = triggeredEvent(
SwapEvent.ShowAlert(
SwapAlertUM.InformationAlert(
message = combinedReference(combinedMessage.toWrappedList()),
onConfirmClick = onDismiss,
),
),
onConsume = onDismiss,
),
changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible),
)
}
fun addAlert(
uiState: SwapStateHolder,
message: TextReference = resourceReference(R.string.common_unknown_error),
onDismiss: () -> Unit = { clearAlert(uiState) },
): SwapStateHolder {
return uiState.copy(
event = triggeredEvent(
SwapEvent.ShowAlert(
SwapAlertUM.GenericError(onDismiss, message),
),
onConsume = onDismiss,
),
)
}
fun clearAlert(uiState: SwapStateHolder): SwapStateHolder = uiState.copy(event = consumedEvent())
fun addNotification(uiState: SwapStateHolder, message: TextReference?, onClick: () -> Unit): SwapStateHolder {
return uiState.copy(
notifications = notificationsFactory.getGeneralErrorStateNotifications(

View file

@ -1,61 +0,0 @@
package com.tangem.feature.swap.ui
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
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.stringResourceSafe
import com.tangem.feature.swap.models.states.events.SwapEvent
import com.tangem.feature.swap.presentation.R
@Composable
internal fun SwapEventEffect(event: StateEvent<SwapEvent>) {
var alertConfig by remember { mutableStateOf<AlertUM?>(value = null) }
val keyboardController = LocalSoftwareKeyboardController.current
LaunchedEffect(key1 = alertConfig) {
keyboardController?.hide()
}
alertConfig?.let {
SwapAlert(state = it, onDismiss = { alertConfig = null })
}
EventEffect(
event = event,
onTrigger = { value ->
when (value) {
is SwapEvent.ShowAlert -> {
alertConfig = value.alert
}
}
},
)
}
@Composable
internal fun SwapAlert(state: AlertUM, onDismiss: () -> Unit) {
val confirmButton = DialogButtonUM(
title = state.confirmButtonText.resolveReference(),
onClick = {
state.onConfirmClick?.invoke()
onDismiss()
},
)
val dismissButton = DialogButtonUM(
title = stringResourceSafe(id = R.string.common_cancel),
onClick = onDismiss,
)
BasicDialog(
message = state.message.resolveReference(),
confirmButton = confirmButton,
onDismissDialog = onDismiss,
title = state.title?.resolveReference(),
dismissButton = dismissButton,
)
}

View file

@ -117,10 +117,6 @@ internal fun SwapScreenContent(
textAlign = TextAlign.Start,
)
}
SwapEventEffect(
event = state.event,
)
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.features.yield.supply.impl.common
import com.tangem.common.ui.alerts.TransactionErrorAlertConverter
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
import com.tangem.common.ui.alerts.TransactionErrorDialogFactory
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.resourceReference
@ -24,6 +23,7 @@ class YieldSupplyAlertFactory @Inject constructor(
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val transactionErrorDialogFactory: TransactionErrorDialogFactory,
) {
fun getGenericErrorState(onFailedTxEmailClick: () -> Unit, popBack: () -> Unit = {}) {
@ -41,35 +41,17 @@ class YieldSupplyAlertFactory @Inject constructor(
}
fun getSendTransactionErrorState(
error: SendTransactionError?,
error: SendTransactionError,
popBack: () -> Unit,
onFailedTxEmailClick: (String) -> Unit,
) {
val transactionErrorAlertConverter = TransactionErrorAlertConverter(
val errorDialog = transactionErrorDialogFactory.create(
error = error,
popBackStack = popBack,
onFailedTxEmailClick = onFailedTxEmailClick,
)
) ?: return
val errorAlert = error?.let { transactionErrorAlertConverter.convert(error) } ?: return
val onConfirmClick = errorAlert.onConfirmClick ?: return
uiMessageSender.send(
DialogMessage.Companion(
title = errorAlert.title,
message = errorAlert.message,
firstActionBuilder = {
EventMessageAction(
title = errorAlert.confirmButtonText,
onClick = onConfirmClick,
)
},
secondActionBuilder = if (errorAlert !is AlertDemoModeUM) {
{ cancelAction() }
} else {
null
},
),
)
uiMessageSender.send(errorDialog)
}
suspend fun onFailedTxEmailClick(userWallet: UserWallet, cryptoCurrency: CryptoCurrency?, errorMessage: String?) {