Updated on 2026-08-14

This commit is contained in:
Tangem 2024-07-16 17:59:37 +05:00
parent dfd7076c2e
commit 9c7a593e17
25 changed files with 201 additions and 97 deletions

View file

@ -86,6 +86,12 @@
<string name="common_copy_address">Скопировать адрес</string>
<string name="common_create">Создать</string>
<string name="common_custom">Свое</string>
<plurals name="common_days">
<item quantity="one">%d день</item>
<item quantity="few">%d дня</item>
<item quantity="many">%d дней</item>
<item quantity="other">%d дней</item>
</plurals>
<string name="common_delete">Удалить</string>
<string name="common_disabled">Отключено</string>
<string name="common_done">Готово</string>

View file

@ -39,6 +39,7 @@
<string name="biometric_lockout_warning_title">Too many attempts</string>
<string name="biometric_unavailable_warning">You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings.</string>
<string name="button_start_backup_process">Start backup process</string>
<string name="buy_token_description">From your fiat card or bank account</string>
<plurals name="card_label_card_count">
<item quantity="one">%d card</item>
<item quantity="other">%d cards</item>
@ -87,6 +88,10 @@
<string name="common_copy_address">Copy address</string>
<string name="common_create">Create</string>
<string name="common_custom">Custom</string>
<plurals name="common_days">
<item quantity="one">%d day</item>
<item quantity="other">%d days</item>
</plurals>
<string name="common_delete">Delete</string>
<string name="common_disabled">Disabled</string>
<string name="common_done">Done</string>
@ -257,6 +262,7 @@
<string name="express_token_list_empty_search">No tokens found. Please try another request</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">Transaction ID copied</string>
<string name="exсhange_token_description">From another currency in your wallet</string>
<string name="feedback_data_collection_message">The following information is optional. You can erase it if you don\'t want to share it.</string>
<string name="feedback_preface_rate_negative">Tell us what functions you are missing, and we will try to help you.</string>
<string name="feedback_preface_scan_failed">Please tell us what card do you have</string>
@ -329,10 +335,12 @@
<string name="markets_add_to_my_portfolio_description">To start buying, exchanging or receiving this asset, add this token to at least 1 network</string>
<string name="markets_add_to_my_portfolio_unavailable_description">This asset is not available</string>
<string name="markets_add_to_portfolio_button">Add to portfolio</string>
<string name="markets_add_token">Add token</string>
<string name="markets_available_networks">Available networks</string>
<string name="markets_common_my_portfolio">My portfolio</string>
<string name="markets_common_title">Market</string>
<string name="markets_generate_addresses_notification">To generate addresses for selected networks, you need to attach a Tangem card</string>
<string name="markets_quick_actions">Quick actions</string>
<string name="markets_select_network">Select network</string>
<string name="markets_select_wallet">Select wallet</string>
<string name="markets_selector_interval_1m_title">1m</string>
@ -348,6 +356,7 @@
<string name="markets_sort_by_top_gainers_title">Top Gainers</string>
<string name="markets_sort_by_top_losers_title">Top losers</string>
<string name="markets_sort_by_trending_title">Trending</string>
<string name="markets_token_details_about_token_title">About %s</string>
<plurals name="markets_token_details_based_on_ratings">
<item quantity="one">Based on %d rating</item>
<item quantity="other">Based on %d ratings</item>
@ -486,6 +495,7 @@
<string name="qr_scanner_camera_denied_title">Camera access denied</string>
<string name="receive_bottom_sheet_warning_message">%1$s (%2$s) on %3$s network</string>
<string name="receive_bottom_sheet_warning_message_full">Send only %s to this address. Sending any other currency will result in its irreversible loss.</string>
<string name="receive_token_description">Show QR-code or share your address</string>
<string name="referral_button_participate">Participate</string>
<string name="referral_error_failed_to_load_info">Failed to load the information about the referral program. Please try again later.</string>
<string name="referral_error_failed_to_load_info_with_reason">Failed to load the information about the referral program. Error code: %s. Please try again later.</string>

View file

@ -12,9 +12,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.GRAY_SCALE_ALPHA
import com.tangem.core.ui.utils.GrayscaleColorFilter
import com.tangem.core.ui.utils.NORMAL_ALPHA
import com.tangem.core.ui.utils.getGreyScaleColorFilter
/**
* Cryptocurrency icon with network badge
@ -76,11 +74,7 @@ private fun BoxScope.ContentIconContainer(
) {
val networkBadgeOffset = TangemTheme.dimens.spacing4
val (alpha, colorFilter) = remember(icon.isGrayscale) {
if (icon.isGrayscale) {
GRAY_SCALE_ALPHA to GrayscaleColorFilter
} else {
NORMAL_ALPHA to null
}
getGreyScaleColorFilter(icon.isGrayscale)
}
ContentIcon(

View file

@ -20,6 +20,7 @@ internal fun InputRowImageBase(
modifier: Modifier = Modifier,
subtitleColor: Color = TangemTheme.colors.text.primary1,
captionColor: Color = TangemTheme.colors.text.tertiary,
isGrayscaleImage: Boolean = false,
extraContent: @Composable RowScope.() -> Unit = {},
) {
Row(
@ -29,6 +30,7 @@ internal fun InputRowImageBase(
) {
InputRowAsyncImage(
imageUrl = imageUrl,
isGrayscale = isGrayscaleImage,
modifier = Modifier
.size(TangemTheme.dimens.spacing36),
)

View file

@ -32,6 +32,7 @@ import com.tangem.core.ui.res.TangemThemePreview
* @param imageUrl icon to load
* @param subtitleColor subtitle text color
* @param captionColor caption text color
* @param isGrayscaleImage whether to display grayscale image
*/
@Suppress("LongParameterList")
@Composable
@ -45,6 +46,7 @@ fun InputRowImageInfo(
title: TextReference? = null,
subtitleColor: Color = TangemTheme.colors.text.primary1,
captionColor: Color = TangemTheme.colors.text.tertiary,
isGrayscaleImage: Boolean = false,
) {
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6),
@ -64,6 +66,7 @@ fun InputRowImageInfo(
imageUrl = imageUrl,
subtitleColor = subtitleColor,
captionColor = captionColor,
isGrayscaleImage = isGrayscaleImage,
) {
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),

View file

