Updated on 2026-08-14
This commit is contained in:
parent
dfd7076c2e
commit
9c7a593e17
25 changed files with 201 additions and 97 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ internal class StakingStateController @Inject constructor() {
|
|||
isBalanceHidden = false,
|
||||
event = consumedEvent(),
|
||||
bottomSheetConfig = null,
|
||||
routeType = RouteType.STAKE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue