diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/StakingFragment.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/StakingFragment.kt index e96be559de..9648e0164d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/StakingFragment.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/StakingFragment.kt @@ -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, ), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt index 9fdd84f082..fc51c5f7e7 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt @@ -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(), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt index 8e7182f088..bc00ead8ca 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt @@ -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, + private val stateController: StakingStateController, ) { - private var mutableCurrentState: MutableStateFlow = MutableStateFlow(getInitState()) - - val currentState: StateFlow - 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 } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index 3852cd6ed2..1253b63881 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -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, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt index 16a8763435..b09e5d9f4c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt @@ -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() {} } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index 3ee1b970cd..e16098575b 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -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(), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingAmountContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingAmountContent.kt new file mode 100644 index 0000000000..e1324855c8 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingAmountContent.kt @@ -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 +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 7ff32c89e2..97491c4a36 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -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 get() = sequenceOf( StakingStates.InitialInfoState.Data( - isPrimaryButtonEnabled = false, + isPrimaryButtonEnabled = true, available = "15 SOL", onStake = "0 SOL", aprRange = stringReference("2.54-5.12%"), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingNavigationButtons.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingNavigationButtons.kt new file mode 100644 index 0000000000..aaceadf9c6 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingNavigationButtons.kt @@ -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 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 + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt index c1f1bf5a95..6a0d321903 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt @@ -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() } } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorAndFeeContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorAndFeeContent.kt new file mode 100644 index 0000000000..a73d0000d4 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorAndFeeContent.kt @@ -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 +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt index 30f890ae97..8176c2ea6c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt @@ -3,4 +3,8 @@ package com.tangem.features.staking.impl.presentation.viewmodel internal interface StakingClickIntents { fun onBackClick() + + fun onNextClick() + + fun onPrevClick() } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 2c94bdea46..22c0686b9c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -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