From 9c7a593e179e4371a3bfe622269f6401caf822ed Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Jul 2024 17:59:37 +0500 Subject: [PATCH] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 6 ++ core/res/src/main/res/values/strings.xml | 10 +++ .../components/currency/icon/CurrencyIcon.kt | 10 +-- .../components/inputrow/InputRowImageBase.kt | 2 + .../components/inputrow/InputRowImageInfo.kt | 3 + .../inputrow/inner/InputRowAsyncImage.kt | 7 ++- .../core/ui/extensions/MarkdownExtension.kt | 7 ++- .../core/ui/extensions/TextReference.kt | 12 ++++ .../tangem/core/ui/utils/GrayscaleUtils.kt | 11 +++- .../details/utils/UserWalletMappers.kt | 2 +- .../state/InnerYieldBalanceState.kt | 4 ++ .../state/StakingStateController.kt | 1 + .../presentation/state/StakingStateRouter.kt | 8 ++- .../impl/presentation/state/StakingUiState.kt | 8 +++ .../RewardsValidatorStateConverter.kt | 22 +++---- .../converters/YieldBalancesConverter.kt | 20 +++--- .../previewdata/InitialStakingStatePreview.kt | 9 +-- .../SetInitialDataStateTransformer.kt | 2 + .../ui/StakingConfirmationContent.kt | 11 +++- .../ui/StakingInitialInfoContent.kt | 63 +++++++++++++------ .../ui/StakingNavigationButtons.kt | 7 ++- .../impl/presentation/ui/StakingScreen.kt | 1 + .../viewmodel/StakingViewModel.kt | 46 +++++++++----- .../ui/components/TokenInfoBlock.kt | 10 +-- .../components/staking/TokenStakingBlock.kt | 16 +---- 25 files changed, 201 insertions(+), 97 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 6cb64bafa2..1eeeaec28d 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -86,6 +86,12 @@ Скопировать адрес Создать Свое + + %d день + %d дня + %d дней + %d дней + Удалить Отключено Готово diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 6b23058989..8a4d5d07db 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -39,6 +39,7 @@ Too many attempts 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. Start backup process + From your fiat card or bank account %d card %d cards @@ -87,6 +88,10 @@ Copy address Create Custom + + %d day + %d days + Delete Disabled Done @@ -257,6 +262,7 @@ No tokens found. Please try another request ID: %s Transaction ID copied + From another currency in your wallet The following information is optional. You can erase it if you don\'t want to share it. Tell us what functions you are missing, and we will try to help you. Please tell us what card do you have @@ -329,10 +335,12 @@ To start buying, exchanging or receiving this asset, add this token to at least 1 network This asset is not available Add to portfolio + Add token Available networks My portfolio Market To generate addresses for selected networks, you need to attach a Tangem card + Quick actions Select network Select wallet 1m @@ -348,6 +356,7 @@ Top Gainers Top losers Trending + About %s Based on %d rating Based on %d ratings @@ -486,6 +495,7 @@ Camera access denied %1$s (%2$s) on %3$s network Send only %s to this address. Sending any other currency will result in its irreversible loss. + Show QR-code or share your address Participate Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Error code: %s. Please try again later. diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/CurrencyIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/CurrencyIcon.kt index ce67d13f6f..d06e59fc30 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/CurrencyIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/CurrencyIcon.kt @@ -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( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt index 00213b0077..417a7edbec 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt @@ -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), ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt index c4fb398c6e..f9811bbe1a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt @@ -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), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/InputRowAsyncImage.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/InputRowAsyncImage.kt index 3fd1ae7722..b85e91ba30 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/InputRowAsyncImage.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/InputRowAsyncImage.kt @@ -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) diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt index b8b11c058f..c510835242 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt @@ -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(" ") \ No newline at end of file +fun AnnotatedString.Builder.appendSpace() = append(" ") + +fun AnnotatedString.Builder.appendColored(text: String, color: Color) = withStyle(SpanStyle(color = color)) { + append(text) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt index 386f69b66b..4aab281036 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt @@ -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. * diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/GrayscaleUtils.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/GrayscaleUtils.kt index f3edb75b60..a71cf3875a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/GrayscaleUtils.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/GrayscaleUtils.kt @@ -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) }) \ No newline at end of file + 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 = if (isGrayscale) { + GRAY_SCALE_ALPHA to GrayscaleColorFilter +} else { + NORMAL_ALPHA to null +} \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt index 94fc6e00b1..eab5ed50e3 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt @@ -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 diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt index 61015e2357..4d5cfcff8a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt @@ -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, 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 { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt index f263b18a14..d3570e6c23 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt @@ -44,6 +44,7 @@ internal class StakingStateController @Inject constructor() { isBalanceHidden = false, event = consumedEvent(), bottomSheetConfig = null, + routeType = RouteType.STAKE, ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt index d3edfd4322..2c0b98f544 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt @@ -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, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index cd1e65b946..4a1be94850 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -23,6 +23,7 @@ internal data class StakingUiState( val confirmationState: StakingStates.ConfirmationState, val isBalanceHidden: Boolean, val bottomSheetConfig: TangemBottomSheetConfig?, + val routeType: RouteType, val event: StateEvent, ) { @@ -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, } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt index 169be97410..e05f2dfcdf 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt @@ -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, ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index 28ede76079..f6d436f3c3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -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), + ), ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt index 4eadd4916a..2c9ac37b88 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt @@ -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"), ), ), ), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index 3cc9baa334..c001927cd1 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -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, private val userWalletProvider: Provider, private val appCurrencyProvider: Provider, @@ -92,6 +93,7 @@ internal class SetInitialDataStateTransformer( rewardSchedule = yield.metadata.rewardSchedule, onInfoClick = clickIntents::onInfoClick, yieldBalance = yieldBalancesConverter.convert(Unit), + isStakeMoreAvailable = isStakeMoreAvailable, ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt index 98f42fccf2..261d4437cf 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt @@ -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, ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 44b3566f06..4eb8244ef3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -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) { +private fun ActiveStakingBlock(groups: List, onClick: (BalanceState) -> Unit) { Column( verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { @@ -253,15 +248,43 @@ private fun ActiveStakingBlock(groups: List) { ) { 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) }, + ), ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingNavigationButtons.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingNavigationButtons.kt index 73e2c6e526..41b426c018 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingNavigationButtons.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingNavigationButtons.kt @@ -126,7 +126,12 @@ private fun getButtonData(currentState: StakingUiState): Pair 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 } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt index e1b08be7ac..c26935b80c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt @@ -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 diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 157dfbebf4..c2b477f54b 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -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 }, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt index 773dc2868f..a6d81965bb 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt @@ -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 diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt index afa3fc743d..c2d7940cbc 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt @@ -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