Updated on 2026-08-14
This commit is contained in:
commit
2a5e3fa7eb
220 changed files with 5932 additions and 1815 deletions
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.features.staking.impl.presentation.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Immutable
|
||||
sealed class FeeState {
|
||||
|
||||
data class Content(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.features.staking.impl.presentation.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceType
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -11,19 +13,23 @@ sealed class InnerYieldBalanceState {
|
|||
val rewardsCrypto: String,
|
||||
val rewardsFiat: String,
|
||||
val isRewardsToClaim: Boolean,
|
||||
val balance: List<BalanceGroupedState>,
|
||||
val balance: ImmutableList<BalanceGroupedState>,
|
||||
) : InnerYieldBalanceState()
|
||||
|
||||
data object Empty : InnerYieldBalanceState()
|
||||
}
|
||||
|
||||
// TODO staking get rid of unstable types
|
||||
@Immutable
|
||||
data class BalanceGroupedState(
|
||||
val items: ImmutableList<BalanceState>,
|
||||
val footer: TextReference?,
|
||||
val title: TextReference,
|
||||
val type: BalanceGroupType,
|
||||
val type: BalanceType,
|
||||
val isClickable: Boolean,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class BalanceState(
|
||||
val validator: Yield.Validator,
|
||||
val cryptoValue: String,
|
||||
|
|
@ -33,10 +39,4 @@ data class BalanceState(
|
|||
val rawCurrencyId: String?,
|
||||
val unbondingPeriod: TextReference,
|
||||
val pendingActions: ImmutableList<PendingAction>,
|
||||
)
|
||||
|
||||
enum class BalanceGroupType {
|
||||
ACTIVE,
|
||||
UNSTAKED,
|
||||
UNKNOWN,
|
||||
}
|
||||
)
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.staking.impl.presentation.state
|
|||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.features.staking.impl.R
|
||||
|
||||
|
|
@ -23,7 +24,15 @@ internal sealed class StakingNotification(val config: NotificationConfig) {
|
|||
onCloseClick = onCloseClick,
|
||||
),
|
||||
) {
|
||||
// TODO staking
|
||||
data class StakedPositionNotFoundError(val message: String) : Error(
|
||||
title = stringReference(message),
|
||||
subtitle = stringReference(message),
|
||||
)
|
||||
|
||||
data class Common(val subtitle: TextReference) : Error(
|
||||
title = resourceReference(R.string.common_error),
|
||||
subtitle = subtitle,
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Warning(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
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.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.SetButtonsStateTransformer
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -20,20 +24,26 @@ internal class StakingStateController @Inject constructor() {
|
|||
|
||||
val uiState: StateFlow<StakingUiState> get() = mutableUiState.asStateFlow()
|
||||
|
||||
private val buttonsTransformer = SetButtonsStateTransformer()
|
||||
|
||||
fun update(function: (StakingUiState) -> StakingUiState) {
|
||||
mutableUiState.update(function = function)
|
||||
mutableUiState.update(function = buttonsTransformer::transform)
|
||||
}
|
||||
|
||||
fun update(transformer: Transformer<StakingUiState>) {
|
||||
mutableUiState.update(function = transformer::transform)
|
||||
mutableUiState.update(function = buttonsTransformer::transform)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
mutableUiState.update { getInitialState() }
|
||||
mutableUiState.update(function = buttonsTransformer::transform)
|
||||
}
|
||||
|
||||
private fun getInitialState(): StakingUiState {
|
||||
return StakingUiState(
|
||||
title = TextReference.EMPTY,
|
||||
clickIntents = StakingClickIntentsStub,
|
||||
cryptoCurrencyName = "",
|
||||
currentStep = StakingStep.InitialInfo,
|
||||
|
|
@ -44,7 +54,8 @@ internal class StakingStateController @Inject constructor() {
|
|||
isBalanceHidden = false,
|
||||
event = consumedEvent(),
|
||||
bottomSheetConfig = null,
|
||||
routeType = RouteType.STAKE,
|
||||
actionType = StakingActionCommonType.ENTER,
|
||||
buttonsState = NavigationButtonsState.Empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.staking.impl.presentation.state
|
||||
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
|
||||
internal class StakingStateRouter(
|
||||
private val appRouter: AppRouter,
|
||||
|
|
@ -14,12 +15,12 @@ internal class StakingStateRouter(
|
|||
|
||||
fun onNextClick() {
|
||||
when (stateController.value.currentStep) {
|
||||
StakingStep.InitialInfo -> when (stateController.value.routeType) {
|
||||
RouteType.STAKE -> showAmount()
|
||||
RouteType.OTHER,
|
||||
RouteType.UNSTAKE,
|
||||
StakingStep.InitialInfo -> when (stateController.value.actionType) {
|
||||
StakingActionCommonType.ENTER -> showAmount()
|
||||
StakingActionCommonType.PENDING_OTHER,
|
||||
StakingActionCommonType.EXIT,
|
||||
-> showConfirmation()
|
||||
RouteType.CLAIM -> showRewardsValidators()
|
||||
StakingActionCommonType.PENDING_REWARDS -> showRewardsValidators()
|
||||
}
|
||||
StakingStep.RewardsValidators,
|
||||
StakingStep.Validators,
|
||||
|
|
@ -32,10 +33,17 @@ internal class StakingStateRouter(
|
|||
}
|
||||
|
||||
fun onPrevClick() {
|
||||
when (stateController.uiState.value.currentStep) {
|
||||
val uiState = stateController.uiState.value
|
||||
when (uiState.currentStep) {
|
||||
StakingStep.InitialInfo -> onBackClick()
|
||||
StakingStep.Amount -> showInitial()
|
||||
StakingStep.Confirmation -> showAmount()
|
||||
StakingStep.Confirmation -> {
|
||||
if (uiState.actionType != StakingActionCommonType.ENTER) {
|
||||
showInitial()
|
||||
} else {
|
||||
showAmount()
|
||||
}
|
||||
}
|
||||
StakingStep.Validators -> showConfirmation()
|
||||
StakingStep.RewardsValidators -> showInitial()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ package com.tangem.features.staking.impl.presentation.state
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
|
||||
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.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.InfoType
|
||||
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -16,6 +18,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
*/
|
||||
@Immutable
|
||||
internal data class StakingUiState(
|
||||
val title: TextReference,
|
||||
val clickIntents: StakingClickIntents,
|
||||
val cryptoCurrencyName: String,
|
||||
val currentStep: StakingStep,
|
||||
|
|
@ -25,7 +28,8 @@ internal data class StakingUiState(
|
|||
val confirmationState: StakingStates.ConfirmationState,
|
||||
val isBalanceHidden: Boolean,
|
||||
val bottomSheetConfig: TangemBottomSheetConfig?,
|
||||
val routeType: RouteType,
|
||||
val actionType: StakingActionCommonType,
|
||||
val buttonsState: NavigationButtonsState,
|
||||
val event: StateEvent<StakingEvent>,
|
||||
) {
|
||||
|
||||
|
|
@ -55,17 +59,6 @@ internal sealed class StakingStates {
|
|||
val isStakeMoreAvailable: Boolean,
|
||||
) : InitialInfoState()
|
||||
|
||||
data class InitialInfoItems(
|
||||
val available: String,
|
||||
val onStake: String,
|
||||
val aprRange: TextReference,
|
||||
val unbondingPeriod: String,
|
||||
val minimumRequirement: String,
|
||||
val rewardClaiming: String,
|
||||
val warmupPeriod: String,
|
||||
val rewardSchedule: String,
|
||||
)
|
||||
|
||||
data class Empty(
|
||||
override val isPrimaryButtonEnabled: Boolean = false,
|
||||
) : InitialInfoState()
|
||||
|
|
@ -94,6 +87,7 @@ internal sealed class StakingStates {
|
|||
val notifications: ImmutableList<StakingNotification>,
|
||||
val footerText: String,
|
||||
val transactionDoneState: TransactionDoneState,
|
||||
val pendingActionInProgress: PendingAction? = null,
|
||||
) : ConfirmationState()
|
||||
|
||||
data class Empty(
|
||||
|
|
@ -108,11 +102,4 @@ enum class StakingStep {
|
|||
Amount,
|
||||
Confirmation,
|
||||
Validators,
|
||||
}
|
||||
|
||||
enum class RouteType {
|
||||
STAKE,
|
||||
UNSTAKE,
|
||||
CLAIM,
|
||||
OTHER,
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.staking.model.stakekit.*
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceGroupType
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceGroupedState
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
|
||||
|
|
@ -59,15 +58,18 @@ internal class YieldBalancesConverter(
|
|||
.groupBy { it.type.toGroup() }
|
||||
.mapNotNull { item ->
|
||||
val (title, footer) = getGroupTitle(item.key)
|
||||
val isClickable = getClickableType(item.key)
|
||||
title?.let {
|
||||
BalanceGroupedState(
|
||||
items = item.value.mapBalances().toPersistentList(),
|
||||
footer = footer,
|
||||
title = it,
|
||||
type = item.key,
|
||||
isClickable = isClickable,
|
||||
)
|
||||
}
|
||||
}
|
||||
.toPersistentList()
|
||||
|
||||
private fun List<BalanceItem>.mapBalances(): List<BalanceState> {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
|
|
@ -111,31 +113,37 @@ internal class YieldBalancesConverter(
|
|||
}
|
||||
|
||||
private fun BalanceType.toGroup() = when (this) {
|
||||
BalanceType.PREPARING,
|
||||
BalanceType.STAKED,
|
||||
BalanceType.REWARDS,
|
||||
BalanceType.AVAILABLE,
|
||||
BalanceType.LOCKED,
|
||||
-> BalanceGroupType.ACTIVE
|
||||
BalanceType.UNSTAKING,
|
||||
BalanceType.UNLOCKING,
|
||||
BalanceType.UNSTAKED,
|
||||
-> BalanceGroupType.UNSTAKED
|
||||
BalanceType.UNKNOWN,
|
||||
-> BalanceGroupType.UNKNOWN
|
||||
-> BalanceType.UNKNOWN
|
||||
else -> this
|
||||
}
|
||||
|
||||
private fun getGroupTitle(type: BalanceGroupType) = when (type) {
|
||||
BalanceGroupType.ACTIVE -> resourceReference(
|
||||
R.string.staking_active,
|
||||
) to resourceReference(
|
||||
R.string.staking_active_footer,
|
||||
)
|
||||
BalanceGroupType.UNSTAKED -> resourceReference(
|
||||
R.string.staking_unstaked,
|
||||
) to resourceReference(
|
||||
R.string.staking_unstaked_footer,
|
||||
)
|
||||
BalanceGroupType.UNKNOWN -> null to null
|
||||
private fun getGroupTitle(type: BalanceType) = when (type) {
|
||||
BalanceType.STAKED -> resourceReference(R.string.staking_active) to
|
||||
resourceReference(R.string.staking_active_footer)
|
||||
BalanceType.UNSTAKED -> resourceReference(R.string.staking_unstaked) to
|
||||
resourceReference(R.string.staking_unstaked_footer)
|
||||
BalanceType.UNSTAKING -> resourceReference(R.string.staking_unstaking) to null
|
||||
BalanceType.AVAILABLE -> null to null
|
||||
BalanceType.PREPARING -> null to null
|
||||
BalanceType.REWARDS -> null to null
|
||||
BalanceType.LOCKED -> null to null
|
||||
BalanceType.UNLOCKING -> null to null
|
||||
BalanceType.UNKNOWN -> null to null
|
||||
}
|
||||
|
||||
private fun getClickableType(type: BalanceType) = when (type) {
|
||||
BalanceType.STAKED,
|
||||
BalanceType.UNSTAKED,
|
||||
-> true
|
||||
BalanceType.AVAILABLE,
|
||||
BalanceType.UNSTAKING,
|
||||
BalanceType.PREPARING,
|
||||
BalanceType.REWARDS,
|
||||
BalanceType.LOCKED,
|
||||
BalanceType.UNLOCKING,
|
||||
BalanceType.UNKNOWN,
|
||||
-> false
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.staking.impl.presentation.state.previewdata
|
|||
import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceType
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
|
|
@ -59,11 +60,12 @@ internal object InitialStakingStatePreview {
|
|||
rewardsFiat = "100 $",
|
||||
rewardsCrypto = "100 SOL",
|
||||
isRewardsToClaim = false,
|
||||
balance = listOf(
|
||||
balance = persistentListOf(
|
||||
BalanceGroupedState(
|
||||
title = stringReference("Staked"),
|
||||
footer = null,
|
||||
type = BalanceGroupType.ACTIVE,
|
||||
type = BalanceType.STAKED,
|
||||
isClickable = true,
|
||||
items = persistentListOf(
|
||||
BalanceState(
|
||||
cryptoValue = "100",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.staking.impl.presentation.state.stub
|
|||
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.InfoType
|
||||
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
|
||||
|
|
@ -11,7 +12,9 @@ object StakingClickIntentsStub : StakingClickIntents {
|
|||
|
||||
override fun onBackClick() {}
|
||||
|
||||
override fun onNextClick(pendingActions: ImmutableList<PendingAction>) {}
|
||||
override fun onNextClick(actionType: StakingActionCommonType?, pendingActions: ImmutableList<PendingAction>) {}
|
||||
|
||||
override fun onActionClick(pendingAction: PendingAction?) {}
|
||||
|
||||
override fun onPrevClick() {}
|
||||
|
||||
|
|
@ -33,8 +36,6 @@ object StakingClickIntentsStub : StakingClickIntents {
|
|||
|
||||
override fun openRewardsValidators() {}
|
||||
|
||||
override fun selectRewardValidator(rewardValue: String) {}
|
||||
|
||||
override fun onExploreClick() {}
|
||||
|
||||
override fun onShareClick() {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class AddStakingErrorTransformer(
|
||||
private val error: StakingError,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val confirmationState =
|
||||
prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
|
||||
|
||||
return prevState.copy(
|
||||
confirmationState = confirmationState.copy(
|
||||
notifications = (confirmationState.notifications + convertToNotification(error)).toPersistentList(),
|
||||
feeState = FeeState.Error,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertToNotification(error: StakingError): StakingNotification {
|
||||
return when (error) {
|
||||
is StakingError.StakedPositionNotFoundError -> StakingNotification.Error.StakedPositionNotFoundError(
|
||||
message = error.toString(),
|
||||
)
|
||||
// TODO staking
|
||||
else -> StakingNotification.Error.Common(
|
||||
subtitle = stringReference(error.toString()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class SetButtonsStateTransformer : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val confirmState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
|
||||
val buttonsState = if (prevState.isButtonsVisible()) {
|
||||
NavigationButtonsState.Data(
|
||||
primaryButton = getPrimaryButton(prevState),
|
||||
prevButton = getPrevButton(prevState),
|
||||
secondaryButton = getSecondaryButton(prevState),
|
||||
extraButtons = getExtraButtons(prevState),
|
||||
txUrl = (confirmState?.transactionDoneState as? TransactionDoneState.Content)?.txUrl,
|
||||
)
|
||||
} else {
|
||||
NavigationButtonsState.Empty
|
||||
}
|
||||
|
||||
return prevState.copy(buttonsState = buttonsState)
|
||||
}
|
||||
|
||||
private fun getPrimaryButton(prevState: StakingUiState): NavigationButton {
|
||||
val confirmState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val innerConfirmState = confirmState?.innerState
|
||||
|
||||
val isPrimaryInProgress =
|
||||
confirmState?.pendingActions?.getPrimaryAction() == confirmState?.pendingActionInProgress
|
||||
val isConfirmation = prevState.currentStep == StakingStep.Confirmation
|
||||
val isInProgress = innerConfirmState == InnerConfirmationStakingState.IN_PROGRESS
|
||||
val isCompleted = innerConfirmState == InnerConfirmationStakingState.COMPLETED
|
||||
|
||||
val isIconVisible = isConfirmation && !isCompleted
|
||||
val isShowProgress = isInProgress && isPrimaryInProgress
|
||||
return NavigationButton(
|
||||
textReference = prevState.getButtonText(),
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
isSecondary = false,
|
||||
isIconVisible = isIconVisible,
|
||||
showProgress = isShowProgress,
|
||||
isEnabled = prevState.isButtonEnabled(),
|
||||
onClick = { prevState.onPrimaryClick() },
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSecondaryButton(prevState: StakingUiState): NavigationButton? {
|
||||
val confirmState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val innerConfirmState = confirmState?.innerState
|
||||
|
||||
val isConfirmation = prevState.currentStep == StakingStep.Confirmation
|
||||
val isInProgress = innerConfirmState == InnerConfirmationStakingState.IN_PROGRESS
|
||||
val isCompleted = innerConfirmState == InnerConfirmationStakingState.COMPLETED
|
||||
|
||||
return confirmState?.pendingActions?.getSecondaryAction()?.let { pendingAction ->
|
||||
val isSecondaryInProgress = pendingAction == confirmState.pendingActionInProgress
|
||||
val isShowProgress = isInProgress && isSecondaryInProgress
|
||||
NavigationButton(
|
||||
textReference = getPendingActionTitle(pendingAction.type),
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
isSecondary = true,
|
||||
isIconVisible = true,
|
||||
showProgress = isShowProgress,
|
||||
isEnabled = prevState.isButtonEnabled(),
|
||||
onClick = { prevState.clickIntents.onActionClick(pendingAction) },
|
||||
).takeIf { isConfirmation && !isCompleted }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPrevButton(prevState: StakingUiState): NavigationButton? {
|
||||
return NavigationButton(
|
||||
textReference = TextReference.EMPTY,
|
||||
iconRes = R.drawable.ic_back_24,
|
||||
isSecondary = true,
|
||||
isIconVisible = true,
|
||||
showProgress = false,
|
||||
isEnabled = true,
|
||||
onClick = prevState.clickIntents::onPrevClick,
|
||||
).takeIf { prevState.currentStep.isPrevButtonVisible() }
|
||||
}
|
||||
|
||||
private fun getExtraButtons(prevState: StakingUiState): ImmutableList<NavigationButton> {
|
||||
return persistentListOf(
|
||||
NavigationButton(
|
||||
textReference = resourceReference(R.string.common_explore),
|
||||
iconRes = R.drawable.ic_web_24,
|
||||
isSecondary = true,
|
||||
isIconVisible = true,
|
||||
showProgress = false,
|
||||
isEnabled = true,
|
||||
onClick = prevState.clickIntents::onExploreClick,
|
||||
),
|
||||
NavigationButton(
|
||||
textReference = resourceReference(R.string.common_share),
|
||||
iconRes = R.drawable.ic_share_24,
|
||||
isSecondary = true,
|
||||
isIconVisible = true,
|
||||
showProgress = false,
|
||||
isEnabled = true,
|
||||
onClick = prevState.clickIntents::onShareClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<PendingAction>.getPrimaryAction(): PendingAction? = getOrNull(0)
|
||||
|
||||
private fun List<PendingAction>.getSecondaryAction(): PendingAction? = getOrNull(1)
|
||||
|
||||
private fun StakingUiState.isButtonsVisible(): Boolean = when (currentStep) {
|
||||
StakingStep.InitialInfo -> isStakeMoreAvailable()
|
||||
StakingStep.RewardsValidators -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
private fun StakingUiState.getButtonText(): TextReference {
|
||||
return when (currentStep) {
|
||||
StakingStep.InitialInfo -> {
|
||||
val initialState = initialInfoState as? StakingStates.InitialInfoState.Data
|
||||
if (initialState?.yieldBalance is InnerYieldBalanceState.Data) {
|
||||
resourceReference(R.string.staking_stake_more)
|
||||
} else {
|
||||
resourceReference(R.string.common_next)
|
||||
}
|
||||
}
|
||||
|
||||
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.Validators -> resourceReference(R.string.common_continue)
|
||||
StakingStep.Amount,
|
||||
StakingStep.RewardsValidators,
|
||||
-> resourceReference(R.string.common_next)
|
||||
}
|
||||
}
|
||||
|
||||
private fun StakingUiState.onPrimaryClick() {
|
||||
when (currentStep) {
|
||||
StakingStep.InitialInfo -> {
|
||||
val actionType = StakingActionCommonType.ENTER.takeIf { isStakeMoreAvailable() }
|
||||
clickIntents.onAmountValueChange("") // reset amount state
|
||||
clickIntents.onNextClick(actionType)
|
||||
}
|
||||
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())
|
||||
}
|
||||
} else {
|
||||
clickIntents.onBackClick()
|
||||
}
|
||||
}
|
||||
StakingStep.RewardsValidators -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun StakingStep.isPrevButtonVisible(): Boolean = when (this) {
|
||||
StakingStep.InitialInfo,
|
||||
StakingStep.RewardsValidators,
|
||||
StakingStep.Confirmation,
|
||||
StakingStep.Validators,
|
||||
-> false
|
||||
StakingStep.Amount,
|
||||
-> true
|
||||
}
|
||||
|
||||
private fun StakingUiState.isButtonEnabled(): Boolean {
|
||||
return when (currentStep) {
|
||||
StakingStep.InitialInfo -> initialInfoState.isPrimaryButtonEnabled
|
||||
StakingStep.Amount -> amountState.isPrimaryButtonEnabled
|
||||
StakingStep.Confirmation -> confirmationState.isPrimaryButtonEnabled
|
||||
StakingStep.RewardsValidators -> rewardsValidatorsState.isPrimaryButtonEnabled
|
||||
StakingStep.Validators -> true
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun getPendingActionTitle(type: StakingActionType?): TextReference = when (type) {
|
||||
StakingActionType.CLAIM_REWARDS -> resourceReference(R.string.common_claim_rewards)
|
||||
StakingActionType.RESTAKE_REWARDS -> resourceReference(R.string.staking_restake_rewards)
|
||||
StakingActionType.WITHDRAW -> resourceReference(R.string.staking_withdraw)
|
||||
StakingActionType.RESTAKE -> resourceReference(R.string.staking_restake)
|
||||
StakingActionType.CLAIM_UNSTAKED -> resourceReference(R.string.staking_claim_unstaked)
|
||||
StakingActionType.UNLOCK_LOCKED -> resourceReference(R.string.staking_unlocked_locked)
|
||||
StakingActionType.STAKE_LOCKED -> resourceReference(R.string.staking_stake_locked)
|
||||
StakingActionType.VOTE -> resourceReference(R.string.staking_vote)
|
||||
StakingActionType.REVOKE -> resourceReference(R.string.staking_revoke)
|
||||
StakingActionType.VOTE_LOCKED -> resourceReference(R.string.staking_vote_locked)
|
||||
StakingActionType.REVOTE -> resourceReference(R.string.staking_revote)
|
||||
StakingActionType.REBOND -> resourceReference(R.string.staking_rebond)
|
||||
StakingActionType.MIGRATE -> resourceReference(R.string.staking_migrate)
|
||||
StakingActionType.STAKE -> resourceReference(R.string.common_stake)
|
||||
StakingActionType.UNSTAKE -> resourceReference(R.string.common_unstake)
|
||||
StakingActionType.UNKNOWN -> TextReference.EMPTY
|
||||
null -> TextReference.EMPTY
|
||||
}
|
||||
|
||||
private fun StakingUiState.isStakeMoreAvailable(): Boolean {
|
||||
val initialState = initialInfoState as? StakingStates.InitialInfoState.Data
|
||||
return initialState?.isStakeMoreAvailable == true || initialState?.yieldBalance is InnerYieldBalanceState.Empty
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SetConfirmationStateInProgressTransformer : Transformer<StakingUiState> {
|
||||
internal class SetConfirmationStateInProgressTransformer(
|
||||
private val pendingAction: PendingAction?,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
return prevState.copy(
|
||||
|
|
@ -19,6 +22,7 @@ internal class SetConfirmationStateInProgressTransformer : Transformer<StakingUi
|
|||
isPrimaryButtonEnabled = false,
|
||||
innerState = InnerConfirmationStakingState.IN_PROGRESS,
|
||||
validatorState = validatorState.copySealed(isClickable = false),
|
||||
pendingActionInProgress = pendingAction,
|
||||
)
|
||||
} else {
|
||||
this
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ internal class SetConfirmationStateLoadingTransformer(
|
|||
footerText = "",
|
||||
transactionDoneState = TransactionDoneState.Empty,
|
||||
pendingActions = persistentListOf(),
|
||||
pendingActionInProgress = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,14 +5,10 @@ import com.tangem.common.ui.amountScreen.converters.AmountStateConverter
|
|||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.staking.impl.R
|
||||
|
|
@ -25,10 +21,9 @@ import com.tangem.features.staking.impl.presentation.state.converters.YieldBalan
|
|||
import com.tangem.features.staking.impl.presentation.state.previewdata.ConfirmationStatePreviewData
|
||||
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SetInitialDataStateTransformer(
|
||||
|
|
@ -62,6 +57,10 @@ internal class SetInitialDataStateTransformer(
|
|||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
return prevState.copy(
|
||||
title = TextReference.Res(
|
||||
R.string.staking_initial_info_title,
|
||||
wrappedList(cryptoCurrencyStatusProvider().currency.name),
|
||||
),
|
||||
clickIntents = clickIntents,
|
||||
currentStep = StakingStep.InitialInfo,
|
||||
initialInfoState = createInitialInfoState(),
|
||||
|
|
@ -87,70 +86,100 @@ internal class SetInitialDataStateTransformer(
|
|||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance
|
||||
|
||||
return persistentListOf(
|
||||
RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_available,
|
||||
startText = TextReference.Res(R.string.staking_details_available),
|
||||
endText = TextReference.Str(
|
||||
value = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = cryptoCurrencyStatus.value.amount,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency.symbol,
|
||||
decimals = cryptoCurrencyStatus.currency.decimals,
|
||||
),
|
||||
return listOfNotNull(
|
||||
createAvailableItem(cryptoCurrencyStatus),
|
||||
createApyItem(),
|
||||
createUnbondingPeriodItem(),
|
||||
createMinimumRequirementItem(cryptoCurrencyStatus),
|
||||
createRewardClaimingItem(),
|
||||
createWarmupPeriodItem(),
|
||||
createRewardScheduleItem(),
|
||||
).toPersistentList()
|
||||
}
|
||||
|
||||
private fun createAvailableItem(cryptoCurrencyStatus: CryptoCurrencyStatus): RoundedListWithDividersItemData {
|
||||
return RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_available,
|
||||
startText = TextReference.Res(R.string.staking_details_available),
|
||||
endText = TextReference.Str(
|
||||
value = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = cryptoCurrencyStatus.value.amount,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency.symbol,
|
||||
decimals = cryptoCurrencyStatus.currency.decimals,
|
||||
),
|
||||
),
|
||||
RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_annual_percentage_rate,
|
||||
startText = TextReference.Res(R.string.staking_details_annual_percentage_rate),
|
||||
endText = getAprRange(),
|
||||
iconClick = { clickIntents.onInfoClick(InfoType.APY) },
|
||||
),
|
||||
RoundedListWithDividersItemData(
|
||||
id = 0, // todo remove in merge
|
||||
startText = TextReference.Res(0), // todo remove in merge
|
||||
endText = TextReference.Str(
|
||||
value = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = (yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance().orZero(),
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency.symbol,
|
||||
decimals = cryptoCurrencyStatus.currency.decimals,
|
||||
),
|
||||
),
|
||||
),
|
||||
RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_unbonding_period,
|
||||
startText = TextReference.Res(R.string.staking_details_unbonding_period),
|
||||
endText = TextReference.Str(yield.metadata.cooldownPeriod.days.toString()),
|
||||
iconClick = { clickIntents.onInfoClick(InfoType.UNBOUNDING_PERIOD) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun createApyItem(): RoundedListWithDividersItemData {
|
||||
return RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_annual_percentage_rate,
|
||||
startText = TextReference.Res(R.string.staking_details_annual_percentage_rate),
|
||||
endText = getAprRange(),
|
||||
iconClick = { clickIntents.onInfoClick(InfoType.APY) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun createUnbondingPeriodItem(): RoundedListWithDividersItemData {
|
||||
return RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_unbonding_period,
|
||||
startText = TextReference.Res(R.string.staking_details_unbonding_period),
|
||||
endText = pluralReference(
|
||||
id = R.plurals.common_days,
|
||||
count = yield.metadata.cooldownPeriod.days,
|
||||
formatArgs = wrappedList(yield.metadata.cooldownPeriod.days),
|
||||
),
|
||||
iconClick = { clickIntents.onInfoClick(InfoType.UNBONDING_PERIOD) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMinimumRequirementItem(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): RoundedListWithDividersItemData? {
|
||||
val minimumCryptoAmount = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum
|
||||
return minimumCryptoAmount?.let {
|
||||
RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_minimum_requirement,
|
||||
startText = TextReference.Res(R.string.staking_details_minimum_requirement),
|
||||
endText = TextReference.Str(
|
||||
value = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = yield.args.enter.args[KEY_AMOUNT]?.minimum?.toBigDecimal(),
|
||||
cryptoAmount = it,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency.symbol,
|
||||
decimals = cryptoCurrencyStatus.currency.decimals,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createRewardClaimingItem(): RoundedListWithDividersItemData {
|
||||
return RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_reward_claiming,
|
||||
startText = TextReference.Res(R.string.staking_details_reward_claiming),
|
||||
endText = TextReference.Str(yield.metadata.rewardClaiming),
|
||||
iconClick = { clickIntents.onInfoClick(InfoType.REWARD_CLAIMING) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun createWarmupPeriodItem(): RoundedListWithDividersItemData {
|
||||
return RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_warmup_period,
|
||||
startText = TextReference.Res(R.string.staking_details_warmup_period),
|
||||
endText = pluralReference(
|
||||
id = R.plurals.common_days,
|
||||
count = yield.metadata.warmupPeriod.days,
|
||||
formatArgs = wrappedList(yield.metadata.warmupPeriod.days),
|
||||
),
|
||||
RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_reward_claiming,
|
||||
startText = TextReference.Res(R.string.staking_details_reward_claiming),
|
||||
endText = TextReference.Str(yield.metadata.rewardClaiming),
|
||||
iconClick = { clickIntents.onInfoClick(InfoType.REWARD_CLAIMING) },
|
||||
),
|
||||
RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_warmup_period,
|
||||
startText = TextReference.Res(R.string.staking_details_warmup_period),
|
||||
endText = TextReference.Str(yield.metadata.warmupPeriod.days.toString()),
|
||||
iconClick = { clickIntents.onInfoClick(InfoType.WARMUP_PERIOD) },
|
||||
),
|
||||
RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_reward_schedule,
|
||||
startText = TextReference.Res(R.string.staking_details_reward_schedule),
|
||||
endText = TextReference.Str(yield.metadata.rewardSchedule),
|
||||
iconClick = { clickIntents.onInfoClick(InfoType.REWARD_SCHEDULE) },
|
||||
),
|
||||
iconClick = { clickIntents.onInfoClick(InfoType.WARMUP_PERIOD) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun createRewardScheduleItem(): RoundedListWithDividersItemData {
|
||||
return RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_reward_schedule,
|
||||
startText = TextReference.Res(R.string.staking_details_reward_schedule),
|
||||
endText = TextReference.Str(yield.metadata.rewardSchedule),
|
||||
iconClick = { clickIntents.onInfoClick(InfoType.REWARD_SCHEDULE) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +220,5 @@ internal class SetInitialDataStateTransformer(
|
|||
|
||||
companion object {
|
||||
private val EQUALITY_THRESHOLD = BigDecimal(1E-10)
|
||||
private const val KEY_AMOUNT = "amount"
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ internal class ShowInfoBottomSheetStateTransformer(
|
|||
title = resourceReference(R.string.staking_details_annual_percentage_rate),
|
||||
text = resourceReference(R.string.staking_details_annual_percentage_rate_info),
|
||||
)
|
||||
InfoType.UNBOUNDING_PERIOD -> StakingInfoBottomSheetConfig(
|
||||
InfoType.UNBONDING_PERIOD -> StakingInfoBottomSheetConfig(
|
||||
title = resourceReference(R.string.staking_details_unbonding_period),
|
||||
text = resourceReference(R.string.staking_details_unbonding_period_info),
|
||||
)
|
||||
|
|
@ -46,7 +46,7 @@ internal class ShowInfoBottomSheetStateTransformer(
|
|||
|
||||
enum class InfoType {
|
||||
APY,
|
||||
UNBOUNDING_PERIOD,
|
||||
UNBONDING_PERIOD,
|
||||
REWARD_CLAIMING,
|
||||
WARMUP_PERIOD,
|
||||
REWARD_SCHEDULE,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,31 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.amount
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class AmountChangeStateTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val yield: Yield,
|
||||
private val value: String,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
private val amountRequirementStateTransformer = AmountRequirementStateTransformer(
|
||||
cryptoCurrencyStatus,
|
||||
yield,
|
||||
value,
|
||||
)
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val updatedAmountState = AmountFieldChangeTransformer(
|
||||
cryptoCurrencyStatus,
|
||||
value,
|
||||
).transform(prevState.amountState)
|
||||
|
||||
return prevState.copy(
|
||||
amountState = AmountFieldChangeTransformer(cryptoCurrencyStatus, value).transform(prevState.amountState),
|
||||
amountState = amountRequirementStateTransformer.transform(updatedAmountState),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,29 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.amount
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldMaxAmountTransformer
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class AmountMaxValueStateTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val yield: Yield,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
private val amountRequirementStateTransformer = AmountRequirementStateTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
yield = yield,
|
||||
value = cryptoCurrencyStatus.value.amount
|
||||
?.parseBigDecimal(cryptoCurrencyStatus.currency.decimals)
|
||||
.orEmpty(),
|
||||
)
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val updatedAmountState = AmountFieldMaxAmountTransformer(cryptoCurrencyStatus).transform(prevState.amountState)
|
||||
return prevState.copy(
|
||||
amountState = AmountFieldMaxAmountTransformer(cryptoCurrencyStatus).transform(prevState.amountState),
|
||||
amountState = amountRequirementStateTransformer.transform(updatedAmountState),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.amount
|
||||
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
import com.tangem.domain.staking.model.stakekit.AddressArgument
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class AmountRequirementStateTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val yield: Yield,
|
||||
private val value: String,
|
||||
) : Transformer<AmountState> {
|
||||
override fun transform(prevState: AmountState): AmountState {
|
||||
val amountRequirements = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]
|
||||
|
||||
return if (prevState !is AmountState.Data || amountRequirements == null) {
|
||||
prevState
|
||||
} else {
|
||||
updateWithError(prevState, amountRequirements)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWithError(prevState: AmountState.Data, amountRequirements: AddressArgument): AmountState {
|
||||
val isRequirementError = isRequirementError(prevState, amountRequirements)
|
||||
return if (isRequirementError) {
|
||||
prevState.copy(
|
||||
amountTextField = prevState.amountTextField.copy(
|
||||
isError = true,
|
||||
error = resourceReference(
|
||||
R.string.staking_amount_requirement_error,
|
||||
wrappedList(
|
||||
BigDecimalFormatter.formatCryptoAmount(
|
||||
amountRequirements.minimum,
|
||||
cryptoCurrencyStatus.currency.symbol,
|
||||
cryptoCurrencyStatus.currency.decimals,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
prevState
|
||||
}
|
||||
}
|
||||
|
||||
private fun isRequirementError(prevState: AmountState.Data, amountRequirements: AddressArgument): Boolean {
|
||||
val amountDecimal = value.parseToBigDecimal(cryptoCurrencyStatus.currency.decimals)
|
||||
|
||||
val isAlreadyErrorState = prevState.amountTextField.isError
|
||||
val isAmountRequired = amountRequirements.required
|
||||
val isAmountZero = amountDecimal.isZero()
|
||||
val isExceedsRequirements =
|
||||
amountRequirements.maximum?.compareTo(amountDecimal) == -1 ||
|
||||
amountRequirements.minimum?.compareTo(amountDecimal) == 1
|
||||
|
||||
return !isAmountZero && isAmountRequired && isExceedsRequirements && !isAlreadyErrorState
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ internal fun StakingClaimRewardsValidatorContent(
|
|||
.background(TangemTheme.colors.background.action)
|
||||
.clickable(
|
||||
onClick = {
|
||||
clickIntents.selectRewardValidator(item.cryptoValue)
|
||||
clickIntents.onActiveStake(item)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ import com.tangem.core.ui.components.SpacerHMax
|
|||
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.state.RouteType
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.TransactionDoneState
|
||||
import com.tangem.features.staking.impl.presentation.state.previewdata.ConfirmationStatePreviewData
|
||||
|
|
@ -34,15 +34,14 @@ internal fun StakingConfirmationContent(
|
|||
amountState: AmountState,
|
||||
state: StakingStates.ConfirmationState,
|
||||
clickIntents: StakingClickIntents,
|
||||
type: RouteType,
|
||||
type: StakingActionCommonType,
|
||||
) {
|
||||
if (state !is StakingStates.ConfirmationState.Data) return
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(TangemTheme.dimens.spacing16)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
|
|
@ -62,7 +61,7 @@ internal fun StakingConfirmationContent(
|
|||
isEditingDisabled = true,
|
||||
onClick = {},
|
||||
)
|
||||
if (type == RouteType.STAKE) {
|
||||
if (type == StakingActionCommonType.ENTER) {
|
||||
ValidatorBlock(validatorState = state.validatorState, onClick = clickIntents::openValidators)
|
||||
}
|
||||
StakingFeeBlock(feeState = state.feeState)
|
||||
|
|
@ -93,7 +92,7 @@ private fun Preview_StakingConfirmationContent() {
|
|||
amountState = AmountStatePreviewData.amountState,
|
||||
state = ConfirmationStatePreviewData.assentStakingState,
|
||||
clickIntents = StakingClickIntentsStub,
|
||||
type = RouteType.STAKE,
|
||||
type = StakingActionCommonType.ENTER,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.features.staking.impl.presentation.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -22,59 +22,87 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.components.containers.FooterContainer
|
||||
import com.tangem.core.ui.components.inputrow.InputRowDefault
|
||||
import com.tangem.core.ui.components.inputrow.InputRowImageInfo
|
||||
import com.tangem.core.ui.components.list.RoundedListWithDividers
|
||||
import com.tangem.core.ui.components.list.roundedListWithDividersItems
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceType
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceGroupedState
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.previewdata.InitialStakingStatePreview
|
||||
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
|
||||
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
|
||||
import com.tangem.utils.StringsSigns.DOT
|
||||
import com.tangem.utils.StringsSigns.PLUS
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
// TODO staking metrics block is temporary disabled
|
||||
// private const val METRICS_BLOCK_KEY = "MetricsBlock"
|
||||
|
||||
private const val STAKING_REWARD_BLOCK_KEY = "StakingRewardBlock"
|
||||
private const val ACTIVE_STAKING_BLOCK_KEY = "ActiveStakingBlock"
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun StakingInitialInfoContent(state: StakingStates.InitialInfoState, clickIntents: StakingClickIntents) {
|
||||
if (state !is StakingStates.InitialInfoState.Data) return
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
modifier = Modifier // Do not put fillMaxSize() in here
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
AnimatedVisibility(state.yieldBalance == InnerYieldBalanceState.Empty) {
|
||||
MetricsBlock(state)
|
||||
}
|
||||
RoundedListWithDividers(state.infoItems)
|
||||
AnimatedContent(targetState = state.yieldBalance, label = "Rewards block visibility animation") {
|
||||
if (it is InnerYieldBalanceState.Data) {
|
||||
StakingRewardBlock(
|
||||
rewardCrypto = it.rewardsCrypto,
|
||||
rewardFiat = it.rewardsFiat,
|
||||
isRewardsToClaim = it.isRewardsToClaim,
|
||||
onRewardsClick = clickIntents::openRewardsValidators,
|
||||
)
|
||||
// TODO staking metrics block is temporary disabled
|
||||
// https://www.figma.com/design/Vs6SkVsFnUPsSCNwlnVf5U?node-id=12484-35755#876661319
|
||||
// if (state.yieldBalance == InnerYieldBalanceState.Empty) {
|
||||
// item(key = METRICS_BLOCK_KEY) {
|
||||
// Column(modifier = Modifier.animateItemPlacement()) {
|
||||
// MetricsBlock(state)
|
||||
// SpacerH12()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
this.roundedListWithDividersItems(
|
||||
rows = state.infoItems,
|
||||
footerContent = { SpacerH12() },
|
||||
)
|
||||
|
||||
if (state.yieldBalance is InnerYieldBalanceState.Data) {
|
||||
item(key = STAKING_REWARD_BLOCK_KEY) {
|
||||
Column(modifier = Modifier.animateItemPlacement()) {
|
||||
StakingRewardBlock(
|
||||
rewardCrypto = state.yieldBalance.rewardsCrypto,
|
||||
rewardFiat = state.yieldBalance.rewardsFiat,
|
||||
isRewardsToClaim = state.yieldBalance.isRewardsToClaim,
|
||||
onRewardsClick = clickIntents::openRewardsValidators,
|
||||
)
|
||||
SpacerH12()
|
||||
}
|
||||
}
|
||||
}
|
||||
AnimatedContent(targetState = state.yieldBalance, label = "Rewards block visibility animation") {
|
||||
if (it is InnerYieldBalanceState.Data) {
|
||||
ActiveStakingBlock(it.balance, clickIntents::onActiveStake)
|
||||
|
||||
if (state.yieldBalance is InnerYieldBalanceState.Data) {
|
||||
item(key = ACTIVE_STAKING_BLOCK_KEY) {
|
||||
Column(modifier = Modifier.animateItemPlacement()) {
|
||||
ActiveStakingBlock(state.yieldBalance.balance, clickIntents::onActiveStake)
|
||||
SpacerH12()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@Composable
|
||||
private fun MetricsBlock(state: StakingStates.InitialInfoState.Data) {
|
||||
Column(
|
||||
|
|
@ -160,7 +188,7 @@ private fun StakingRewardBlock(
|
|||
InputRowDefault(
|
||||
title = resourceReference(R.string.staking_rewards),
|
||||
text = text,
|
||||
iconRes = R.drawable.ic_chevron_right_24,
|
||||
iconRes = R.drawable.ic_chevron_right_24.takeIf { isRewardsToClaim },
|
||||
textColor = textColor,
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
|
|
@ -168,13 +196,14 @@ private fun StakingRewardBlock(
|
|||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(),
|
||||
enabled = isRewardsToClaim,
|
||||
onClick = onRewardsClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActiveStakingBlock(groups: List<BalanceGroupedState>, onClick: (BalanceState) -> Unit) {
|
||||
private fun ActiveStakingBlock(groups: ImmutableList<BalanceGroupedState>, onClick: (BalanceState) -> Unit) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
|
|
@ -192,18 +221,20 @@ private fun ActiveStakingBlock(groups: List<BalanceGroupedState>, onClick: (Bala
|
|||
) {
|
||||
group.items.forEachIndexed { index, balance ->
|
||||
key(balance.validator.address) {
|
||||
val caption = combinedReference(
|
||||
if (group.type == BalanceGroupType.UNSTAKED) {
|
||||
resourceReference(R.string.staking_details_unbonding_period)
|
||||
val caption = if (group.type == BalanceType.UNSTAKING) {
|
||||
combinedReference(
|
||||
resourceReference(R.string.staking_details_unbonding_period),
|
||||
annotatedReference {
|
||||
appendSpace()
|
||||
appendColored(
|
||||
text = balance.unbondingPeriod.resolveReference(),
|
||||
color = TangemTheme.colors.text.accent,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
resourceReference(R.string.app_name)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
combinedReference(
|
||||
resourceReference(R.string.app_name),
|
||||
annotatedReference {
|
||||
appendSpace()
|
||||
appendColored(
|
||||
|
|
@ -213,20 +244,21 @@ private fun ActiveStakingBlock(groups: List<BalanceGroupedState>, onClick: (Bala
|
|||
),
|
||||
color = TangemTheme.colors.text.accent,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
InputRowImageInfo(
|
||||
title = group.title.takeIf { index == 0 },
|
||||
subtitle = stringReference(balance.validator.name),
|
||||
caption = caption,
|
||||
isGrayscaleImage = group.type == BalanceGroupType.UNSTAKED,
|
||||
isGrayscaleImage = group.type == BalanceType.UNSTAKING,
|
||||
infoTitle = balance.fiatAmount,
|
||||
infoSubtitle = balance.cryptoAmount,
|
||||
imageUrl = balance.validator.image.orEmpty(),
|
||||
modifier = Modifier.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(),
|
||||
enabled = group.isClickable,
|
||||
onClick = { onClick(balance) },
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,201 +0,0 @@
|
|||
package com.tangem.features.staking.impl.presentation.ui
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.common.ui.amountScreen.ui.SendDoneButtons
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerW12
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
|
||||
@Composable
|
||||
internal fun StakingNavigationButtons(uiState: StakingUiState, modifier: Modifier = Modifier) {
|
||||
val confirmInnerState = (uiState.confirmationState as? StakingStates.ConfirmationState.Data)?.innerState
|
||||
val isSuccessState = confirmInnerState == InnerConfirmationStakingState.COMPLETED
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
) {
|
||||
val confirmationDataState = uiState.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val transactionDoneState = confirmationDataState?.transactionDoneState as? TransactionDoneState.Content
|
||||
|
||||
SendDoneButtons(
|
||||
txUrl = transactionDoneState?.txUrl.orEmpty(),
|
||||
onExploreClick = uiState.clickIntents::onExploreClick,
|
||||
onShareClick = uiState.clickIntents::onShareClick,
|
||||
isVisible = isSuccessState,
|
||||
)
|
||||
StakingNavigationButton(
|
||||
uiState = uiState,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StakingNavigationButton(uiState: StakingUiState, modifier: Modifier = Modifier) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
||||
val isButtonsVisible = isPrevButtonVisible(uiState.currentStep)
|
||||
|
||||
val innerConfirmState = (uiState.confirmationState as? StakingStates.ConfirmationState.Data)?.innerState
|
||||
val isInProgressInnerState = innerConfirmState == InnerConfirmationStakingState.IN_PROGRESS
|
||||
val isInAssentInnerState = innerConfirmState == InnerConfirmationStakingState.ASSENT
|
||||
|
||||
val showTangemIcon = uiState.currentStep == StakingStep.Confirmation &&
|
||||
(isInProgressInnerState || isInAssentInnerState)
|
||||
|
||||
val buttonTextId = getButtonData(currentState = uiState)
|
||||
val (isButtonEnabled, isButtonDisplayed) = isButtonEnabled(uiState)
|
||||
val buttonIcon = if (showTangemIcon) {
|
||||
TangemButtonIconPosition.End(R.drawable.ic_tangem_24)
|
||||
} else {
|
||||
TangemButtonIconPosition.None
|
||||
}
|
||||
|
||||
Row(modifier = modifier) {
|
||||
AnimatedVisibility(
|
||||
visible = isButtonsVisible,
|
||||
enter = expandHorizontally(expandFrom = Alignment.End),
|
||||
exit = shrinkHorizontally(shrinkTowards = Alignment.End),
|
||||
) {
|
||||
Row {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_back_24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
.background(TangemTheme.colors.button.secondary)
|
||||
.clickable { uiState.clickIntents.onPrevClick() }
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
)
|
||||
SpacerW12()
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = isButtonDisplayed,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
TangemButton(
|
||||
text = stringResource(buttonTextId),
|
||||
icon = buttonIcon,
|
||||
enabled = isButtonEnabled && isButtonDisplayed,
|
||||
onClick = {
|
||||
if (showTangemIcon) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
onPrimaryClick(uiState)
|
||||
},
|
||||
showProgress = isInProgressInnerState,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getButtonData(currentState: StakingUiState): Int {
|
||||
return when (currentState.currentStep) {
|
||||
StakingStep.InitialInfo -> {
|
||||
val initialState = currentState.initialInfoState as? StakingStates.InitialInfoState.Data
|
||||
if (initialState?.yieldBalance is InnerYieldBalanceState.Data) {
|
||||
R.string.staking_stake_more
|
||||
} else {
|
||||
R.string.common_next
|
||||
}
|
||||
}
|
||||
StakingStep.Confirmation -> {
|
||||
val confirmationState = currentState.confirmationState
|
||||
if (confirmationState is StakingStates.ConfirmationState.Data) {
|
||||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
|
||||
R.string.common_close
|
||||
} else {
|
||||
R.string.common_stake
|
||||
}
|
||||
} else {
|
||||
R.string.common_close
|
||||
}
|
||||
}
|
||||
StakingStep.Validators -> R.string.common_continue
|
||||
StakingStep.Amount,
|
||||
StakingStep.RewardsValidators,
|
||||
-> R.string.common_next
|
||||
}
|
||||
}
|
||||
|
||||
private fun onPrimaryClick(currentState: StakingUiState) {
|
||||
when (currentState.currentStep) {
|
||||
StakingStep.InitialInfo -> {
|
||||
val initialState = currentState.initialInfoState as? StakingStates.InitialInfoState.Data
|
||||
if (initialState?.yieldBalance is InnerYieldBalanceState.Data) {
|
||||
if (initialState.isStakeMoreAvailable) {
|
||||
currentState.clickIntents.onNextClick()
|
||||
}
|
||||
} else {
|
||||
currentState.clickIntents.onNextClick()
|
||||
}
|
||||
}
|
||||
StakingStep.Amount -> currentState.clickIntents.onNextClick()
|
||||
StakingStep.Confirmation -> {
|
||||
val confirmationState = currentState.confirmationState
|
||||
if (confirmationState is StakingStates.ConfirmationState.Data) {
|
||||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
|
||||
currentState.clickIntents.onBackClick()
|
||||
} else {
|
||||
currentState.clickIntents.onNextClick()
|
||||
}
|
||||
} else {
|
||||
currentState.clickIntents.onBackClick()
|
||||
}
|
||||
}
|
||||
StakingStep.Validators -> currentState.clickIntents.onNextClick()
|
||||
StakingStep.RewardsValidators -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun isPrevButtonVisible(step: StakingStep): Boolean = when (step) {
|
||||
StakingStep.InitialInfo,
|
||||
StakingStep.RewardsValidators,
|
||||
StakingStep.Confirmation,
|
||||
-> false
|
||||
StakingStep.Amount,
|
||||
StakingStep.Validators,
|
||||
-> true
|
||||
}
|
||||
|
||||
private fun isButtonEnabled(uiState: StakingUiState): Pair<Boolean, Boolean> {
|
||||
return when (uiState.currentStep) {
|
||||
StakingStep.InitialInfo -> {
|
||||
val initialState = uiState.initialInfoState as? StakingStates.InitialInfoState.Data
|
||||
val isDisplayed = initialState?.isStakeMoreAvailable == true
|
||||
uiState.initialInfoState.isPrimaryButtonEnabled to isDisplayed
|
||||
}
|
||||
StakingStep.Amount -> uiState.amountState.isPrimaryButtonEnabled to true
|
||||
StakingStep.Confirmation -> uiState.confirmationState.isPrimaryButtonEnabled to true
|
||||
StakingStep.RewardsValidators -> uiState.rewardsValidatorsState.isPrimaryButtonEnabled to false
|
||||
StakingStep.Validators -> true to true
|
||||
}
|
||||
}
|
||||
|
|
@ -13,8 +13,10 @@ 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.navigationButtons.NavigationButtonsBlock
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
|
|
@ -29,7 +31,7 @@ import kotlinx.coroutines.flow.withIndex
|
|||
|
||||
@Composable
|
||||
internal fun StakingScreen(uiState: StakingUiState) {
|
||||
BackHandler(onBack = uiState.clickIntents::onBackClick)
|
||||
BackHandler(onBack = uiState.clickIntents::onPrevClick)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(color = TangemTheme.colors.background.tertiary)
|
||||
|
|
@ -45,8 +47,13 @@ internal fun StakingScreen(uiState: StakingUiState) {
|
|||
uiState = uiState,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
StakingNavigationButtons(
|
||||
uiState = uiState,
|
||||
NavigationButtonsBlock(
|
||||
buttonState = uiState.buttonsState,
|
||||
modifier = Modifier.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
)
|
||||
StakingBottomSheet(bottomSheetConfig = uiState.bottomSheetConfig)
|
||||
}
|
||||
|
|
@ -68,7 +75,7 @@ private fun SendAppBar(uiState: StakingUiState) {
|
|||
StakingStep.RewardsValidators,
|
||||
StakingStep.Validators,
|
||||
StakingStep.Confirmation,
|
||||
-> stringResource(id = R.string.common_stake)
|
||||
-> uiState.title.resolveReference()
|
||||
}
|
||||
val backIcon = when (uiState.currentStep) {
|
||||
StakingStep.Amount,
|
||||
|
|
@ -155,7 +162,7 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M
|
|||
amountState = uiState.amountState,
|
||||
state = uiState.confirmationState,
|
||||
clickIntents = uiState.clickIntents,
|
||||
type = uiState.routeType,
|
||||
type = uiState.actionType,
|
||||
)
|
||||
StakingStep.Validators -> {
|
||||
val confirmState = uiState.confirmationState
|
||||
|
|
|
|||
|
|
@ -1,13 +1,23 @@
|
|||
package com.tangem.features.staking.impl.presentation.ui.block
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingNotification
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
internal fun NotificationsBlock(notifications: List<StakingNotification>) {
|
||||
notifications.forEach {
|
||||
Notification(config = it.config, iconTint = TangemTheme.colors.icon.accent)
|
||||
internal fun NotificationsBlock(notifications: ImmutableList<StakingNotification>) {
|
||||
notifications.forEach { notification ->
|
||||
key(notification) {
|
||||
Notification(
|
||||
config = notification.config,
|
||||
iconTint = when (notification) {
|
||||
is StakingNotification.Error -> TangemTheme.colors.icon.warning
|
||||
is StakingNotification.Warning -> TangemTheme.colors.icon.accent
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ internal fun StakingFeeBlock(feeState: FeeState) {
|
|||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.common_network_fee_title),
|
||||
style = TangemTheme.typography.caption2,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.staking.impl.presentation.viewmodel
|
|||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.InfoType
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -12,13 +13,18 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
|
|||
|
||||
fun onBackClick()
|
||||
|
||||
fun onNextClick(pendingActions: ImmutableList<PendingAction> = persistentListOf())
|
||||
fun onNextClick(
|
||||
actionType: StakingActionCommonType? = null,
|
||||
pendingActions: ImmutableList<PendingAction> = persistentListOf(),
|
||||
)
|
||||
|
||||
fun onActionClick(pendingAction: PendingAction?)
|
||||
|
||||
fun onPrevClick()
|
||||
|
||||
fun onInfoClick(infoType: InfoType)
|
||||
|
||||
override fun onAmountNext() = onNextClick()
|
||||
override fun onAmountNext() = onNextClick(actionType = null)
|
||||
|
||||
fun openValidators()
|
||||
|
||||
|
|
@ -26,8 +32,6 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
|
|||
|
||||
fun openRewardsValidators()
|
||||
|
||||
fun selectRewardValidator(rewardValue: String)
|
||||
|
||||
fun onActiveStake(activeStake: BalanceState)
|
||||
|
||||
fun onExploreClick()
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
|
|||
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
|
|
@ -62,6 +63,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase,
|
||||
private val submitHashUseCase: SubmitHashUseCase,
|
||||
private val isStakeMoreAvailableUseCase: IsStakeMoreAvailableUseCase,
|
||||
private val stakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
|
||||
|
||||
|
|
@ -100,28 +102,36 @@ internal class StakingViewModel @Inject constructor(
|
|||
stakingStateRouter.onBackClick()
|
||||
}
|
||||
|
||||
override fun onNextClick(pendingActions: ImmutableList<PendingAction>) {
|
||||
handleOnNextConfirmationClick()
|
||||
override fun onNextClick(actionType: StakingActionCommonType?, pendingActions: ImmutableList<PendingAction>) {
|
||||
if (actionType != null) {
|
||||
stateController.update { it.copy(actionType = actionType) }
|
||||
}
|
||||
stakingStateRouter.onNextClick()
|
||||
if (isAssentState()) {
|
||||
estimateGas(pendingActions)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnNextConfirmationClick() {
|
||||
override fun onActionClick(pendingAction: PendingAction?) {
|
||||
handleOnNextConfirmationClick(pendingAction)
|
||||
stakingStateRouter.onNextClick()
|
||||
}
|
||||
|
||||
private fun handleOnNextConfirmationClick(pendingAction: PendingAction?) {
|
||||
if (isAssentState()) {
|
||||
viewModelScope.launch {
|
||||
stateController.update(SetConfirmationStateInProgressTransformer())
|
||||
stateController.update(SetConfirmationStateInProgressTransformer(pendingAction))
|
||||
|
||||
val confirmationState =
|
||||
value.confirmationState as? StakingStates.ConfirmationState.Data ?: error("No confirmation state")
|
||||
val validatorState = confirmationState.validatorState as? ValidatorState.Content
|
||||
?: error("No validator provided")
|
||||
val pendingActions = confirmationState.pendingActions
|
||||
|
||||
val stakingTransaction = getStakingTransactionUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
params = ActionParams(
|
||||
actionCommonType = getStakingCommonType(),
|
||||
actionCommonType = value.actionType,
|
||||
integrationId = yield.id,
|
||||
amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||
?: error("No amount provided"),
|
||||
|
|
@ -129,8 +139,8 @@ internal class StakingViewModel @Inject constructor(
|
|||
?: error("No available address"),
|
||||
validatorAddress = validatorState.chosenValidator.address,
|
||||
token = yield.token,
|
||||
passthrough = pendingActions.firstOrNull()?.passthrough,
|
||||
type = pendingActions.firstOrNull()?.type,
|
||||
passthrough = pendingAction?.passthrough,
|
||||
type = pendingAction?.type,
|
||||
),
|
||||
).getOrElse {
|
||||
error(it)
|
||||
|
|
@ -141,7 +151,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
transactionId = stakingTransaction.id,
|
||||
gasEstimate = stakingTransaction.gasEstimate ?: error("No gas estimate available"),
|
||||
txData = TransactionData.Compiled(value = it.hexToBytes()),
|
||||
pendingActions = pendingActions,
|
||||
pendingActionList = confirmationState.pendingActions,
|
||||
)
|
||||
} ?: error("No unsigned transaction available")
|
||||
}
|
||||
|
|
@ -156,21 +166,31 @@ internal class StakingViewModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
val cryptoCurrencyValue = cryptoCurrencyStatus.value
|
||||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
?: error("No confirmation state")
|
||||
val validatorState = confirmationState.validatorState as? ValidatorState.Content
|
||||
?: error("No validator provided")
|
||||
|
||||
val pendingAction = pendingActions.firstOrNull()
|
||||
val stakingGasEstimate = estimateGasUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
params = ActionParams(
|
||||
actionCommonType = getStakingCommonType(),
|
||||
actionCommonType = value.actionType,
|
||||
integrationId = yield.id,
|
||||
amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||
?: error("No amount provided"),
|
||||
address = cryptoCurrencyValue.networkAddress?.defaultAddress?.value
|
||||
?: error("No available address"),
|
||||
validatorAddress = yield.validators.getOrNull(0)?.address ?: error("No available validator"),
|
||||
validatorAddress = validatorState.chosenValidator.address,
|
||||
token = yield.token,
|
||||
passthrough = pendingActions.firstOrNull()?.passthrough,
|
||||
type = pendingActions.firstOrNull()?.type,
|
||||
passthrough = pendingAction?.passthrough,
|
||||
type = pendingAction?.type,
|
||||
),
|
||||
).getOrElse { error("Can't get fee info") }
|
||||
).getOrElse {
|
||||
stateController.update(AddStakingErrorTransformer(it))
|
||||
return@launch
|
||||
}
|
||||
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentTransformer(
|
||||
|
|
@ -196,7 +216,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onAmountValueChange(value: String) {
|
||||
stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, value))
|
||||
stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, yield, value))
|
||||
}
|
||||
|
||||
override fun onAmountPasteTriggerDismiss() {
|
||||
|
|
@ -204,7 +224,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onMaxValueClick() {
|
||||
stateController.update(AmountMaxValueStateTransformer(cryptoCurrencyStatus))
|
||||
stateController.update(AmountMaxValueStateTransformer(cryptoCurrencyStatus, yield))
|
||||
}
|
||||
|
||||
override fun onCurrencyChangeClick(isFiat: Boolean) {
|
||||
|
|
@ -217,25 +237,17 @@ internal class StakingViewModel @Inject constructor(
|
|||
stateController.update(ValidatorSelectChangeTransformer(validator))
|
||||
}
|
||||
|
||||
override fun openRewardsValidators() {
|
||||
stateController.update { it.copy(routeType = RouteType.CLAIM) }
|
||||
onNextClick()
|
||||
}
|
||||
|
||||
override fun selectRewardValidator(rewardValue: String) {
|
||||
stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, rewardValue))
|
||||
onNextClick()
|
||||
}
|
||||
override fun openRewardsValidators() = onNextClick(actionType = StakingActionCommonType.PENDING_REWARDS)
|
||||
|
||||
override fun onActiveStake(activeStake: BalanceState) {
|
||||
val routeType = if (activeStake.pendingActions.isEmpty()) {
|
||||
RouteType.UNSTAKE
|
||||
val actionType = if (activeStake.pendingActions.isEmpty()) {
|
||||
StakingActionCommonType.EXIT
|
||||
} else {
|
||||
RouteType.OTHER
|
||||
StakingActionCommonType.PENDING_OTHER
|
||||
}
|
||||
stateController.update { it.copy(routeType = routeType) }
|
||||
stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, activeStake.cryptoValue))
|
||||
onNextClick(activeStake.pendingActions)
|
||||
stateController.update(ValidatorSelectChangeTransformer(activeStake.validator))
|
||||
stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, yield, activeStake.cryptoValue))
|
||||
onNextClick(actionType, activeStake.pendingActions)
|
||||
}
|
||||
|
||||
override fun onExploreClick() {
|
||||
|
|
@ -249,7 +261,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onShareClick() {
|
||||
// TODO staking analytics event
|
||||
// TODO add hash to clipboard and send analytics event
|
||||
}
|
||||
|
||||
fun setRouter(router: InnerStakingRouter, stateRouter: StakingStateRouter) {
|
||||
|
|
@ -317,7 +329,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
transactionId: String,
|
||||
gasEstimate: StakingGasEstimate,
|
||||
txData: TransactionData,
|
||||
pendingActions: ImmutableList<PendingAction>,
|
||||
pendingActionList: ImmutableList<PendingAction>,
|
||||
) {
|
||||
sendTransactionUseCase(
|
||||
txData = txData,
|
||||
|
|
@ -331,14 +343,14 @@ internal class StakingViewModel @Inject constructor(
|
|||
appCurrencyProvider = Provider { appCurrency },
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
stakingGasEstimate = gasEstimate,
|
||||
pendingActionList = pendingActions,
|
||||
pendingActionList = pendingActionList,
|
||||
),
|
||||
)
|
||||
// todo add error dialog
|
||||
},
|
||||
ifRight = { txHash ->
|
||||
submitHash(transactionId, txHash)
|
||||
|
||||
updateStakeBalance()
|
||||
val txUrl = getExplorerTransactionUrlUseCase(
|
||||
txHash = txHash,
|
||||
networkId = cryptoCurrencyStatus.currency.network.id,
|
||||
|
|
@ -371,17 +383,22 @@ internal class StakingViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateStakeBalance() {
|
||||
viewModelScope.launch {
|
||||
stakingYieldBalanceUseCase(
|
||||
userWalletId = userWalletId,
|
||||
address = CryptoCurrencyAddress(
|
||||
cryptoCurrencyStatus.currency,
|
||||
cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
),
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAssentState(): Boolean {
|
||||
return value.currentStep == StakingStep.Confirmation &&
|
||||
(value.confirmationState as? StakingStates.ConfirmationState.Data)?.innerState ==
|
||||
InnerConfirmationStakingState.ASSENT
|
||||
}
|
||||
|
||||
private fun getStakingCommonType() = when (value.routeType) {
|
||||
RouteType.STAKE -> StakingActionCommonType.ENTER
|
||||
RouteType.UNSTAKE -> StakingActionCommonType.EXIT
|
||||
RouteType.CLAIM,
|
||||
RouteType.OTHER,
|
||||
-> StakingActionCommonType.PENDING
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue