Updated on 2026-08-14
This commit is contained in:
parent
0d0cb3e871
commit
5f68c41da2
13 changed files with 216 additions and 56 deletions
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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%"),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -3,4 +3,8 @@ package com.tangem.features.staking.impl.presentation.viewmodel
|
|||
internal interface StakingClickIntents {
|
||||
|
||||
fun onBackClick()
|
||||
|
||||
fun onNextClick()
|
||||
|
||||
fun onPrevClick()
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue