Updated on 2026-08-14

This commit is contained in:
Tangem 2024-07-23 14:43:09 +03:00
parent 67016b4755
commit 57eb2359a7
19 changed files with 291 additions and 125 deletions

View file

@ -3,6 +3,7 @@ package com.tangem.features.staking.impl.presentation.state
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.amountScreen.models.AmountState
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
@ -47,6 +48,14 @@ internal sealed class StakingStates {
sealed class InitialInfoState : StakingStates() {
data class Data(
override val isPrimaryButtonEnabled: Boolean,
val infoItems: ImmutableList<RoundedListWithDividersItemData>,
val aprRange: TextReference,
val onInfoClick: (InfoType) -> Unit,
val yieldBalance: InnerYieldBalanceState,
val isStakeMoreAvailable: Boolean,
) : InitialInfoState()
data class InitialInfoItems(
val available: String,
val onStake: String,
val aprRange: TextReference,
@ -55,10 +64,7 @@ internal sealed class StakingStates {
val rewardClaiming: String,
val warmupPeriod: String,
val rewardSchedule: String,
val onInfoClick: (InfoType) -> Unit,
val yieldBalance: InnerYieldBalanceState,
val isStakeMoreAvailable: Boolean,
) : InitialInfoState()
)
data class Empty(
override val isPrimaryButtonEnabled: Boolean = false,

View file

@ -1,21 +1,59 @@
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.Yield
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
import kotlinx.collections.immutable.persistentListOf
internal object InitialStakingStatePreview {
val defaultState = StakingStates.InitialInfoState.Data(
isPrimaryButtonEnabled = true,
available = "15 SOL",
onStake = "0 SOL",
aprRange = stringReference("2.54-5.12%"),
unbondingPeriod = "3d",
minimumRequirement = "12 SOL",
rewardClaiming = "Auto",
warmupPeriod = "Days",
rewardSchedule = "Block",
infoItems = persistentListOf(
RoundedListWithDividersItemData(
id = R.string.staking_details_available,
startText = TextReference.Res(R.string.staking_details_available),
endText = TextReference.Str("15 SOL"),
),
RoundedListWithDividersItemData(
id = R.string.staking_details_apy,
startText = TextReference.Res(R.string.staking_details_apy),
endText = TextReference.Str("2.54-5.12%"),
),
RoundedListWithDividersItemData(
id = R.string.staking_details_on_stake,
startText = TextReference.Res(R.string.staking_details_on_stake),
endText = TextReference.Str("0 SOL"),
),
RoundedListWithDividersItemData(
id = R.string.staking_details_unbonding_period,
startText = TextReference.Res(R.string.staking_details_unbonding_period),
endText = TextReference.Str("3d"),
),
RoundedListWithDividersItemData(
id = R.string.staking_details_minimum_requirement,
startText = TextReference.Res(R.string.staking_details_minimum_requirement),
endText = TextReference.Str("12 SOL"),
),
RoundedListWithDividersItemData(
id = R.string.staking_details_reward_claiming,
startText = TextReference.Res(R.string.staking_details_reward_claiming),
endText = TextReference.Str("Auto"),
),
RoundedListWithDividersItemData(
id = R.string.staking_details_warmup_period,
startText = TextReference.Res(R.string.staking_details_warmup_period),
endText = TextReference.Str("Days"),
),
RoundedListWithDividersItemData(
id = R.string.staking_details_reward_schedule,
startText = TextReference.Res(R.string.staking_details_reward_schedule),
endText = TextReference.Str("Block"),
),
),
onInfoClick = {},
yieldBalance = InnerYieldBalanceState.Empty,
isStakeMoreAvailable = true,

View file

@ -14,7 +14,6 @@ import com.tangem.utils.Provider
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
@Suppress("UnusedPrivateMember")
internal class SetConfirmationStateAssentTransformer(
private val appCurrencyProvider: Provider<AppCurrency>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,

View file

@ -5,7 +5,6 @@ import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
@Suppress("UnusedPrivateMember")
internal class SetConfirmationStateLoadingTransformer(
private val yield: Yield,
) : Transformer<StakingUiState> {

View file

@ -4,6 +4,7 @@ import com.tangem.common.extensions.remove
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
@ -26,6 +27,8 @@ import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickInten
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 java.math.BigDecimal
internal class SetInitialDataStateTransformer(
@ -70,33 +73,87 @@ internal class SetInitialDataStateTransformer(
}
private fun createInitialInfoState(): StakingStates.InitialInfoState.Data {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance
return StakingStates.InitialInfoState.Data(
isPrimaryButtonEnabled = true,
available = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = cryptoCurrencyStatus.value.amount,
cryptoCurrency = cryptoCurrencyStatus.currency.symbol,
decimals = cryptoCurrencyStatus.currency.decimals,
),
onStake = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = (yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance().orZero(),
cryptoCurrency = cryptoCurrencyStatus.currency.symbol,
decimals = cryptoCurrencyStatus.currency.decimals,
),
aprRange = getAprRange(),
unbondingPeriod = yield.metadata.cooldownPeriod.days.toString(),
minimumRequirement = yield.metadata.minimumStake.toString(),
rewardClaiming = yield.metadata.rewardClaiming,
warmupPeriod = yield.metadata.warmupPeriod.days.toString(),
rewardSchedule = yield.metadata.rewardSchedule,
infoItems = getInfoItems(),
onInfoClick = clickIntents::onInfoClick,
yieldBalance = yieldBalancesConverter.convert(Unit),
isStakeMoreAvailable = isStakeMoreAvailable,
)
}
private fun getInfoItems(): PersistentList<RoundedListWithDividersItemData> {
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,
),
),
),
RoundedListWithDividersItemData(
id = R.string.staking_details_apy,
startText = TextReference.Res(R.string.staking_details_apy),
endText = getAprRange(),
iconClick = { clickIntents.onInfoClick(InfoType.APY) },
),
RoundedListWithDividersItemData(
id = R.string.staking_details_on_stake,
startText = TextReference.Res(R.string.staking_details_on_stake),
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) },
),
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(),
cryptoCurrency = cryptoCurrencyStatus.currency.symbol,
decimals = cryptoCurrencyStatus.currency.decimals,
),
),
),
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) },
),
)
}
private fun createInitialAmountState(): AmountState {
return amountStateConverter.convert("")
}
@ -134,5 +191,6 @@ internal class SetInitialDataStateTransformer(
companion object {
private val EQUALITY_THRESHOLD = BigDecimal(1E-10)
private const val KEY_AMOUNT = "amount"
}
}

