Updated on 2026-08-14
This commit is contained in:
parent
60258b3096
commit
fa948bd91a
28 changed files with 651 additions and 83 deletions
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.features.staking.impl.presentation.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
|
||||
@Immutable
|
||||
internal sealed class InnerFeeState {
|
||||
|
||||
data class Content(
|
||||
val fees: TransactionFee,
|
||||
) : InnerFeeState()
|
||||
|
||||
data object Loading : InnerFeeState()
|
||||
|
||||
data object Error : InnerFeeState()
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.features.staking.impl.presentation.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.domain.staking.model.Yield
|
||||
|
||||
@Immutable
|
||||
internal sealed class InnerValidatorState {
|
||||
|
||||
data class Content(
|
||||
val chosenValidator: Yield.Validator,
|
||||
) : InnerValidatorState()
|
||||
|
||||
data object Loading : InnerValidatorState()
|
||||
|
||||
data object Error : InnerValidatorState()
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
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.wrappedList
|
||||
import com.tangem.features.staking.impl.R
|
||||
|
||||
internal sealed class StakingNotification(val config: NotificationConfig) {
|
||||
|
||||
sealed class Error(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.ic_alert_24,
|
||||
buttonState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : StakingNotification(
|
||||
config = NotificationConfig(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
buttonsState = buttonState,
|
||||
onCloseClick = onCloseClick,
|
||||
),
|
||||
) {
|
||||
// TODO staking
|
||||
}
|
||||
|
||||
sealed class Warning(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
buttonsState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : StakingNotification(
|
||||
config = NotificationConfig(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
buttonsState = buttonsState,
|
||||
onCloseClick = onCloseClick,
|
||||
),
|
||||
) {
|
||||
data class EarnRewards(
|
||||
val currencyName: String,
|
||||
val days: Int,
|
||||
) : Warning(
|
||||
title = resourceReference(R.string.staking_notification_earn_rewards_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.staking_notification_earn_rewards_text,
|
||||
wrappedList(currencyName, days),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,6 @@ internal class StakingStateController @Inject constructor() {
|
|||
currentStep = StakingStep.InitialInfo,
|
||||
initialInfoState = StakingStates.InitialInfoState.Empty(),
|
||||
amountState = AmountState.Empty(),
|
||||
validatorState = StakingStates.ValidatorState.Empty(),
|
||||
confirmStakingState = StakingStates.ConfirmStakingState.Empty(),
|
||||
isBalanceHidden = false,
|
||||
event = consumedEvent(),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.features.staking.impl.presentation.state
|
||||
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import kotlinx.coroutines.flow.update
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
internal class StakingStateRouter(
|
||||
|
|
@ -22,7 +21,7 @@ internal class StakingStateRouter(
|
|||
isSuccess -> popBackStack()
|
||||
else -> when (type) {
|
||||
StakingStep.Amount -> showInitial()
|
||||
StakingStep.ValidatorAndFee -> showAmount()
|
||||
StakingStep.Confirm -> showAmount()
|
||||
else -> popBackStack()
|
||||
}
|
||||
}
|
||||
|
|
@ -31,9 +30,9 @@ internal class StakingStateRouter(
|
|||
fun onNextClick() {
|
||||
when (stateController.uiState.value.currentStep) {
|
||||
StakingStep.InitialInfo -> showAmount()
|
||||
StakingStep.Amount -> showValidator()
|
||||
StakingStep.ValidatorAndFee -> showConfirm()
|
||||
StakingStep.Confirm -> onBackClick()
|
||||
StakingStep.Amount -> showConfirm()
|
||||
StakingStep.Confirm -> showSuccess()
|
||||
StakingStep.Success -> onBackClick()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,13 +51,13 @@ internal class StakingStateRouter(
|
|||
stateController.update { it.copy(currentStep = StakingStep.Amount) }
|
||||
}
|
||||
|
||||
fun showValidator() {
|
||||
stateController.update { it.copy(currentStep = StakingStep.ValidatorAndFee) }
|
||||
}
|
||||
|
||||
fun showConfirm() {
|
||||
stateController.update { it.copy(currentStep = StakingStep.Confirm) }
|
||||
}
|
||||
|
||||
fun showSuccess() {
|
||||
stateController.update { it.copy(currentStep = StakingStep.Success) }
|
||||
}
|
||||
|
||||
private fun getInitialState() = StakingStep.InitialInfo
|
||||
}
|
||||
|
|
@ -1,10 +1,15 @@
|
|||
package com.tangem.features.staking.impl.presentation.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.Yield
|
||||
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Ui states of the staking screen
|
||||
|
|
@ -16,7 +21,6 @@ internal data class StakingUiState(
|
|||
val currentStep: StakingStep,
|
||||
val initialInfoState: StakingStates.InitialInfoState,
|
||||
val amountState: AmountState,
|
||||
val validatorState: StakingStates.ValidatorState,
|
||||
val confirmStakingState: StakingStates.ConfirmStakingState,
|
||||
val isBalanceHidden: Boolean,
|
||||
val event: StateEvent<StakingEvent>,
|
||||
|
|
@ -25,12 +29,10 @@ internal data class StakingUiState(
|
|||
fun copyWrapped(
|
||||
initialInfoState: StakingStates.InitialInfoState = this.initialInfoState,
|
||||
amountState: AmountState = this.amountState,
|
||||
validatorState: StakingStates.ValidatorState = this.validatorState,
|
||||
confirmStakingState: StakingStates.ConfirmStakingState = this.confirmStakingState,
|
||||
): StakingUiState = copy(
|
||||
initialInfoState = initialInfoState,
|
||||
amountState = amountState,
|
||||
validatorState = validatorState,
|
||||
confirmStakingState = confirmStakingState,
|
||||
)
|
||||
}
|
||||
|
|
@ -58,32 +60,14 @@ internal sealed class StakingStates {
|
|||
) : InitialInfoState()
|
||||
}
|
||||
|
||||
/** Validator state */
|
||||
sealed class ValidatorState : StakingStates() {
|
||||
data class Data(
|
||||
override val isPrimaryButtonEnabled: Boolean,
|
||||
) : ValidatorState()
|
||||
|
||||
data class Empty(
|
||||
override val isPrimaryButtonEnabled: Boolean = false,
|
||||
) : ValidatorState()
|
||||
}
|
||||
|
||||
/** Fee state */
|
||||
sealed class FeeState : StakingStates() {
|
||||
data class Data(
|
||||
override val isPrimaryButtonEnabled: Boolean,
|
||||
) : FeeState()
|
||||
|
||||
data class Empty(
|
||||
override val isPrimaryButtonEnabled: Boolean = false,
|
||||
) : FeeState()
|
||||
}
|
||||
|
||||
/** Confirm state */
|
||||
sealed class ConfirmStakingState : StakingStates() {
|
||||
data class Data(
|
||||
override val isPrimaryButtonEnabled: Boolean,
|
||||
val feeState: FeeState,
|
||||
val validatorState: ValidatorState,
|
||||
val notifications: ImmutableList<StakingNotification>,
|
||||
val footerText: String,
|
||||
val isSuccess: Boolean,
|
||||
val isStaking: Boolean,
|
||||
) : ConfirmStakingState()
|
||||
|
|
@ -92,11 +76,25 @@ internal sealed class StakingStates {
|
|||
override val isPrimaryButtonEnabled: Boolean = false,
|
||||
) : ConfirmStakingState()
|
||||
}
|
||||
|
||||
data class FeeState(
|
||||
val innerFeeState: InnerFeeState,
|
||||
val fee: Fee?,
|
||||
val rate: BigDecimal?,
|
||||
val isFeeConvertibleToFiat: Boolean,
|
||||
val appCurrency: AppCurrency,
|
||||
val isFeeApproximate: Boolean,
|
||||
)
|
||||
|
||||
data class ValidatorState(
|
||||
val validatorState: InnerValidatorState,
|
||||
val availableValidators: List<Yield.Validator>,
|
||||
)
|
||||
}
|
||||
|
||||
enum class StakingStep {
|
||||
InitialInfo,
|
||||
Amount,
|
||||
ValidatorAndFee,
|
||||
Confirm,
|
||||
Success,
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.previewdata
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType.Coin
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.Yield
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerFeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerValidatorState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingNotification
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal object ConfirmStakingStatePreviewData {
|
||||
|
||||
private val validatorList = listOf(
|
||||
Yield.Validator(
|
||||
address = "0xa6e768fef2d1af36c0cfdb276422e7881a83e951",
|
||||
status = "active",
|
||||
name = "Luganodes",
|
||||
image = "https://assets.stakek.it/validators/luganodes.png",
|
||||
apr = BigDecimal("0.054823398040640445"),
|
||||
commission = 0.1,
|
||||
stakedBalance = "355544384.45009977",
|
||||
website = "https://luganodes.com/",
|
||||
votingPower = 0.09778360195377911,
|
||||
preferred = true,
|
||||
),
|
||||
Yield.Validator(
|
||||
address = "0x35b1ca0f398905cf752e6fe122b51c88022fca32",
|
||||
status = "active",
|
||||
name = "InfStones",
|
||||
image = "https://assets.stakek.it/validators/infstones.png",
|
||||
apr = BigDecimal("0.057786472172836965"),
|
||||
commission = 0.05,
|
||||
stakedBalance = "12495684.05643019",
|
||||
website = "https://infstones.com/",
|
||||
votingPower = 0.0034366257754399774,
|
||||
preferred = true,
|
||||
),
|
||||
Yield.Validator(
|
||||
address = "0xd14a87025109013b0a2354a775cb335f926af65a",
|
||||
status = "active",
|
||||
name = "Kiln",
|
||||
image = "https://assets.stakek.it/validators/kiln.png",
|
||||
apr = BigDecimal("0.057786472172836965"),
|
||||
commission = 0.05,
|
||||
stakedBalance = "85400369.96393165",
|
||||
website = "https://infstones.com/",
|
||||
votingPower = 0.023487238579718264,
|
||||
preferred = true,
|
||||
),
|
||||
)
|
||||
|
||||
private val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "MATIC",
|
||||
value = BigDecimal(0.159806),
|
||||
decimals = 18,
|
||||
type = Coin,
|
||||
),
|
||||
)
|
||||
|
||||
val confirmStakingState = StakingStates.ConfirmStakingState.Data(
|
||||
isPrimaryButtonEnabled = true,
|
||||
feeState = StakingStates.FeeState(
|
||||
innerFeeState = InnerFeeState.Content(TransactionFee.Single(fee)),
|
||||
fee = fee,
|
||||
rate = BigDecimal.ONE,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = true,
|
||||
),
|
||||
validatorState = StakingStates.ValidatorState(
|
||||
validatorState = InnerValidatorState.Content(
|
||||
chosenValidator = validatorList[0],
|
||||
),
|
||||
availableValidators = validatorList,
|
||||
),
|
||||
footerText = "You stake \$715.11 and will be receiving ~\$35 monthly",
|
||||
notifications = persistentListOf(
|
||||
StakingNotification.Warning.EarnRewards(
|
||||
currencyName = "Solana",
|
||||
days = 2,
|
||||
),
|
||||
),
|
||||
isStaking = false,
|
||||
isSuccess = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.features.staking.impl.presentation.state.previewdata.ConfirmStakingStatePreviewData
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
internal class SetConfirmStateDataStateTransformer(
|
||||
private val yield: Yield,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val isFeeApproximateUseCase: IsFeeApproximateUseCase,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
// TODO staking fill with real data
|
||||
return prevState.copy(
|
||||
confirmStakingState = ConfirmStakingStatePreviewData.confirmStakingState,
|
||||
)
|
||||
}
|
||||
|
||||
private fun isFeeApproximate(fee: Fee): Boolean {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
return isFeeApproximateUseCase(
|
||||
networkId = cryptoCurrencyStatus.currency.network.id,
|
||||
amountType = fee.amount.type,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@ internal class SetInitialDataStateTransformer(
|
|||
if (maxApr - minApr < EQUALITY_THRESHOLD) {
|
||||
return stringReference("$formattedMinApr%")
|
||||
}
|
||||
return resourceReference(R.string.common_percent_range, wrappedList(formattedMinApr, formattedMaxApr))
|
||||
return resourceReference(R.string.common_range, wrappedList(formattedMinApr, formattedMaxApr))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
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.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
||||
import com.tangem.common.ui.amountScreen.ui.AmountBlock
|
||||
import com.tangem.core.ui.components.SpacerHMax
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.previewdata.ConfirmStakingStatePreviewData
|
||||
import com.tangem.features.staking.impl.presentation.ui.block.NotificationsBlock
|
||||
import com.tangem.features.staking.impl.presentation.ui.block.StakingFeeBlock
|
||||
|
||||
@Composable
|
||||
internal fun StakingConfirmContent(amountState: AmountState, state: StakingStates.ConfirmStakingState) {
|
||||
if (state !is StakingStates.ConfirmStakingState.Data) return
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(TangemTheme.dimens.spacing16)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
AmountBlock(
|
||||
amountState = amountState,
|
||||
isClickDisabled = true,
|
||||
isEditingDisabled = true,
|
||||
onClick = {},
|
||||
)
|
||||
StakingFeeBlock(feeState = state.feeState)
|
||||
NotificationsBlock(notifications = state.notifications)
|
||||
SpacerHMax()
|
||||
FooterText(text = state.footerText)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FooterText(text: String) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = text,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_StakingConfirmContent() {
|
||||
TangemThemePreview {
|
||||
Column(Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
StakingConfirmContent(
|
||||
amountState = AmountStatePreviewData.amountState,
|
||||
state = ConfirmStakingStatePreviewData.confirmStakingState,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,8 +51,8 @@ private fun StakingNavigationButton(uiState: StakingUiState, modifier: Modifier
|
|||
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 isButtonsVisible = uiState.currentStep != StakingStep.Success
|
||||
val isStakingState = uiState.currentStep == StakingStep.Success && !isSuccess && !isStaking
|
||||
|
||||
val (buttonTextId, buttonClick) = getButtonData(
|
||||
currentState = uiState,
|
||||
|
|
@ -103,8 +103,8 @@ private fun getButtonData(currentState: StakingUiState): Pair<Int, () -> Unit> {
|
|||
return when (currentState.currentStep) {
|
||||
StakingStep.InitialInfo,
|
||||
StakingStep.Amount,
|
||||
StakingStep.ValidatorAndFee,
|
||||
StakingStep.Confirm,
|
||||
StakingStep.Success,
|
||||
-> R.string.common_next to { currentState.clickIntents.onNextClick() }
|
||||
}
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ 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
|
||||
StakingStep.Success -> true
|
||||
}
|
||||
}
|
||||
|
|
@ -52,13 +52,13 @@ private fun SendAppBar(uiState: StakingUiState) {
|
|||
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 -> ""
|
||||
StakingStep.Confirm -> stringResource(id = R.string.common_stake)
|
||||
StakingStep.Success -> ""
|
||||
}
|
||||
val backIcon = when (uiState.currentStep) {
|
||||
StakingStep.Amount,
|
||||
StakingStep.ValidatorAndFee,
|
||||
StakingStep.Confirm,
|
||||
StakingStep.Success,
|
||||
-> {
|
||||
R.drawable.ic_close_24
|
||||
}
|
||||
|
|
@ -127,8 +127,9 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M
|
|||
isBalanceHiding = uiState.isBalanceHidden,
|
||||
clickIntents = uiState.clickIntents,
|
||||
)
|
||||
StakingStep.ValidatorAndFee -> StakingValidatorAndFeeContent(
|
||||
state = uiState.validatorState,
|
||||
StakingStep.Confirm -> StakingConfirmContent(
|
||||
amountState = uiState.amountState,
|
||||
state = uiState.confirmStakingState,
|
||||
)
|
||||
else -> TODO()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.features.staking.impl.presentation.ui.block
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingNotification
|
||||
|
||||
@Composable
|
||||
internal fun NotificationsBlock(notifications: List<StakingNotification>) {
|
||||
notifications.forEach {
|
||||
Notification(config = it.config, iconTint = TangemTheme.colors.icon.accent)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package com.tangem.features.staking.impl.presentation.ui.block
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
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.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.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.rows.SelectorRowItem
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.amountScreen.utils.getCryptoReference
|
||||
import com.tangem.common.ui.amountScreen.utils.getFiatReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerFeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Composable
|
||||
internal fun StakingFeeBlock(feeState: StakingStates.FeeState) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.common_network_fee_title),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
val feeAmount = feeState.fee?.amount
|
||||
val (title, icon) = R.string.common_fee_selector_option_market to R.drawable.ic_bird_24
|
||||
SelectorRowItem(
|
||||
titleRes = title,
|
||||
iconRes = icon,
|
||||
preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate),
|
||||
postDot = if (feeState.isFeeConvertibleToFiat) {
|
||||
getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
ellipsizeOffset = feeAmount?.currencySymbol?.length,
|
||||
isSelected = true,
|
||||
showDivider = false,
|
||||
showSelectedAppearance = false,
|
||||
paddingValues = PaddingValues(),
|
||||
)
|
||||
FeeLoading(feeState.innerFeeState)
|
||||
FeeError(feeState.innerFeeState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.FeeLoading(feeSelectorState: InnerFeeState) {
|
||||
AnimatedContent(
|
||||
targetState = feeSelectorState,
|
||||
label = "Fee Loading State Change",
|
||||
modifier = Modifier.align(Alignment.CenterEnd),
|
||||
) {
|
||||
if (it == InnerFeeState.Loading) {
|
||||
RectangleShimmer(
|
||||
radius = TangemTheme.dimens.radius3,
|
||||
modifier = Modifier.size(
|
||||
height = TangemTheme.dimens.size12,
|
||||
width = TangemTheme.dimens.size90,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.FeeError(feeSelectorState: InnerFeeState) {
|
||||
AnimatedContent(
|
||||
targetState = feeSelectorState,
|
||||
label = "Fee Error State Change",
|
||||
modifier = Modifier.align(Alignment.CenterEnd),
|
||||
) {
|
||||
if (it == InnerFeeState.Error) {
|
||||
Text(
|
||||
text = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) value: StakingStates.FeeState) {
|
||||
TangemThemePreview {
|
||||
StakingFeeBlock(
|
||||
feeState = value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class FeeBlockPreviewProvider : PreviewParameterProvider<StakingStates.FeeState> {
|
||||
|
||||
override val values: Sequence<StakingStates.FeeState>
|
||||
get() = sequenceOf(
|
||||
feeState,
|
||||
)
|
||||
|
||||
private val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "MATIC",
|
||||
value = BigDecimal(0.159806),
|
||||
decimals = 18,
|
||||
type = AmountType.Coin,
|
||||
),
|
||||
)
|
||||
|
||||
private val feeState = StakingStates.FeeState(
|
||||
innerFeeState = InnerFeeState.Content(TransactionFee.Single(normal = fee)),
|
||||
fee = fee,
|
||||
rate = BigDecimal.ONE,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = true,
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
|
@ -15,14 +15,17 @@ import com.tangem.domain.staking.model.Yield
|
|||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
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.state.StakingStep
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.HideBalanceStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.SetConfirmStateDataStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.SetInitialDataStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountChangeStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountCurrencyChangeStateTransformer
|
||||
|
|
@ -45,6 +48,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val isFeeApproximateUseCase: IsFeeApproximateUseCase,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
|
||||
|
||||
|
|
@ -72,9 +76,10 @@ internal class StakingViewModel @Inject constructor(
|
|||
|
||||
private var innerRouter: InnerStakingRouter by Delegates.notNull()
|
||||
private var userWallet: UserWallet by Delegates.notNull()
|
||||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||
private var appCurrency: AppCurrency by Delegates.notNull()
|
||||
|
||||
init {
|
||||
subscribeOnSelectedAppCurrency()
|
||||
subscribeOnBalanceHiding()
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
}
|
||||
|
|
@ -85,6 +90,27 @@ internal class StakingViewModel @Inject constructor(
|
|||
|
||||
override fun onNextClick() {
|
||||
stakingStateRouter.onNextClick()
|
||||
when (value.currentStep) {
|
||||
StakingStep.Confirm -> {
|
||||
stateController.update(
|
||||
SetConfirmStateDataStateTransformer(
|
||||
yield = yield,
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
isFeeApproximateUseCase = isFeeApproximateUseCase,
|
||||
),
|
||||
)
|
||||
}
|
||||
StakingStep.InitialInfo -> {
|
||||
// TODO staking
|
||||
}
|
||||
StakingStep.Amount -> {
|
||||
// TODO staking
|
||||
}
|
||||
StakingStep.Success -> {
|
||||
// TODO staking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPrevClick() {
|
||||
|
|
@ -131,7 +157,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
yield = yield,
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
userWalletProvider = Provider { userWallet },
|
||||
appCurrencyProvider = Provider { selectedAppCurrencyFlow.value },
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
@ -153,18 +179,14 @@ internal class StakingViewModel @Inject constructor(
|
|||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
|
||||
return getSelectedAppCurrencyUseCase()
|
||||
private fun subscribeOnSelectedAppCurrency() {
|
||||
getSelectedAppCurrencyUseCase()
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.map { maybeAppCurrency ->
|
||||
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
||||
.onEach { maybeAppCurrency ->
|
||||
appCurrency = maybeAppCurrency.getOrElse { AppCurrency.Default }
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = AppCurrency.Default,
|
||||
)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue