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

@ -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