View file

@ -7,9 +7,7 @@ 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.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@ -27,8 +25,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
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.rows.CornersToRound
import com.tangem.core.ui.components.rows.RoundableCornersRow
import com.tangem.core.ui.components.list.RoundedListWithDividers
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -37,7 +34,6 @@ import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
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.state.transformers.InfoType
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.utils.StringsSigns.DOT
import com.tangem.utils.StringsSigns.PLUS
@ -55,13 +51,12 @@ internal fun StakingInitialInfoContent(state: StakingStates.InitialInfoState, cl
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing16,
)
.verticalScroll(rememberScrollState()),
),
) {
AnimatedVisibility(state.yieldBalance == InnerYieldBalanceState.Empty) {
MetricsBlock(state)
}
StakingDetailsRows(state)
RoundedListWithDividers(state.infoItems)
AnimatedContent(targetState = state.yieldBalance, label = "Rewards block visibility animation") {
if (it is InnerYieldBalanceState.Data) {
StakingRewardBlock(
@ -142,57 +137,6 @@ private fun MetricsBlock(state: StakingStates.InitialInfoState.Data) {
}
}
@Composable
internal fun StakingDetailsRows(state: StakingStates.InitialInfoState.Data) {
Column {
InitialInfoContentRow(
startText = stringResource(id = R.string.staking_details_available),
endText = state.available,
cornersToRound = CornersToRound.TOP_2,
)
InitialInfoContentRow(
startText = stringResource(id = R.string.staking_details_on_stake),
endText = state.onStake,
cornersToRound = CornersToRound.ZERO,
)
InitialInfoContentRow(
startText = stringResource(id = R.string.staking_details_apy),
endText = state.aprRange.resolveReference(),
cornersToRound = CornersToRound.ZERO,
iconClick = { state.onInfoClick(InfoType.APY) },
)
InitialInfoContentRow(
startText = stringResource(id = R.string.staking_details_unbonding_period),
endText = state.unbondingPeriod,
cornersToRound = CornersToRound.ZERO,
iconClick = { state.onInfoClick(InfoType.UNBOUNDING_PERIOD) },
)
InitialInfoContentRow(
startText = stringResource(id = R.string.staking_details_minimum_requirement),
endText = state.minimumRequirement,
cornersToRound = CornersToRound.ZERO,
)
InitialInfoContentRow(
startText = stringResource(id = R.string.staking_details_reward_claiming),
endText = state.rewardClaiming,
cornersToRound = CornersToRound.ZERO,
iconClick = { state.onInfoClick(InfoType.REWARD_CLAIMING) },
)
InitialInfoContentRow(
startText = stringResource(id = R.string.staking_details_warmup_period),
endText = state.warmupPeriod,
cornersToRound = CornersToRound.ZERO,
iconClick = { state.onInfoClick(InfoType.WARMUP_PERIOD) },
)
InitialInfoContentRow(
startText = stringResource(id = R.string.staking_details_reward_schedule),
endText = state.rewardSchedule,
cornersToRound = CornersToRound.BOTTOM_2,
iconClick = { state.onInfoClick(InfoType.REWARD_SCHEDULE) },
)
}
}
@Composable
private fun StakingRewardBlock(
rewardCrypto: String,
@ -295,25 +239,7 @@ private fun ActiveStakingBlock(groups: List<BalanceGroupedState>, onClick: (Bala
}
}
@Composable
private fun InitialInfoContentRow(
startText: String,
endText: String,
cornersToRound: CornersToRound,
iconClick: (() -> Unit)? = null,
) {
RoundableCornersRow(
startText = startText,
startTextColor = TangemTheme.colors.text.primary1,
startTextStyle = TangemTheme.typography.body2,
endText = endText,
endTextColor = TangemTheme.colors.text.tertiary,
endTextStyle = TangemTheme.typography.body2,
cornersToRound = cornersToRound,
iconResId = R.drawable.ic_information_24,
iconClick = iconClick,
)
}
// region preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)