diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 84cc72c8e2..ee19fe0771 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 5ef91d4236..002bbbf669 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -88,6 +88,10 @@ Copy address Create Custom + + %d day + %d days + Delete Disabled Done 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