Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-24 15:50:00 +05:00
parent fbfcad9325
commit 3acfadfb08
16 changed files with 242 additions and 9 deletions

View file

@ -548,8 +548,18 @@
<string name="settings_forget_wallet">Забыть кошелек</string>
<string name="settings_forget_wallet_footer">Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова.</string>
<string name="settings_wallet_name_title">Имя</string>
<string name="staking_details_apy">APY</string>
<string name="staking_details_apy_info">Годовой процентный доход, который вы можете получить от участия в стейкинге.</string>
<string name="staking_details_available">Доступно</string>
<string name="staking_details_reward_claiming">Способ возраграждения</string>
<string name="staking_details_reward_claiming_info">Способ получения вознаграждений за стейкинг. Он может быть автоматическим или ручным.</string>
<string name="staking_details_reward_schedule">Период возрагражения</string>
<string name="staking_details_reward_schedule_info">Это период, определяющий, когда участники стейкинга получат свои вознаграждения.</string>
<string name="staking_details_title">Стейкинг %s</string>
<string name="staking_details_unbonding_period">Период вывода</string>
<string name="staking_details_unbonding_period_info">Период, который необходимо подождать после запроса на вывод средств из стейкинга, прежде чем токены станут доступны.</string>
<string name="staking_details_warmup_period">Период прогрева</string>
<string name="staking_details_warmup_period_info">Время, необходимое для начала процесса стейкинга.</string>
<string name="story_awe_description">Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте.</string>
<string name="story_awe_title">Революционный аппаратный кошелек</string>
<string name="story_backup_description">До трех карт с одним кошельком</string>

View file

@ -554,6 +554,7 @@
<string name="settings_wallet_name_title">Name</string>
<string name="staking_details_apr">APR</string>
<string name="staking_details_apy">APY</string>
<string name="staking_details_apy_info">The annual percentage return you can earn from participating in staking.</string>
<string name="staking_details_available">Available</string>
<string name="staking_details_average_reward_rate">Average Reward Rate</string>
<string name="staking_details_estimated_profit">%s est. profit</string>
@ -563,11 +564,15 @@
<string name="staking_details_no_rewards_to_claim">No rewards to claim</string>
<string name="staking_details_on_stake">On stake</string>
<string name="staking_details_reward_claiming">Reward claiming</string>
<string name="staking_details_reward_claiming_info">A way to receive staking rewards. It can be claimed automatically or manually.</string>
<string name="staking_details_reward_schedule">Reward schedule</string>
<string name="staking_details_reward_schedule_info">This is a schedule that determines when participants in staking receive their rewards.</string>
<string name="staking_details_rewards_to_claim">Rewards to claim: %s</string>
<string name="staking_details_title">Staking %s</string>
<string name="staking_details_unbonding_period">Unbonding Period</string>
<string name="staking_details_unbonding_period_info">The period you must wait after requesting to withdraw funds from staking before the tokens become available.</string>
<string name="staking_details_warmup_period">Warmup period</string>
<string name="staking_details_warmup_period_info">The allocated time for activating participation in staking.</string>
<string name="staking_native">Native staking</string>
<string name="staking_notification_earn_rewards_text" formatted="false">Staking allow you to earn %1s. Your staking rewards arrive every ~%2s days.</string>
<string name="staking_notification_earn_rewards_title">Earn staking rewards</string>

View file

@ -0,0 +1,55 @@
package com.tangem.core.ui.components.appbar
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
/**
* App bar without buttons
*
* @param text title
* @param modifier modifier
*
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=123%3A287&t=WdN5XpixzZLlQAZO-4"
* >Figma component</a>
*/
@Composable
fun AppBar(text: TextReference, modifier: Modifier = Modifier) {
Box(modifier = modifier.fillMaxWidth()) {
Text(
text = text.resolveReference(),
color = TangemTheme.colors.text.primary1,
maxLines = 1,
style = TangemTheme.typography.subtitle1,
modifier = Modifier
.align(Alignment.Center)
.padding(vertical = TangemTheme.dimens.spacing10),
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun AppBar_Preview() {
TangemThemePreview {
AppBar(
text = stringReference("Tangem"),
modifier = Modifier.background(TangemTheme.colors.background.primary),
)
}
}
// endregion

View file

@ -2,12 +2,16 @@ package com.tangem.core.ui.components.rows
import android.content.res.Configuration
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.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -17,9 +21,9 @@ 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.R
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.R
@Suppress("LongParameterList")
@Composable
@ -32,6 +36,7 @@ fun RoundableCornersRow(
endTextStyle: TextStyle,
cornersToRound: CornersToRound,
iconResId: Int? = null,
iconClick: (() -> Unit)? = null,
) {
Surface(
shape = cornersToRound.getShape(),
@ -54,11 +59,16 @@ fun RoundableCornersRow(
maxLines = 1,
style = startTextStyle,
)
if (iconResId != null) {
if (iconResId != null && iconClick != null) {
Icon(
modifier = Modifier
.padding(TangemTheme.dimens.spacing4)
.size(TangemTheme.dimens.size16),
.size(TangemTheme.dimens.size16)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false, radius = TangemTheme.dimens.radius10),
onClick = iconClick,
),
painter = painterResource(id = R.drawable.ic_alert_24),
contentDescription = null,
tint = TangemTheme.colors.text.tertiary,

View file

@ -42,6 +42,7 @@ internal class StakingStateController @Inject constructor() {
confirmStakingState = StakingStates.ConfirmStakingState.Empty(),
isBalanceHidden = false,
event = consumedEvent(),
bottomSheetConfig = null,
)
}
}

View file

@ -3,9 +3,11 @@ 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.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.features.staking.impl.presentation.state.transformers.InfoType
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal
@ -22,6 +24,7 @@ internal data class StakingUiState(
val amountState: AmountState,
val confirmStakingState: StakingStates.ConfirmStakingState,
val isBalanceHidden: Boolean,
val bottomSheetConfig: TangemBottomSheetConfig?,
val event: StateEvent<StakingEvent>,
) {
@ -52,6 +55,7 @@ internal sealed class StakingStates {
val rewardClaiming: String,
val warmupPeriod: String,
val rewardSchedule: String,
val onInfoClick: (InfoType) -> Unit,
) : InitialInfoState()
data class Empty(

View file

@ -0,0 +1,9 @@
package com.tangem.features.staking.impl.presentation.state.bottomsheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.extensions.TextReference
data class StakingInfoBottomSheetConfig(
val title: TextReference,
val text: TextReference,
) : TangemBottomSheetConfigContent

View file

@ -1,6 +1,7 @@
package com.tangem.features.staking.impl.presentation.state.stub
import com.tangem.domain.staking.model.Yield
import com.tangem.features.staking.impl.presentation.state.transformers.InfoType
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
object StakingClickIntentsStub : StakingClickIntents {
@ -11,6 +12,8 @@ object StakingClickIntentsStub : StakingClickIntents {
override fun onPrevClick() {}
override fun onInfoClick(infoType: InfoType) {}
override fun onAmountValueChange(value: String) {}
override fun onAmountPasteTriggerDismiss() {}

View file

@ -0,0 +1,10 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
internal class DismissBottomSheetStateTransformer : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(bottomSheetConfig = null)
}
}

View file

@ -70,7 +70,7 @@ internal class SetInitialDataStateTransformer(
rewardClaiming = yield.metadata.rewardClaiming,
warmupPeriod = yield.metadata.warmupPeriod.days.toString(),
rewardSchedule = yield.metadata.rewardSchedule,
onInfoClick = clickIntents::onInfoClick,
)
}

View file

@ -0,0 +1,53 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.state.bottomsheet.StakingInfoBottomSheetConfig
import com.tangem.utils.transformer.Transformer
internal class ShowInfoBottomSheetStateTransformer(
private val infoType: InfoType,
private val onDismiss: () -> Unit,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(
bottomSheetConfig = TangemBottomSheetConfig(
onDismissRequest = onDismiss,
isShow = true,
content = when (infoType) {
InfoType.APY -> StakingInfoBottomSheetConfig(
title = resourceReference(R.string.staking_details_apy),
text = resourceReference(R.string.staking_details_apy_info),
)
InfoType.UNBOUNDING_PERIOD -> StakingInfoBottomSheetConfig(
title = resourceReference(R.string.staking_details_unbonding_period),
text = resourceReference(R.string.staking_details_unbonding_period_info),
)
InfoType.REWARD_CLAIMING -> StakingInfoBottomSheetConfig(
title = resourceReference(R.string.staking_details_reward_claiming),
text = resourceReference(R.string.staking_details_reward_claiming_info),
)
InfoType.WARMUP_PERIOD -> StakingInfoBottomSheetConfig(
title = resourceReference(R.string.staking_details_warmup_period),
text = resourceReference(R.string.staking_details_warmup_period_info),
)
InfoType.REWARD_SCHEDULE -> StakingInfoBottomSheetConfig(
title = resourceReference(R.string.staking_details_reward_schedule),
text = resourceReference(R.string.staking_details_reward_schedule_info),
)
},
),
)
}
}
enum class InfoType {
APY,
UNBOUNDING_PERIOD,
REWARD_CLAIMING,
WARMUP_PERIOD,
REWARD_SCHEDULE,
}

View file

@ -25,6 +25,7 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.transformers.InfoType
@Composable
internal fun StakingInitialInfoContent(state: StakingStates.InitialInfoState) {
@ -121,11 +122,13 @@ internal fun StakingDetailsRows(state: StakingStates.InitialInfoState.Data) {
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),
@ -136,21 +139,29 @@ internal fun StakingDetailsRows(state: StakingStates.InitialInfoState.Data) {
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 InitialInfoContentRow(startText: String, endText: String, cornersToRound: CornersToRound) {
private fun InitialInfoContentRow(
startText: String,
endText: String,
cornersToRound: CornersToRound,
iconClick: (() -> Unit)? = null,
) {
RoundableCornersRow(
startText = startText,
startTextColor = TangemTheme.colors.text.primary1,
@ -159,7 +170,8 @@ private fun InitialInfoContentRow(startText: String, endText: String, cornersToR
endTextColor = TangemTheme.colors.text.tertiary,
endTextStyle = TangemTheme.typography.body2,
cornersToRound = cornersToRound,
iconResId = null, // TODO staking add bottom sheets when text will be available
iconResId = R.drawable.ic_information_24,
iconClick = iconClick,
)
}
@ -189,6 +201,7 @@ private class StakingInitialInfoContentPreviewProvider : PreviewParameterProvide
rewardClaiming = "Auto",
warmupPeriod = "Days",
rewardSchedule = "Block",
onInfoClick = {},
),
)
}

View file

@ -14,11 +14,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.tangem.common.ui.amountScreen.AmountScreenContent
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.StakingStates
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.bottomsheet.StakingInfoBottomSheetConfig
import com.tangem.features.staking.impl.presentation.ui.bottomsheet.StakingInfoBottomSheet
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
@ -45,6 +48,15 @@ internal fun StakingScreen(uiState: StakingUiState) {
StakingNavigationButtons(
uiState = uiState,
)
StakingBottomSheet(bottomSheetConfig = uiState.bottomSheetConfig)
}
}
@Composable
fun StakingBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
if (bottomSheetConfig == null) return
when (bottomSheetConfig.content) {
is StakingInfoBottomSheetConfig -> StakingInfoBottomSheet(bottomSheetConfig)
}
}

View file

@ -0,0 +1,38 @@
package com.tangem.features.staking.impl.presentation.ui.bottomsheet
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
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 com.tangem.core.ui.components.appbar.AppBar
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.staking.impl.presentation.state.bottomsheet.StakingInfoBottomSheetConfig
@Composable
fun StakingInfoBottomSheet(config: TangemBottomSheetConfig) {
val scrollState = rememberScrollState()
TangemBottomSheet(
config = config,
) { content: StakingInfoBottomSheetConfig ->
Column(
modifier = Modifier.verticalScroll(scrollState),
) {
AppBar(text = content.title)
Text(
text = content.text.resolveReference(),
color = TangemTheme.colors.text.secondary,
style = TangemTheme.typography.body2,
modifier = Modifier.padding(
horizontal = TangemTheme.dimens.spacing28,
vertical = TangemTheme.dimens.spacing16,
),
)
}
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.features.staking.impl.presentation.viewmodel
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.domain.staking.model.Yield
import com.tangem.features.staking.impl.presentation.state.transformers.InfoType
internal interface StakingClickIntents : AmountScreenClickIntents {
@ -11,6 +12,8 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
fun onPrevClick()
fun onInfoClick(infoType: InfoType)
override fun onAmountNext() = onNextClick()
fun openValidators()

View file

@ -22,8 +22,7 @@ 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.StakingUiState
import com.tangem.features.staking.impl.presentation.state.transformers.HideBalanceStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.SetInitialDataStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.*
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountChangeStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountCurrencyChangeStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountMaxValueStateTransformer
@ -92,6 +91,14 @@ internal class StakingViewModel @Inject constructor(
stakingStateRouter.onBackClick()
}
override fun onInfoClick(infoType: InfoType) {
stateController.update(
ShowInfoBottomSheetStateTransformer(infoType) {
stateController.update(DismissBottomSheetStateTransformer())
},
)
}
override fun onAmountValueChange(value: String) {
stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, value))
}