Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-03 20:38:27 +05:00
parent 920d664fb6
commit 8f48ebb4bb
11 changed files with 100 additions and 24 deletions

View file

@ -2,11 +2,13 @@ package com.tangem.common.ui.navigationButtons
import android.content.res.Configuration
import androidx.compose.animation.*
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -14,14 +16,18 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.common.ui.navigationButtons.preview.NavigationButtonsPreview
import com.tangem.core.ui.components.Keyboard
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.components.keyboardAsState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.isNullOrEmpty
import com.tangem.core.ui.extensions.rememberHapticFeedback
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@ -29,12 +35,17 @@ import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.ImmutableList
@Composable
fun NavigationButtonsBlock(buttonState: NavigationButtonsState, modifier: Modifier = Modifier) {
fun NavigationButtonsBlock(
buttonState: NavigationButtonsState,
modifier: Modifier = Modifier,
footerText: TextReference? = null,
) {
val state = buttonState as? NavigationButtonsState.Data
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier.fillMaxWidth(),
) {
InfoText(footerText)
ExtraButtons(state?.extraButtons, state?.txUrl)
Row(
modifier = Modifier.fillMaxWidth(),
@ -134,6 +145,39 @@ private fun ExtraButtons(extraButtons: ImmutableList<NavigationButton>?, txUrl:
}
}
@Composable
private fun InfoText(footerText: TextReference?, modifier: Modifier = Modifier) {
var isVisibleProxy by remember { mutableStateOf(!footerText.isNullOrEmpty()) }
val keyboard by keyboardAsState()
// the text should appear when the keyboard is closed
LaunchedEffect(footerText, keyboard) {
if (footerText.isNullOrEmpty() && keyboard is Keyboard.Opened) {
return@LaunchedEffect
}
isVisibleProxy = !footerText.isNullOrEmpty()
}
AnimatedVisibility(
visible = isVisibleProxy,
modifier = modifier,
enter = slideInVertically() + fadeIn(),
exit = fadeOut(tween(durationMillis = 300)),
label = "Animate footer text appearance",
) {
val text = remember(this) { requireNotNull(footerText) }
Text(
text = text.resolveReference(),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = TangemTheme.dimens.spacing12),
)
}
}
@Composable
private fun rememberNavigationButton(button: NavigationButton?): MutableState<NavigationButton?> {
return remember(

View file

@ -696,6 +696,7 @@
<string name="staking_rewards">Rewards</string>
<string name="staking_stake_locked">Stake locked</string>
<string name="staking_stake_more">Stake more</string>
<string name="staking_summary_description_text">You stake %1$s and will be receiving ~%2$s yearly</string>
<string name="staking_title_stake">Stake %s</string>
<string name="staking_title_unstake">Unstake %s</string>
<string name="staking_unbonding">Unbonding</string>

View file

@ -89,7 +89,7 @@ internal sealed class StakingStates {
val validatorState: ValidatorState,
val pendingAction: PendingAction?,
val notifications: ImmutableList<NotificationUM>,
val footerText: String,
val footerText: TextReference,
val transactionDoneState: TransactionDoneState,
val isApprovalNeeded: Boolean,
val reduceAmountBy: BigDecimal?,

View file

@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType.Coin
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.Yield
@ -77,7 +78,7 @@ internal object ConfirmationStatePreviewData {
chosenValidator = validatorList[0],
availableValidators = validatorList,
),
footerText = "You stake \$715.11 and will be receiving ~\$35 monthly",
footerText = stringReference("You stake \$715.11 and will be receiving ~\$35 monthly"),
notifications = persistentListOf(
StakingNotification.Info.EarnRewards(
subtitleText = resourceReference(

View file

@ -1,6 +1,7 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.*
@ -37,6 +38,7 @@ internal class SetConfirmationStateCompletedTransformer(
validatorState = validatorState.copySealed(
isClickable = false,
),
footerText = TextReference.EMPTY,
notifications = persistentListOf(),
transactionDoneState = TransactionDoneState.Content(
timestamp = System.currentTimeMillis(),

View file

@ -1,5 +1,6 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
@ -19,6 +20,7 @@ internal class SetConfirmationStateInProgressTransformer : Transformer<StakingUi
isPrimaryButtonEnabled = false,
innerState = InnerConfirmationStakingState.IN_PROGRESS,
validatorState = validatorState.copySealed(isClickable = false),
footerText = TextReference.EMPTY,
)
} else {
this

View file

@ -1,12 +1,22 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.utils.extensions.orZero
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
internal class SetConfirmationStateLoadingTransformer(
private val yield: Yield,
private val appCurrency: AppCurrency,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
@ -25,7 +35,7 @@ internal class SetConfirmationStateLoadingTransformer(
availableValidators = yield.validators,
),
notifications = persistentListOf(),
footerText = "",
footerText = getFooter(prevState),
transactionDoneState = TransactionDoneState.Empty,
pendingAction = possibleConfirmationState?.pendingAction,
isApprovalNeeded = false,
@ -33,4 +43,35 @@ internal class SetConfirmationStateLoadingTransformer(
),
)
}
private fun getFooter(state: StakingUiState): TextReference {
val amountState = state.amountState as? AmountState.Data
val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data
val validatorState = confirmationState?.validatorState as? ValidatorState.Content
val isEnterAction = state.actionType == StakingActionCommonType.ENTER
val apr = validatorState?.chosenValidator?.apr.orZero()
val amountDecimal = amountState?.amountTextField?.fiatAmount?.value
val potentialReward = amountDecimal?.multiply(apr)
val amountValue = BigDecimalFormatter.formatFiatAmount(
fiatAmount = amountDecimal,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
val potentialRewardValue = BigDecimalFormatter.formatFiatAmount(
fiatAmount = potentialReward,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
return if (isEnterAction && amountDecimal != null && potentialReward != null) {
resourceReference(
id = R.string.staking_summary_description_text,
formatArgs = wrappedList(amountValue, potentialRewardValue),
)
} else {
TextReference.EMPTY
}
}
}

View file

@ -211,7 +211,7 @@ internal class SetInitialDataStateTransformer(
feeState = FeeState.Loading,
validatorState = ValidatorState.Loading,
notifications = persistentListOf(),
footerText = "",
footerText = TextReference.EMPTY,
transactionDoneState = TransactionDoneState.Empty,
pendingAction = null,
isApprovalNeeded = isApprovalNeeded,

View file

@ -5,20 +5,16 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
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.runtime.remember
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.components.transactions.TransactionDoneTitle
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -71,22 +67,9 @@ internal fun StakingConfirmationContent(
}
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

View file

@ -36,6 +36,7 @@ import kotlinx.coroutines.flow.withIndex
@Composable
internal fun StakingScreen(uiState: StakingUiState) {
val snackbarHostState = remember { SnackbarHostState() }
val confirmationState = uiState.confirmationState as? StakingStates.ConfirmationState.Data
BackHandler(onBack = uiState.clickIntents::onPrevClick)
Column(
@ -55,6 +56,7 @@ internal fun StakingScreen(uiState: StakingUiState) {
)
NavigationButtonsBlock(
buttonState = uiState.buttonsState,
footerText = confirmationState?.footerText.takeIf { uiState.currentStep == StakingStep.Confirmation },
modifier = Modifier.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,

View file

@ -172,7 +172,7 @@ internal class StakingViewModel @Inject constructor(
}
stakingStateRouter.onNextClick()
when {
isInitState() -> stateController.update(SetConfirmationStateLoadingTransformer(yield))
isInitState() -> stateController.update(SetConfirmationStateLoadingTransformer(yield, appCurrency))
isAssentState() -> getFee(pendingAction)
}
}
@ -183,7 +183,7 @@ internal class StakingViewModel @Inject constructor(
override fun getFee(pendingAction: PendingAction?) {
viewModelScope.launch {
stateController.update(SetConfirmationStateLoadingTransformer(yield))
stateController.update(SetConfirmationStateLoadingTransformer(yield, appCurrency))
val cryptoCurrencyValue = cryptoCurrencyStatus.value
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
?: error("No confirmation state")