@ -10,17 +10,22 @@ import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.currency.icon.LoadingIcon
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.getGreyScaleColorFilter
/**
* Loads image by url for icon in the input row
*
* @param imageUrl url of the image
* @param modifier modifier
* @param isGrayscale whether to apply grayscale filter
*/
@Composable
internal fun InputRowAsyncImage(imageUrl: String, modifier: Modifier = Modifier) {
internal fun InputRowAsyncImage(imageUrl: String, modifier: Modifier = Modifier, isGrayscale: Boolean = false) {
val (alpha, colorFilter) = getGreyScaleColorFilter(isGrayscale = isGrayscale)
SubcomposeAsyncImage(
modifier = modifier,
colorFilter = colorFilter,
alpha = alpha,
model = ImageRequest.Builder(context = LocalContext.current)
.data(imageUrl)
.crossfade(enable = true)

View file

@ -2,6 +2,7 @@ package com.tangem.core.ui.extensions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontWeight
@ -55,4 +56,8 @@ fun AnnotatedString.Builder.appendMarkdown(markdownText: String, node: ASTNode):
return this
}
fun AnnotatedString.Builder.appendSpace() = append(" ")
fun AnnotatedString.Builder.appendSpace() = append(" ")
fun AnnotatedString.Builder.appendColored(text: String, color: Color) = withStyle(SpanStyle(color = color)) {
append(text)
}

View file

@ -9,6 +9,7 @@ import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.AnnotatedString.Builder
import androidx.compose.ui.text.buildAnnotatedString
import org.intellij.markdown.MarkdownElementTypes
import kotlin.contracts.ExperimentalContracts
@ -100,6 +101,17 @@ fun annotatedReference(value: AnnotatedString): TextReference {
return TextReference.Annotated(value)
}
/**
* Creates a [TextReference] using an annotated string value.
*
* @param onAnnotated The annotated string builder.
* @return A [TextReference] representing the provided annotated string value.
*/
@Composable
inline fun annotatedReference(onAnnotated: Builder.() -> Unit): TextReference {
return TextReference.Annotated(buildAnnotatedString { onAnnotated() })
}
/**
* Creates a [TextReference] using a plural string resource ID with count and optional format arguments.
*

View file

@ -8,4 +8,13 @@ const val GRAY_SCALE_ALPHA = 0.4f
const val NORMAL_ALPHA = 1f
val GrayscaleColorFilter: ColorFilter
get() = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(GRAY_SCALE_SATURATION) })
get() = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(GRAY_SCALE_SATURATION) })
/**
* Returns alpha and color filter for grayscale if [isGrayscale] is true
*/
fun getGreyScaleColorFilter(isGrayscale: Boolean): Pair<Float, ColorFilter?> = if (isGrayscale) {
GRAY_SCALE_ALPHA to GrayscaleColorFilter
} else {
NORMAL_ALPHA to null
}

View file

@ -9,7 +9,7 @@ import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM
import com.tangem.features.details.impl.R
import com.tangem.utils.Strings.STARS
import com.tangem.utils.StringsSigns.STARS
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList

View file

@ -3,6 +3,7 @@ package com.tangem.features.staking.impl.presentation.state
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.staking.model.Yield
import kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal
sealed class InnerYieldBalanceState {
data class Data(
@ -19,14 +20,17 @@ data class BalanceGroupedState(
val items: ImmutableList<BalanceState>,
val footer: TextReference?,
val title: TextReference,
val type: BalanceGroupType,
)
data class BalanceState(
val validator: Yield.Validator,
val cryptoValue: String,
val cryptoDecimal: BigDecimal,
val cryptoAmount: TextReference,
val fiatAmount: TextReference,
val rawCurrencyId: String?,
val unbondingPeriod: TextReference,
)
enum class BalanceGroupType {

View file

@ -44,6 +44,7 @@ internal class StakingStateController @Inject constructor() {
isBalanceHidden = false,
event = consumedEvent(),
bottomSheetConfig = null,
routeType = RouteType.STAKE,
)
}
}

View file

@ -13,8 +13,12 @@ internal class StakingStateRouter(
}
fun onNextClick() {
when (stateController.uiState.value.currentStep) {
StakingStep.InitialInfo -> showAmount()
when (stateController.value.currentStep) {
StakingStep.InitialInfo -> when (stateController.value.routeType) {
RouteType.STAKE -> showAmount()
RouteType.UNSTAKE -> showConfirmation()
RouteType.CLAIM -> showRewardsValidators()
}
StakingStep.RewardsValidators,
StakingStep.Validators,
StakingStep.Amount,

View file

@ -23,6 +23,7 @@ internal data class StakingUiState(
val confirmationState: StakingStates.ConfirmationState,
val isBalanceHidden: Boolean,
val bottomSheetConfig: TangemBottomSheetConfig?,
val routeType: RouteType,
val event: StateEvent<StakingEvent>,
) {
@ -55,6 +56,7 @@ internal sealed class StakingStates {
val rewardSchedule: String,
val onInfoClick: (InfoType) -> Unit,
val yieldBalance: InnerYieldBalanceState,
val isStakeMoreAvailable: Boolean,
) : InitialInfoState()
data class Empty(
@ -98,4 +100,10 @@ enum class StakingStep {
Amount,
Confirmation,
Validators,
}
enum class RouteType {
STAKE,
UNSTAKE,
CLAIM,
}

View file

@ -1,7 +1,6 @@
package com.tangem.features.staking.impl.presentation.state.converters
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
@ -10,12 +9,12 @@ import com.tangem.domain.staking.model.BalanceType
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.staking.model.YieldBalance
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.BalanceState
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.utils.Provider
import com.tangem.utils.StringsSigns.PLUS
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.addOrReplace
import kotlinx.collections.immutable.toPersistentList
import java.math.BigDecimal
@ -33,13 +32,6 @@ internal class RewardsValidatorStateConverter(
StakingStates.RewardsValidatorsState.Data(
isPrimaryButtonEnabled = true,
rewards = balances
// todo remove when real data is available
.addOrReplace(
item = balances.first().copy(
type = BalanceType.REWARDS,
),
predicate = { true },
)
.filter { it.type == BalanceType.REWARDS }
.mapRewardBalances(cryptoCurrencyStatus)
.toPersistentList(),
@ -56,11 +48,16 @@ internal class RewardsValidatorStateConverter(
}
val cryptoValue = balance.amount.times(balance.pricePerShare)
val fiatValue = cryptoCurrencyStatus.value.fiatRate?.times(cryptoValue)
val unbondingPeriod = yield.metadata.cooldownPeriod.days
validator?.toBalanceState(
cryptoCurrencyStatus = cryptoCurrencyStatus,
cryptoValue = cryptoValue,
fiatValue = fiatValue,
unbondingPeriod = pluralReference(
id = R.plurals.common_days,
count = unbondingPeriod,
formatArgs = wrappedList(unbondingPeriod),
),
)
}
@ -68,6 +65,7 @@ internal class RewardsValidatorStateConverter(
cryptoCurrencyStatus: CryptoCurrencyStatus,
cryptoValue: BigDecimal,
fiatValue: BigDecimal?,
unbondingPeriod: TextReference,
): BalanceState {
val appCurrency = appCurrencyProvider()
val cryptoCurrency = cryptoCurrencyStatus.currency
@ -92,9 +90,11 @@ internal class RewardsValidatorStateConverter(
return BalanceState(
validator = this,
cryptoValue = cryptoValue.parseBigDecimal(cryptoCurrency.decimals),
cryptoDecimal = cryptoValue,
cryptoAmount = cryptoAmount,
fiatAmount = fiatAmount,
rawCurrencyId = cryptoCurrency.id.rawCurrencyId,
unbondingPeriod = unbondingPeriod,
)
}
}

View file

@ -1,7 +1,9 @@
package com.tangem.features.staking.impl.presentation.state.converters
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
@ -14,7 +16,6 @@ import com.tangem.features.staking.impl.presentation.state.BalanceState
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.addOrReplace
import com.tangem.utils.isNullOrZero
import kotlinx.collections.immutable.toPersistentList
@ -54,13 +55,7 @@ internal class YieldBalancesConverter(
}
private fun getGroupedBalance(balance: YieldBalanceItem) = balance.items
// todo remove when real data is available
.addOrReplace(
item = balance.items.first().copy(
type = BalanceType.REWARDS,
),
predicate = { true },
)
.sortedBy { it.type }
.groupBy { it.type.toGroup() }
.mapNotNull { item ->
val (title, footer) = getGroupTitle(item.key)
@ -69,6 +64,7 @@ internal class YieldBalancesConverter(
items = item.value.mapBalances().toPersistentList(),
footer = footer,
title = it,
type = item.key,
)
}
}
@ -83,11 +79,12 @@ internal class YieldBalancesConverter(
}
val cryptoAmount = balance.amount * balance.pricePerShare
val fiatAmount = cryptoCurrencyStatus.value.fiatRate?.times(cryptoAmount)
val unbondingPeriod = yield.metadata.cooldownPeriod.days
validator?.let {
BalanceState(
validator = validator,
cryptoValue = cryptoAmount.parseBigDecimal(cryptoCurrency.decimals),
cryptoDecimal = cryptoAmount,
cryptoAmount = stringReference(
BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = cryptoAmount,
@ -102,6 +99,11 @@ internal class YieldBalancesConverter(
),
),
rawCurrencyId = balance.rawCurrencyId,
unbondingPeriod = pluralReference(
id = R.plurals.common_days,
count = unbondingPeriod,
formatArgs = wrappedList(unbondingPeriod),
),
)
}
}

View file

@ -2,10 +2,7 @@ package com.tangem.features.staking.impl.presentation.state.previewdata
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.staking.model.Yield
import com.tangem.features.staking.impl.presentation.state.BalanceGroupedState
import com.tangem.features.staking.impl.presentation.state.BalanceState
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.*
import kotlinx.collections.immutable.persistentListOf
internal object InitialStakingStatePreview {
@ -21,6 +18,7 @@ internal object InitialStakingStatePreview {
rewardSchedule = "Block",
onInfoClick = {},
yieldBalance = InnerYieldBalanceState.Empty,
isStakeMoreAvailable = true,
)
val stateWithYield = defaultState.copy(
@ -32,10 +30,12 @@ internal object InitialStakingStatePreview {
BalanceGroupedState(
title = stringReference("Staked"),
footer = null,
type = BalanceGroupType.ACTIVE,
items = persistentListOf(
BalanceState(
cryptoValue = "100",
cryptoAmount = stringReference("100 SOL"),
cryptoDecimal = "100".toBigDecimal(),
fiatAmount = stringReference("100 $"),
rawCurrencyId = null,
validator = Yield.Validator(
@ -50,6 +50,7 @@ internal object InitialStakingStatePreview {
votingPower = null,
preferred = false,
),
unbondingPeriod = stringReference("3 days"),
),
),
),

View file

@ -31,6 +31,7 @@ import java.math.BigDecimal
internal class SetInitialDataStateTransformer(
private val clickIntents: StakingClickIntents,
private val yield: Yield,
private val isStakeMoreAvailable: Boolean,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val userWalletProvider: Provider<UserWallet>,
private val appCurrencyProvider: Provider<AppCurrency>,
@ -92,6 +93,7 @@ internal class SetInitialDataStateTransformer(
rewardSchedule = yield.metadata.rewardSchedule,
onInfoClick = clickIntents::onInfoClick,
yieldBalance = yieldBalancesConverter.convert(Unit),
isStakeMoreAvailable = isStakeMoreAvailable,
)
}

View file

@ -18,21 +18,23 @@ 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
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.RouteType
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.TransactionDoneState
import com.tangem.features.staking.impl.presentation.state.previewdata.ConfirmationStatePreviewData
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
import com.tangem.features.staking.impl.presentation.ui.block.NotificationsBlock
import com.tangem.features.staking.impl.presentation.ui.block.StakingFeeBlock
import com.tangem.features.staking.impl.presentation.ui.block.ValidatorBlock
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.TransactionDoneState
@Composable
internal fun StakingConfirmationContent(
amountState: AmountState,
state: StakingStates.ConfirmationState,
clickIntents: StakingClickIntents,
type: RouteType,
) {
if (state !is StakingStates.ConfirmationState.Data) return
@ -60,7 +62,9 @@ internal fun StakingConfirmationContent(
isEditingDisabled = true,
onClick = {},
)
ValidatorBlock(validatorState = state.validatorState, onClick = clickIntents::openValidators)
if (type == RouteType.STAKE) {
ValidatorBlock(validatorState = state.validatorState, onClick = clickIntents::openValidators)
}
StakingFeeBlock(feeState = state.feeState)
NotificationsBlock(notifications = state.notifications)
SpacerHMax()
@ -89,6 +93,7 @@ private fun Preview_StakingConfirmationContent() {
amountState = AmountStatePreviewData.amountState,
state = ConfirmationStatePreviewData.assentStakingState,
clickIntents = StakingClickIntentsStub,
type = RouteType.STAKE,
)
}
}

View file

@ -21,7 +21,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -35,9 +34,7 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.BalanceGroupedState
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
import com.tangem.features.staking.impl.presentation.state.StakingStates
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
@ -77,7 +74,7 @@ internal fun StakingInitialInfoContent(state: StakingStates.InitialInfoState, cl
}
AnimatedContent(targetState = state.yieldBalance, label = "Rewards block visibility animation") {
if (it is InnerYieldBalanceState.Data) {
ActiveStakingBlock(it.balance)
ActiveStakingBlock(it.balance, clickIntents::onActiveStake)
}
}
}
@ -204,17 +201,15 @@ private fun StakingRewardBlock(
onRewardsClick: () -> Unit,
) {
val (text, textColor) = if (isRewardsToClaim) {
annotatedReference(
buildAnnotatedString {
append(PLUS)
appendSpace()
append(rewardFiat)
appendSpace()
append(DOT)
appendSpace()
append(rewardCrypto)
},
) to TangemTheme.colors.text.primary1
annotatedReference {
append(PLUS)
appendSpace()
append(rewardFiat)
appendSpace()
append(DOT)
appendSpace()
append(rewardCrypto)
} to TangemTheme.colors.text.primary1
} else {
resourceReference(R.string.staking_details_no_rewards_to_claim) to TangemTheme.colors.text.tertiary
}
@ -235,7 +230,7 @@ private fun StakingRewardBlock(
}
@Composable
private fun ActiveStakingBlock(groups: List<BalanceGroupedState>) {
private fun ActiveStakingBlock(groups: List<BalanceGroupedState>, onClick: (BalanceState) -> Unit) {
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
@ -253,15 +248,43 @@ private fun ActiveStakingBlock(groups: List<BalanceGroupedState>) {
) {
group.items.forEachIndexed { index, balance ->
key(balance.validator.address) {
val caption = combinedReference(
if (group.type == BalanceGroupType.UNSTAKED) {
resourceReference(R.string.staking_details_unbonding_period)
annotatedReference {
appendSpace()
appendColored(
text = balance.unbondingPeriod.resolveReference(),
color = TangemTheme.colors.text.accent,
)
}
} else {
resourceReference(R.string.app_name)
annotatedReference {
appendSpace()
appendColored(
text = BigDecimalFormatter.formatPercent(
percent = balance.validator.apr.orZero(),
useAbsoluteValue = true,
),
color = TangemTheme.colors.text.accent,
)
}
},
)
InputRowImageInfo(
title = group.title.takeIf { index == 0 },
subtitle = stringReference(balance.validator.name),
caption = stringReference(
BigDecimalFormatter.formatPercent(balance.validator.apr.orZero(), true),
),
caption = caption,
isGrayscaleImage = group.type == BalanceGroupType.UNSTAKED,
infoTitle = balance.fiatAmount,
infoSubtitle = balance.cryptoAmount,
imageUrl = balance.validator.image.orEmpty(),
modifier = Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
onClick = { onClick(balance) },
),
)
}
}

View file

@ -126,7 +126,12 @@ private fun getButtonData(currentState: StakingUiState): Pair<Int, (() -> Unit)?
StakingStep.InitialInfo -> {
val initialState = currentState.initialInfoState as? StakingStates.InitialInfoState.Data
if (initialState?.yieldBalance is InnerYieldBalanceState.Data) {
R.string.staking_stake_more to currentState.clickIntents::onNextClick
val click = if (initialState.isStakeMoreAvailable) {
currentState.clickIntents::onNextClick
} else {
null
}
R.string.staking_stake_more to click
} else {
R.string.common_next to currentState.clickIntents::onNextClick
}

View file

@ -155,6 +155,7 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M
amountState = uiState.amountState,
state = uiState.confirmationState,
clickIntents = uiState.clickIntents,
type = uiState.routeType,
)
StakingStep.Validators -> {
val confirmState = uiState.confirmationState

View file

@ -14,10 +14,7 @@ import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.staking.GetStakingTransactionUseCase
import com.tangem.domain.staking.EstimateGasUseCase
import com.tangem.domain.staking.SaveUnsubmittedHashUseCase
import com.tangem.domain.staking.SubmitHashUseCase
import com.tangem.domain.staking.*
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.staking.model.action.StakingActionCommonType
import com.tangem.domain.staking.model.transaction.ActionParams
@ -32,10 +29,6 @@ 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.*
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.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
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
@ -66,6 +59,7 @@ internal class StakingViewModel @Inject constructor(
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase,
private val submitHashUseCase: SubmitHashUseCase,
private val isStakeMoreAvailableUseCase: IsStakeMoreAvailableUseCase,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
@ -117,15 +111,26 @@ internal class StakingViewModel @Inject constructor(
viewModelScope.launch {
stateController.update(SetConfirmationStateInProgressTransformer())
val confirmationState =
value.confirmationState as? StakingStates.ConfirmationState.Data ?: error("No confirmation state")
val validatorState = confirmationState.validatorState as? ValidatorState.Content
?: error("No validator provided")
val actionType = when (value.routeType) {
RouteType.STAKE -> StakingActionCommonType.ENTER
RouteType.UNSTAKE -> StakingActionCommonType.EXIT
RouteType.CLAIM -> TODO()
}
val stakingTransaction = getStakingTransactionUseCase(
params = ActionParams(
actionCommonType = StakingActionCommonType.ENTER,
actionCommonType = actionType,
integrationId = yield.id,
amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
?: error("No amount provided"),
address = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
?: error("No available address"),
validatorAddress = yield.validators.getOrNull(0)?.address ?: error("No available validator"),
validatorAddress = validatorState.chosenValidator.address,
token = yield.token,
),
).getOrElse {
@ -151,9 +156,15 @@ internal class StakingViewModel @Inject constructor(
),
)
val actionType = when (value.routeType) {
RouteType.STAKE -> StakingActionCommonType.ENTER
RouteType.UNSTAKE -> StakingActionCommonType.EXIT
RouteType.CLAIM -> TODO()
}
val stakingGasEstimate = estimateGasUseCase(
params = ActionParams(
actionCommonType = StakingActionCommonType.ENTER,
actionCommonType = actionType,
integrationId = yield.id,
amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
?: error("No amount provided"),
@ -209,16 +220,19 @@ internal class StakingViewModel @Inject constructor(
}
override fun openRewardsValidators() {
stakingStateRouter.showRewardsValidators()
stateController.update { it.copy(routeType = RouteType.CLAIM) }
onNextClick()
}
override fun selectRewardValidator(rewardValue: String) {
stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, rewardValue))
stakingStateRouter.onNextClick()
onNextClick()
}
override fun onActiveStake(activeStake: BalanceState) {
// todo unstake
stateController.update { it.copy(routeType = RouteType.UNSTAKE) }
stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, activeStake.cryptoValue))
onNextClick()
}
override fun onExploreClick() {
@ -253,10 +267,14 @@ internal class StakingViewModel @Inject constructor(
getCryptoCurrencyStatusSyncUseCase(userWalletId, cryptoCurrencyId).fold(
ifRight = {
cryptoCurrencyStatus = it
val networkId = cryptoCurrencyStatus.currency.network.id
val isStakeMoreAvailable = isStakeMoreAvailableUseCase(networkId)
stateController.update(
transformer = SetInitialDataStateTransformer(
clickIntents = this@StakingViewModel,
yield = yield,
isStakeMoreAvailable = isStakeMoreAvailable.getOrElse { false },
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
userWalletProvider = Provider { userWallet },
appCurrencyProvider = Provider { appCurrency },

View file

@ -18,9 +18,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.GRAY_SCALE_ALPHA
import com.tangem.core.ui.utils.GrayscaleColorFilter
import com.tangem.core.ui.utils.NORMAL_ALPHA
import com.tangem.core.ui.utils.getGreyScaleColorFilter
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfoBlockState
import com.tangem.features.tokendetails.impl.R
@ -46,11 +44,7 @@ internal fun TokenInfoBlock(state: TokenInfoBlockState, modifier: Modifier = Mod
}
val (alpha, colorFilter) = remember(state.iconState.isGrayscale) {
if (state.iconState.isGrayscale) {
GRAY_SCALE_ALPHA to GrayscaleColorFilter
} else {
NORMAL_ALPHA to null
}
getGreyScaleColorFilter(state.iconState.isGrayscale)
}
CurrencyIcon(
modifier = Modifier

View file

@ -19,9 +19,7 @@ import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.GRAY_SCALE_ALPHA
import com.tangem.core.ui.utils.GrayscaleColorFilter
import com.tangem.core.ui.utils.NORMAL_ALPHA
import com.tangem.core.ui.utils.getGreyScaleColorFilter
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingAvailableBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingErrorBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingLoadingBlock
@ -74,11 +72,7 @@ private fun StakingAvailableContent(state: StakingBlockUM.StakeAvailable, modifi
) {
Row {
val (alpha, colorFilter) = remember(state.iconState.isGrayscale) {
if (state.iconState.isGrayscale) {
GRAY_SCALE_ALPHA to GrayscaleColorFilter
} else {
NORMAL_ALPHA to null
}
getGreyScaleColorFilter(state.iconState.isGrayscale)
}
CurrencyIcon(
modifier = Modifier
@ -138,11 +132,7 @@ private fun StakingLoading(iconState: IconState, modifier: Modifier = Modifier)
) {
Row {
val (alpha, colorFilter) = remember(iconState.isGrayscale) {
if (iconState.isGrayscale) {
GRAY_SCALE_ALPHA to GrayscaleColorFilter
} else {
NORMAL_ALPHA to null
}
getGreyScaleColorFilter(iconState.isGrayscale)
}
CurrencyIcon(
modifier = Modifier