Updated on 2026-08-14
This commit is contained in:
parent
faa58cf142
commit
ea8c9079b7
24 changed files with 311 additions and 69 deletions
|
|
@ -88,6 +88,7 @@ internal sealed class StakingStates {
|
|||
val footerText: String,
|
||||
val transactionDoneState: TransactionDoneState,
|
||||
val pendingActionInProgress: PendingAction? = null,
|
||||
val isApprovalNeeded: Boolean,
|
||||
) : ConfirmationState()
|
||||
|
||||
data class Empty(
|
||||
|
|
|
|||
|
|
@ -86,5 +86,6 @@ internal object ConfirmationStatePreviewData {
|
|||
),
|
||||
transactionDoneState = TransactionDoneState.Empty,
|
||||
pendingActions = persistentListOf(),
|
||||
isApprovalNeeded = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -38,6 +38,10 @@ object StakingClickIntentsStub : StakingClickIntents {
|
|||
|
||||
override fun openRewardsValidators() {}
|
||||
|
||||
override fun showApprovalBottomSheet() {}
|
||||
|
||||
override fun onApprovalClick() {}
|
||||
|
||||
override fun onExploreClick() {}
|
||||
|
||||
override fun onShareClick() {}
|
||||
|
|
|
|||
|
|
@ -135,23 +135,7 @@ internal class SetButtonsStateTransformer : Transformer<StakingUiState> {
|
|||
}
|
||||
}
|
||||
|
||||
StakingStep.Confirmation -> {
|
||||
if (confirmationState is StakingStates.ConfirmationState.Data) {
|
||||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
|
||||
resourceReference(R.string.common_close)
|
||||
} else {
|
||||
when (actionType) {
|
||||
StakingActionCommonType.ENTER -> resourceReference(R.string.common_stake)
|
||||
StakingActionCommonType.EXIT -> resourceReference(R.string.common_unstake)
|
||||
StakingActionCommonType.PENDING_OTHER,
|
||||
StakingActionCommonType.PENDING_REWARDS,
|
||||
-> getPendingActionTitle(confirmationState.pendingActions.firstOrNull()?.type)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resourceReference(R.string.common_close)
|
||||
}
|
||||
}
|
||||
StakingStep.Confirmation -> getConfirmationButtonText()
|
||||
StakingStep.Validators -> resourceReference(R.string.common_continue)
|
||||
StakingStep.Amount,
|
||||
StakingStep.RewardsValidators,
|
||||
|
|
@ -159,6 +143,30 @@ internal class SetButtonsStateTransformer : Transformer<StakingUiState> {
|
|||
}
|
||||
}
|
||||
|
||||
private fun StakingUiState.getConfirmationButtonText(): TextReference {
|
||||
return if (confirmationState is StakingStates.ConfirmationState.Data) {
|
||||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
|
||||
resourceReference(R.string.common_close)
|
||||
} else {
|
||||
when (actionType) {
|
||||
StakingActionCommonType.ENTER -> {
|
||||
if (confirmationState.isApprovalNeeded) {
|
||||
resourceReference(R.string.give_permission_title)
|
||||
} else {
|
||||
resourceReference(R.string.common_stake)
|
||||
}
|
||||
}
|
||||
StakingActionCommonType.EXIT -> resourceReference(R.string.common_unstake)
|
||||
StakingActionCommonType.PENDING_OTHER,
|
||||
StakingActionCommonType.PENDING_REWARDS,
|
||||
-> getPendingActionTitle(confirmationState.pendingActions.firstOrNull()?.type)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resourceReference(R.string.common_close)
|
||||
}
|
||||
}
|
||||
|
||||
private fun StakingUiState.onPrimaryClick() {
|
||||
when (currentStep) {
|
||||
StakingStep.InitialInfo -> {
|
||||
|
|
@ -169,18 +177,27 @@ internal class SetButtonsStateTransformer : Transformer<StakingUiState> {
|
|||
StakingStep.Validators,
|
||||
StakingStep.Amount,
|
||||
-> clickIntents.onNextClick()
|
||||
StakingStep.Confirmation -> {
|
||||
if (confirmationState is StakingStates.ConfirmationState.Data) {
|
||||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
|
||||
clickIntents.onBackClick()
|
||||
} else {
|
||||
clickIntents.onActionClick(confirmationState.pendingActions.firstOrNull())
|
||||
}
|
||||
StakingStep.Confirmation -> onConfirmationClick()
|
||||
StakingStep.RewardsValidators -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun StakingUiState.onConfirmationClick() {
|
||||
if (confirmationState is StakingStates.ConfirmationState.Data) {
|
||||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
|
||||
clickIntents.onBackClick()
|
||||
} else {
|
||||
val isEnterAction = actionType == StakingActionCommonType.ENTER
|
||||
val isApproveNeeded = confirmationState.isApprovalNeeded
|
||||
|
||||
if (isEnterAction && isApproveNeeded) {
|
||||
clickIntents.showApprovalBottomSheet()
|
||||
} else {
|
||||
clickIntents.onBackClick()
|
||||
clickIntents.onActionClick(confirmationState.pendingActions.firstOrNull())
|
||||
}
|
||||
}
|
||||
StakingStep.RewardsValidators -> Unit
|
||||
} else {
|
||||
clickIntents.onBackClick()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.Provider
|
||||
|
|
@ -49,6 +48,7 @@ internal class SetConfirmationStateAssentTransformer(
|
|||
validatorState = validatorState.copySealed(isClickable = true),
|
||||
pendingActions = pendingActionList,
|
||||
isPrimaryButtonEnabled = true,
|
||||
isApprovalNeeded = false,
|
||||
)
|
||||
} else {
|
||||
return this
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ internal class SetConfirmationStateLoadingTransformer(
|
|||
innerState = InnerConfirmationStakingState.ASSENT,
|
||||
feeState = FeeState.Loading,
|
||||
validatorState = ValidatorState.Content(
|
||||
isClickable = false,
|
||||
isClickable = true,
|
||||
chosenValidator = chosenValidator,
|
||||
availableValidators = yield.validators,
|
||||
),
|
||||
|
|
@ -34,6 +34,7 @@ internal class SetConfirmationStateLoadingTransformer(
|
|||
transactionDoneState = TransactionDoneState.Empty,
|
||||
pendingActions = persistentListOf(),
|
||||
pendingActionInProgress = null,
|
||||
isApprovalNeeded = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.approval
|
||||
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SetApprovalBottomSheetInProgressTransformer(
|
||||
private val onDismiss: () -> Unit,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val approvalBottomSheetConfig = prevState.bottomSheetConfig?.content as? GiveTxPermissionBottomSheetConfig
|
||||
return prevState.copy(
|
||||
bottomSheetConfig = prevState.bottomSheetConfig?.copy(
|
||||
onDismissRequest = onDismiss,
|
||||
isShow = true,
|
||||
content = approvalBottomSheetConfig?.let { config ->
|
||||
config.copy(
|
||||
data = config.data.copy(
|
||||
approveButton = config.data.approveButton.copy(
|
||||
enabled = false,
|
||||
loading = true,
|
||||
),
|
||||
cancelButton = config.data.cancelButton.copy(
|
||||
enabled = false,
|
||||
),
|
||||
),
|
||||
onCancel = onDismiss,
|
||||
)
|
||||
} as TangemBottomSheetConfigContent,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.approval
|
||||
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.*
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.features.staking.impl.presentation.state.ValidatorState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class ShowApprovalBottomSheetTransformer(
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val onDismiss: () -> Unit,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val cryptoCurrency = cryptoCurrencyStatusProvider().currency
|
||||
val cryptoCurrencyValue = cryptoCurrencyStatusProvider().value
|
||||
|
||||
val amountState = prevState.amountState as? AmountState.Data ?: return prevState
|
||||
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
|
||||
val validatorState = confirmationState.validatorState as? ValidatorState.Content ?: return prevState
|
||||
val feeState = confirmationState.feeState as? FeeState.Content ?: return prevState
|
||||
|
||||
val walletAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value.orEmpty()
|
||||
val validatorAddress = validatorState.chosenValidator.address
|
||||
val feeCryptoValue = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = feeState.fee?.amount?.value,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
val feeFiatValue = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = feeState.fee?.amount?.maxValue,
|
||||
fiatCurrencyCode = appCurrencyProvider().code,
|
||||
fiatCurrencySymbol = appCurrencyProvider().symbol,
|
||||
)
|
||||
return prevState.copy(
|
||||
bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = onDismiss,
|
||||
content = GiveTxPermissionBottomSheetConfig(
|
||||
data = GiveTxPermissionState.ReadyForRequest(
|
||||
currency = cryptoCurrency.symbol,
|
||||
amount = amountState.amountTextField.value,
|
||||
approveType = ApproveType.LIMITED,
|
||||
walletAddress = walletAddress,
|
||||
spenderAddress = validatorAddress,
|
||||
fee = resourceReference(
|
||||
R.string.common_crypto_fiat_format,
|
||||
wrappedList(feeCryptoValue, feeFiatValue),
|
||||
),
|
||||
approveButton = ApprovePermissionButton(
|
||||
enabled = true,
|
||||
loading = false,
|
||||
onClick = prevState.clickIntents::onApprovalClick,
|
||||
),
|
||||
cancelButton = CancelPermissionButton(
|
||||
enabled = true,
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.give_permission_staking_subtitle,
|
||||
formatArgs = wrappedList(cryptoCurrency.symbol),
|
||||
),
|
||||
dialogText = resourceReference(R.string.give_permission_staking_footer),
|
||||
),
|
||||
onCancel = onDismiss,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenContent
|
||||
import com.tangem.common.ui.bottomsheet.permission.GiveTxPermissionBottomSheet
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButtonsBlock
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
|
|
@ -64,6 +66,7 @@ fun StakingBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
|
|||
if (bottomSheetConfig == null) return
|
||||
when (bottomSheetConfig.content) {
|
||||
is StakingInfoBottomSheetConfig -> StakingInfoBottomSheet(bottomSheetConfig)
|
||||
is GiveTxPermissionBottomSheetConfig -> GiveTxPermissionBottomSheet(bottomSheetConfig)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
|
|||
|
||||
fun onActiveStake(activeStake: BalanceState)
|
||||
|
||||
fun showApprovalBottomSheet()
|
||||
|
||||
fun onApprovalClick()
|
||||
|
||||
fun onExploreClick()
|
||||
|
||||
fun onShareClick()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import com.tangem.common.extensions.hexToBytes
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.bundle.unbundle
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.haptic.VibratorHapticManager
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
@ -36,9 +39,13 @@ import com.tangem.features.staking.impl.presentation.state.transformers.amount.A
|
|||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountCurrencyChangeStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountMaxValueStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountPasteDismissStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetApprovalBottomSheetInProgressTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.ShowApprovalBottomSheetTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.validator.ValidatorSelectChangeTransformer
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -64,14 +71,16 @@ internal class StakingViewModel @Inject constructor(
|
|||
private val submitHashUseCase: SubmitHashUseCase,
|
||||
private val isStakeMoreAvailableUseCase: IsStakeMoreAvailableUseCase,
|
||||
private val stakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase,
|
||||
private val isApproveNeededUseCase: IsApproveNeededUseCase,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
private val vibratorHapticManager: VibratorHapticManager,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
|
||||
|
||||
val uiState: StateFlow<StakingUiState> = stateController.uiState
|
||||
val value: StakingUiState get() = uiState.value
|
||||
|
||||
var stakingStateRouter: StakingStateRouter by Delegates.notNull()
|
||||
private set
|
||||
private var stakingStateRouter: StakingStateRouter by Delegates.notNull()
|
||||
|
||||
private val cryptoCurrencyId: CryptoCurrency.ID =
|
||||
savedStateHandle.get<Bundle>(AppRoute.Staking.CRYPTO_CURRENCY_ID_KEY)
|
||||
|
|
@ -92,6 +101,10 @@ internal class StakingViewModel @Inject constructor(
|
|||
private var userWallet: UserWallet by Delegates.notNull()
|
||||
private var appCurrency: AppCurrency by Delegates.notNull()
|
||||
|
||||
private var isApprovalNeeded: Boolean = false
|
||||
|
||||
private var approvalJobHolder: JobHolder = JobHolder()
|
||||
|
||||
init {
|
||||
subscribeOnSelectedAppCurrency()
|
||||
subscribeOnBalanceHiding()
|
||||
|
|
@ -254,6 +267,29 @@ internal class StakingViewModel @Inject constructor(
|
|||
onNextClick(actionType, activeStake.pendingActions)
|
||||
}
|
||||
|
||||
override fun showApprovalBottomSheet() {
|
||||
stateController.update(
|
||||
ShowApprovalBottomSheetTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
) {
|
||||
stateController.update(DismissBottomSheetStateTransformer())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun onApprovalClick() {
|
||||
viewModelScope.launch {
|
||||
stateController.update(
|
||||
SetApprovalBottomSheetInProgressTransformer {
|
||||
stateController.update(DismissBottomSheetStateTransformer())
|
||||
},
|
||||
)
|
||||
|
||||
// todo staking polygon
|
||||
}.saveIn(approvalJobHolder)
|
||||
}
|
||||
|
||||
override fun onExploreClick() {
|
||||
val confirmationDataState = uiState.value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val transactionDoneState = confirmationDataState?.transactionDoneState as? TransactionDoneState.Content
|
||||
|
|
@ -265,7 +301,16 @@ internal class StakingViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onShareClick() {
|
||||
// TODO add hash to clipboard and send analytics event
|
||||
val confirmationDataState = uiState.value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val transactionDoneState = confirmationDataState?.transactionDoneState as? TransactionDoneState.Content
|
||||
val txUrl = transactionDoneState?.txUrl
|
||||
|
||||
if (txUrl != null) {
|
||||
vibratorHapticManager.performOneTime(TangemHapticEffect.OneTime.Click)
|
||||
clipboardManager.setText(text = txUrl)
|
||||
}
|
||||
|
||||
// TODO staking [REDACTED_TASK_KEY]
|
||||
}
|
||||
|
||||
fun setRouter(router: InnerStakingRouter, stateRouter: StakingStateRouter) {
|
||||
|
|
@ -273,6 +318,10 @@ internal class StakingViewModel @Inject constructor(
|
|||
this.stakingStateRouter = stateRouter
|
||||
}
|
||||
|
||||
private fun setupApprovalNeeded() {
|
||||
isApprovalNeeded = isApproveNeededUseCase(cryptoCurrencyStatus.currency).getOrElse { false }
|
||||
}
|
||||
|
||||
private fun subscribeOnCurrencyStatusUpdates() {
|
||||
viewModelScope.launch {
|
||||
getUserWalletUseCase(userWalletId).fold(
|
||||
|
|
@ -287,6 +336,8 @@ internal class StakingViewModel @Inject constructor(
|
|||
ifRight = {
|
||||
cryptoCurrencyStatus = it
|
||||
|
||||
setupApprovalNeeded()
|
||||
|
||||
val networkId = cryptoCurrencyStatus.currency.network.id
|
||||
val isStakeMoreAvailable = isStakeMoreAvailableUseCase(networkId)
|
||||
stateController.update(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue