Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-14 15:06:29 +03:00
parent 0d0cb3e871
commit 5f68c41da2
13 changed files with 216 additions and 56 deletions

View file

@ -12,6 +12,7 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.features.staking.impl.navigation.InnerStakingRouter
import com.tangem.features.staking.impl.presentation.state.StakingStateController
import com.tangem.features.staking.impl.presentation.state.StakingStateRouter
import com.tangem.features.staking.impl.presentation.ui.StakingScreen
import com.tangem.features.staking.impl.presentation.viewmodel.StakingViewModel
@ -31,6 +32,9 @@ internal class StakingFragment : ComposeFragment() {
@Inject
lateinit var router: StakingRouter
@Inject
lateinit var stateController: StakingStateController
@Inject
lateinit var analyticsEventsHandler: AnalyticsEventHandler
@ -48,6 +52,7 @@ internal class StakingFragment : ComposeFragment() {
innerStakingRouter,
StakingStateRouter(
fragmentManager = WeakReference(parentFragmentManager),
stateController = stateController,
),
)
}

View file

@ -35,7 +35,7 @@ internal class StakingStateController @Inject constructor() {
return StakingUiState(
clickIntents = StakingClickIntentsStub,
cryptoCurrencyName = "",
currentScreen = StakingUiStateType.InitialInfo,
currentStep = StakingStep.InitialInfo,
initialInfoState = StakingStates.InitialInfoState.Empty(),
amountState = StakingStates.AmountState.Empty(),
validatorState = StakingStates.ValidatorState.Empty(),

View file

@ -1,22 +1,15 @@
package com.tangem.features.staking.impl.presentation.state
import androidx.fragment.app.FragmentManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import java.lang.ref.WeakReference
internal class StakingStateRouter(
private val fragmentManager: WeakReference<FragmentManager>,
private val stateController: StakingStateController,
) {
private var mutableCurrentState: MutableStateFlow<StakingUiStateType> = MutableStateFlow(getInitState())
val currentState: StateFlow<StakingUiStateType>
get() = mutableCurrentState.asStateFlow()
fun clear() {
mutableCurrentState.update { getInitState() }
stateController.update { it.copy(currentStep = getInitialState()) }
}
fun popBackStack() {
@ -24,49 +17,48 @@ internal class StakingStateRouter(
}
fun onBackClick(isSuccess: Boolean = false) {
val type = currentState.value
val type = stateController.uiState.value.currentStep
when {
isSuccess -> popBackStack()
else -> when (type) {
StakingUiStateType.Amount -> showInitial()
StakingUiStateType.ValidatorAndFee -> showAmount()
StakingStep.Amount -> showInitial()
StakingStep.ValidatorAndFee -> showAmount()
else -> popBackStack()
}
}
}
fun onNextClick() {
when (currentState.value) {
StakingUiStateType.InitialInfo -> showAmount()
StakingUiStateType.Amount,
StakingUiStateType.ValidatorAndFee,
-> showConfirm()
StakingUiStateType.Confirm -> onBackClick()
when (stateController.uiState.value.currentStep) {
StakingStep.InitialInfo -> showAmount()
StakingStep.Amount -> showValidator()
StakingStep.ValidatorAndFee -> showConfirm()
StakingStep.Confirm -> onBackClick()
}
}
fun onPrevClick() {
when (currentState.value) {
StakingUiStateType.Amount -> showInitial()
when (stateController.uiState.value.currentStep) {
StakingStep.Amount -> showInitial()
else -> popBackStack()
}
}
private fun showInitial() {
mutableCurrentState.update { StakingUiStateType.InitialInfo }
stateController.update { it.copy(currentStep = StakingStep.InitialInfo) }
}
fun showAmount() {
mutableCurrentState.update { StakingUiStateType.Amount }
stateController.update { it.copy(currentStep = StakingStep.Amount) }
}
fun showValidator() {
mutableCurrentState.update { StakingUiStateType.ValidatorAndFee }
stateController.update { it.copy(currentStep = StakingStep.ValidatorAndFee) }
}
fun showConfirm() {
mutableCurrentState.update { StakingUiStateType.Confirm }
stateController.update { it.copy(currentStep = StakingStep.Confirm) }
}
private fun getInitState() = StakingUiStateType.InitialInfo
private fun getInitialState() = StakingStep.InitialInfo
}

View file

@ -12,7 +12,7 @@ import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickInten
internal data class StakingUiState(
val clickIntents: StakingClickIntents,
val cryptoCurrencyName: String,
val currentScreen: StakingUiStateType,
val currentStep: StakingStep,
val initialInfoState: StakingStates.InitialInfoState,
val amountState: StakingStates.AmountState,
val validatorState: StakingStates.ValidatorState,
@ -94,6 +94,8 @@ internal sealed class StakingStates {
sealed class ConfirmStakingState : StakingStates() {
data class Data(
override val isPrimaryButtonEnabled: Boolean,
val isSuccess: Boolean,
val isStaking: Boolean,
) : ConfirmStakingState()
data class Empty(
@ -102,7 +104,7 @@ internal sealed class StakingStates {
}
}
enum class StakingUiStateType {
enum class StakingStep {
InitialInfo,
Amount,
ValidatorAndFee,

View file

@ -5,4 +5,8 @@ import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickInten
object StakingClickIntentsStub : StakingClickIntents {
override fun onBackClick() {}
override fun onNextClick() {}
override fun onPrevClick() {}
}

View file

@ -10,7 +10,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.R
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.StakingUiStateType
import com.tangem.features.staking.impl.presentation.state.StakingStep
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.utils.Provider
import java.math.BigDecimal
@ -24,7 +24,7 @@ internal class SetInitialDataStateTransformer(
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(
clickIntents = clickIntents,
currentScreen = StakingUiStateType.InitialInfo,
currentStep = StakingStep.InitialInfo,
initialInfoState = createInitialInfoState(),
)
}

View file

@ -0,0 +1,11 @@
package com.tangem.features.staking.impl.presentation.ui
import androidx.compose.runtime.Composable
import com.tangem.features.staking.impl.presentation.state.StakingStates
@Composable
internal fun StakingAmountContent(state: StakingStates.AmountState) {
if (state !is StakingStates.AmountState.Data) return
// TODO staking
}

View file

@ -3,9 +3,11 @@ package com.tangem.features.staking.impl.presentation.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -15,7 +17,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.rows.CornersToRound
import com.tangem.core.ui.components.rows.RoundableCornersRow
import com.tangem.core.ui.extensions.resolveReference
@ -32,19 +33,13 @@ internal fun StakingInitialInfoContent(state: StakingStates.InitialInfoState) {
Column(
modifier = Modifier // Do not put fillMaxSize() in here
.background(TangemTheme.colors.background.tertiary)
.padding(TangemTheme.dimens.spacing16),
.padding(TangemTheme.dimens.spacing16)
.verticalScroll(rememberScrollState()),
) {
MetricsBlock(state)
Spacer(modifier = Modifier.height(8.dp))
StakingDetailsRows(state)
Spacer(modifier = Modifier.height(8.dp))
PrimaryButton(
text = stringResource(id = R.string.common_stake),
modifier = Modifier.fillMaxWidth(),
onClick = {
// TODO staking
},
)
}
}
@ -185,7 +180,7 @@ private class StakingInitialInfoContentPreviewProvider : PreviewParameterProvide
override val values: Sequence<StakingStates.InitialInfoState.Data>
get() = sequenceOf(
StakingStates.InitialInfoState.Data(
isPrimaryButtonEnabled = false,
isPrimaryButtonEnabled = true,
available = "15 SOL",
onStake = "0 SOL",
aprRange = stringReference("2.54-5.12%"),

View file

@ -0,0 +1,119 @@
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.*
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.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.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.state.StakingStep
@Composable
internal fun StakingNavigationButtons(uiState: StakingUiState, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing16,
),
) {
StakingNavigationButton(
uiState = uiState,
modifier = Modifier,
)
}
}
@Composable
private fun StakingNavigationButton(uiState: StakingUiState, modifier: Modifier = Modifier) {
val hapticFeedback = LocalHapticFeedback.current
val confirmState = uiState.confirmStakingState
val isSuccess = (confirmState as? StakingStates.ConfirmStakingState.Data)?.isSuccess ?: false
val isStaking = (confirmState as? StakingStates.ConfirmStakingState.Data)?.isStaking ?: false
val isButtonsVisible = uiState.currentStep != StakingStep.Confirm
val isStakingState = uiState.currentStep == StakingStep.Confirm && !isSuccess && !isStaking
val (buttonTextId, buttonClick) = getButtonData(
currentState = uiState,
)
val isButtonEnabled = isButtonEnabled(uiState)
val buttonIcon = if (isStakingState) {
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()
}
}
TangemButton(
text = stringResource(buttonTextId),
icon = buttonIcon,
enabled = isButtonEnabled,
onClick = {
if (isStakingState) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
buttonClick()
},
showProgress = false,
modifier = Modifier.fillMaxWidth(),
colors = TangemButtonsDefaults.primaryButtonColors,
)
}
}
private fun getButtonData(currentState: StakingUiState): Pair<Int, () -> Unit> {
return when (currentState.currentStep) {
StakingStep.InitialInfo,
StakingStep.Amount,
StakingStep.ValidatorAndFee,
StakingStep.Confirm,
-> R.string.common_next to { currentState.clickIntents.onNextClick() }
}
}
private fun isButtonEnabled(uiState: StakingUiState): Boolean {
return when (uiState.currentStep) {
StakingStep.InitialInfo -> uiState.initialInfoState.isPrimaryButtonEnabled
StakingStep.Amount -> uiState.amountState.isPrimaryButtonEnabled
StakingStep.ValidatorAndFee -> uiState.validatorState.isPrimaryButtonEnabled
StakingStep.Confirm -> uiState.confirmStakingState.isPrimaryButtonEnabled
}
}

View file

@ -16,7 +16,7 @@ import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.state.StakingUiStateType
import com.tangem.features.staking.impl.presentation.state.StakingStep
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
@ -40,25 +40,28 @@ internal fun StakingScreen(uiState: StakingUiState) {
uiState = uiState,
modifier = Modifier.weight(1f),
)
StakingNavigationButtons(
uiState = uiState,
)
}
}
@Composable
private fun SendAppBar(uiState: StakingUiState) {
val titleRes = when (uiState.currentScreen) {
StakingUiStateType.InitialInfo -> stringResource(id = R.string.common_stake)
StakingUiStateType.Amount -> stringResource(id = R.string.send_amount_label)
StakingUiStateType.ValidatorAndFee -> stringResource(id = R.string.common_stake)
StakingUiStateType.Confirm -> ""
val titleRes = when (uiState.currentStep) {
StakingStep.InitialInfo -> stringResource(id = R.string.common_stake)
StakingStep.Amount -> stringResource(id = R.string.send_amount_label)
StakingStep.ValidatorAndFee -> stringResource(id = R.string.common_stake)
StakingStep.Confirm -> ""
}
val backIcon = when (uiState.currentScreen) {
StakingUiStateType.Amount,
StakingUiStateType.ValidatorAndFee,
StakingUiStateType.Confirm,
val backIcon = when (uiState.currentStep) {
StakingStep.Amount,
StakingStep.ValidatorAndFee,
StakingStep.Confirm,
-> {
R.drawable.ic_close_24
}
StakingUiStateType.InitialInfo -> {
StakingStep.InitialInfo -> {
R.drawable.ic_back_24
}
}
@ -74,7 +77,7 @@ private fun SendAppBar(uiState: StakingUiState) {
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = Modifier) {
val currentScreen = uiState.currentScreen
val currentScreen = uiState.currentStep
var currentStateProxy by remember { mutableStateOf(currentScreen) }
var isTransitionAnimationRunning by remember { mutableStateOf(false) }
@ -115,10 +118,16 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M
isTransitionAnimationRunning = transition.targetState != transition.currentState
when (state) {
StakingUiStateType.InitialInfo -> StakingInitialInfoContent(
StakingStep.InitialInfo -> StakingInitialInfoContent(
state = uiState.initialInfoState,
)
else -> Unit
StakingStep.Amount -> StakingAmountContent(
state = uiState.amountState,
)
StakingStep.ValidatorAndFee -> StakingValidatorAndFeeContent(
state = uiState.validatorState,
)
else -> TODO()
}
}
}

View file

@ -0,0 +1,11 @@
package com.tangem.features.staking.impl.presentation.ui
import androidx.compose.runtime.Composable
import com.tangem.features.staking.impl.presentation.state.StakingStates
@Composable
internal fun StakingValidatorAndFeeContent(state: StakingStates.ValidatorState) {
if (state !is StakingStates.ValidatorState.Data) return
// TODO staking
}

View file

@ -3,4 +3,8 @@ package com.tangem.features.staking.impl.presentation.viewmodel
internal interface StakingClickIntents {
fun onBackClick()
fun onNextClick()
fun onPrevClick()
}

View file

@ -63,6 +63,14 @@ internal class StakingViewModel @Inject constructor(
stakingStateRouter.onBackClick()
}
override fun onNextClick() {
stakingStateRouter.onNextClick()
}
override fun onPrevClick() {
stakingStateRouter.onBackClick()
}
fun setRouter(router: InnerStakingRouter, stateRouter: StakingStateRouter) {
innerRouter = router
this.stakingStateRouter = stateRouter