Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-13 15:57:36 +03:00
parent 434c459728
commit 42f276c56f
25 changed files with 308 additions and 157 deletions

View file

@ -3,9 +3,11 @@ package com.tangem.datasource.api.common.adapter
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapters.EnumJsonAdapter
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO.BalanceTypeDTO
import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.MetadataDTO.RewardClaimingDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.MetadataDTO.RewardScheduleDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.RewardTypeDTO
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionStatusDTO
@ -24,13 +26,15 @@ object UnknownEnumMoshiAdapter {
fun Moshi.Builder.addStakeKitEnumFallbackAdapters(): Moshi.Builder {
val map = mapOf(
BalanceTypeDTO::class.java to BalanceTypeDTO.UNKNOWN,
NetworkTypeDTO::class.java to NetworkTypeDTO.UNKNOWN,
StakingActionTypeDTO::class.java to StakingActionTypeDTO.UNKNOWN,
YieldDTO.RewardTypeDTO::class.java to YieldDTO.RewardTypeDTO.UNKNOWN,
BalanceDTO.BalanceType::class.java to BalanceDTO.BalanceType.UNKNOWN,
StakingTransactionTypeDTO::class.java to StakingTransactionTypeDTO.UNKNOWN,
StakingTransactionStatusDTO::class.java to StakingTransactionStatusDTO.UNKNOWN,
RewardClaimingDTO::class.java to RewardClaimingDTO.UNKNOWN,
RewardScheduleDTO::class.java to RewardScheduleDTO.UNKNOWN,
RewardTypeDTO::class.java to RewardTypeDTO.UNKNOWN,
StakingActionStatusDTO::class.java to StakingActionStatusDTO.UNKNOWN,
StakingActionTypeDTO::class.java to StakingActionTypeDTO.UNKNOWN,
StakingTransactionStatusDTO::class.java to StakingTransactionStatusDTO.UNKNOWN,
StakingTransactionTypeDTO::class.java to StakingTransactionTypeDTO.UNKNOWN,
)
return apply {

View file

@ -19,7 +19,7 @@ data class BalanceDTO(
@Json(name = "groupId")
val groupId: String,
@Json(name = "type")
val type: BalanceType,
val type: BalanceTypeDTO,
@Json(name = "amount")
val amount: BigDecimal,
@Json(name = "date")
@ -38,7 +38,7 @@ data class BalanceDTO(
val providerId: String?,
) {
enum class BalanceType {
enum class BalanceTypeDTO {
@Json(name = "available")
AVAILABLE,

View file

@ -105,13 +105,13 @@ data class YieldDTO(
@Json(name = "type")
val type: String,
@Json(name = "rewardSchedule")
val rewardSchedule: String,
val rewardSchedule: RewardScheduleDTO,
@Json(name = "cooldownPeriod")
val cooldownPeriod: PeriodDTO,
@Json(name = "warmupPeriod")
val warmupPeriod: PeriodDTO,
@Json(name = "rewardClaiming")
val rewardClaiming: String,
val rewardClaiming: RewardClaimingDTO,
@Json(name = "defaultValidator")
val defaultValidator: String?,
@Json(name = "minimumStake")
@ -135,6 +135,41 @@ data class YieldDTO(
@Json(name = "enabled")
val enabled: Boolean,
)
enum class RewardScheduleDTO {
@Json(name = "block")
BLOCK,
@Json(name = "week")
WEEK,
@Json(name = "hour")
HOUR,
@Json(name = "day")
DAY,
@Json(name = "month")
MONTH,
@Json(name = "era")
ERA,
@Json(name = "epoch")
EPOCH,
UNKNOWN,
}
enum class RewardClaimingDTO {
@Json(name = "auto")
AUTO,
@Json(name = "manual")
MANUAL,
UNKNOWN,
}
}
enum class RewardTypeDTO {

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="add_custom_token_choose_network">Netzwerk wählen</string>
<string name="add_custom_token_title">Token anlegen</string>
<string name="add_custom_token_title">Token hinzuf.</string>
<string name="add_tokens_title">Token verwalten</string>
<string name="address_qr_code_message_format">Sende nur %1$s ( %2$s ) vom %3$s -Netzwerk an diese Adresse. Die Verwendung anderer Token und Netzwerke kann zum Verlust von Geldern führen.</string>
<string name="alert_button_how_to_scan">So scannt man</string>
@ -88,7 +88,6 @@
<string name="common_copy">Kopieren</string>
<string name="common_copy_address">Adresse kopieren</string>
<string name="common_create">Erstellen</string>
<string name="common_crypto_fiat_format">%1$s (%2$s)</string>
<string name="common_custom">Eigen</string>
<plurals name="common_days">
<item quantity="one">%d tag</item>
@ -280,7 +279,7 @@
<string name="feedback_subject_support">Rückmeldung</string>
<string name="feedback_subject_support_tangem">Feedback zu Tangem</string>
<string name="feedback_subject_tx_failed">Eine Transaktion kann nicht gesendet werden</string>
<string name="give_permission_current_transaction">Nur diese</string>
<string name="give_permission_current_transaction">diese Transaktion</string>
<string name="give_permission_fee_footer">Das Netzwerk erhebt eine Token-Genehmigungsgebühr, um zu überprüfen, ob Sie die Verwendung Ihres Tokens für den Swap genehmigen.</string>
<string name="give_permission_policy_type_footer">Gib das Genehmigungslimit für das ausgewählte Token an</string>
<string name="give_permission_rows_amount">Betrag %s</string>
@ -448,7 +447,7 @@
<string name="onboarding_exit_alert_title">Möchtest du den Aktivierungsprozess abbrechen?</string>
<string name="onboarding_getting_started">Erste Schritte</string>
<string name="onboarding_linking_error_card_with_wallets">Für die Karte, die du hinzufügen möchtest, wurde bereits eine andere Wallets erstellt. Wenn du Guthaben auf dieser Wallets hast, hebe es bitte ab, setze diese Karte zurück und füge sie als Backup hinzu.</string>
<string name="onboarding_navbar_title_creating_backup">Backups anlegen</string>
<string name="onboarding_navbar_title_creating_backup">Backups erstellen</string>
<string name="onboarding_seed_button_read_more">Lese mehr über die Seed-Phrase</string>
<plurals name="onboarding_seed_generate_message_words_count">
<item quantity="one">leer</item>
@ -501,7 +500,7 @@
<string name="organize_tokens_group">Gruppe erstellen</string>
<string name="organize_tokens_sort_by_balance">Nach Guthaben</string>
<string name="organize_tokens_title">Token organisieren</string>
<string name="organize_tokens_ungroup">Gruppe löschen</string>
<string name="organize_tokens_ungroup">Grup. entf.</string>
<string name="qr_scanner_camera_denied_gallery_button">Wählen aus der Galerie aus</string>
<string name="qr_scanner_camera_denied_settings_button">Einstellungen</string>
<string name="qr_scanner_camera_denied_text">Du hast keinen Zugriff auf deine Kamera gewährt</string>
@ -640,6 +639,7 @@
<string name="send_transaction_success">Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert</string>
<string name="send_tron_account_activation_error">%1$s ist ein Vermögenswert im Tron-Netzwerk. Um die Gebühr zu berechnen und eine Transaktion durchzuführen, musst du etwas Tron (TRX) auf deinem Konto einzahlen.</string>
<string name="send_validation_invalid_address">Ungültige Adresse</string>
<string name="send_wallet_balance_format">%1$s (%2$s)</string>
<string name="sent_transaction_sent_title">Transaktion gesendet</string>
<string name="settings_card_settings_footer">Bereite das Scannen der Karte vor, die du einrichten möchtest.</string>
<string name="settings_forget_wallet">Entferne diese Wallet</string>
@ -648,7 +648,7 @@
<string name="staking_active">Aktiv</string>
<string name="staking_active_footer">Um deine Kryptos zu unstaken, klick hier.</string>
<string name="staking_amount_requirement_error">Die Anzahl der zu stakenden Krypros muss mindesten %s betragen</string>
<string name="staking_claim_unstaked">Nicht gestakte beanspruche</string>
<string name="staking_claim_unstaked">nicht gestakte beanspruche</string>
<string name="staking_details_annual_percentage_rate">Jährliche prozentuale Rendite</string>
<string name="staking_details_annual_percentage_rate_info">Die jährliche prozentuale Rendite, die du durch die Teilnahme am Staking erzielen kannst.</string>
<string name="staking_details_apr">Effektiver Jahreszins</string>
@ -672,10 +672,6 @@
<string name="staking_details_warmup_period_info">Die zugewiesene Zeit für die Aktivierung der Teilnahme am Staking.</string>
<string name="staking_migrate">Migrieren</string>
<string name="staking_native">Natives Staking</string>
<string name="staking_notification_earn_rewards_text_period_day">Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jeden tag.</string>
<string name="staking_notification_earn_rewards_text_period_hour">Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jede Stunde.</string>
<string name="staking_notification_earn_rewards_text_period_month">Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jeden Monat.</string>
<string name="staking_notification_earn_rewards_text_period_week">Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jede Woche.</string>
<string name="staking_notification_earn_rewards_title">Verdiene Staking-Belohnungen</string>
<string name="staking_notification_unstake_text">Die Belohnungen werden sofort nach dem unstaken gestoppt. Der unstakingprozess dauert %s.</string>
<string name="staking_rebond">Erneut binden</string>
@ -683,8 +679,8 @@
<string name="staking_restake_rewards">Belohnungen erneut staken</string>
<string name="staking_revoke">Widerrufen</string>
<string name="staking_revote">Neuwahl</string>
<string name="staking_reward_claiming_auto">Automatisch</string>
<string name="staking_reward_claiming_manual">Händisch</string>
<string name="staking_reward_claiming_auto">automatisch</string>
<string name="staking_reward_claiming_manual">händisch</string>
<string name="staking_reward_schedule_block">Block</string>
<string name="staking_reward_schedule_day">Tag</string>
<string name="staking_reward_schedule_each_day">Täglich</string>
@ -698,7 +694,7 @@
<string name="staking_stake_more">Mehr staken</string>
<string name="staking_title_stake">Stake %s</string>
<string name="staking_title_unstake">Staking beenden%s</string>
<string name="staking_unlocked_locked">Gelocktes unlocken</string>
<string name="staking_unlocked_locked">gelocktes unlocken</string>
<string name="staking_unstaked">Unstaken</string>
<string name="staking_unstaked_footer">Prüfe, was nicht eingesetzt wurde, um dein Vermögen zu beanspruchen</string>
<string name="staking_unstaking">Staking beenden</string>

View file

@ -86,7 +86,6 @@
<string name="common_copy">Copier</string>
<string name="common_copy_address">Copier l\'adresse</string>
<string name="common_create">Créer</string>
<string name="common_crypto_fiat_format">%1$s (%2$s)</string>
<string name="common_custom">Personnalisé</string>
<plurals name="common_days">
<item quantity="one">%d jour</item>
@ -271,7 +270,7 @@
<string name="feedback_subject_support">Commentaires</string>
<string name="feedback_subject_support_tangem">Commentaires sur Tangem</string>
<string name="feedback_subject_tx_failed">Impossible d\'envoyer une transaction</string>
<string name="give_permission_current_transaction">Transaction</string>
<string name="give_permission_current_transaction">Transaction </string>
<string name="give_permission_fee_footer">Le réseau facturera des frais d\'approbation de jeton pour vérifier que vous autorisez l\'utilisation de votre jeton pour l\'échange.</string>
<string name="give_permission_policy_type_footer">Spécifiez la limite approuvée pour le jeton sélectionné</string>
<string name="give_permission_rows_amount">Montant %s</string>
@ -568,6 +567,7 @@
<string name="send_transaction_success">La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps</string>
<string name="send_tron_account_activation_error">%1$s est un actif du réseau Tron. Pour calculer les frais et effectuer une transaction, déposez du Tron (TRX) sur votre compte.</string>
<string name="send_validation_invalid_address">Adresse incorrecte</string>
<string name="send_wallet_balance_format">%1$s (%2$s)</string>
<string name="sent_transaction_sent_title">Transaction envoyée</string>
<string name="settings_card_settings_footer">Préparez-vous à numériser la carte que vous souhaitez configurer.</string>
<string name="settings_forget_wallet">Oublier le portefeuille</string>

View file

@ -87,7 +87,6 @@
<string name="common_copy">コピー</string>
<string name="common_copy_address">アドレスをコピー</string>
<string name="common_create">作成</string>
<string name="common_crypto_fiat_format">%1$s ( %2$s )</string>
<string name="common_custom">設定</string>
<plurals name="common_days">
<item quantity="other">%d 日</item>
@ -125,7 +124,6 @@
<string name="common_passphrase">パスフレーズ</string>
<string name="common_paste">ペースト</string>
<string name="common_range">%1$s-%2$s</string>
<string name="common_range_with_space">%1$s — %2$s</string>
<string name="common_read_more">続きを読む</string>
<string name="common_receive">受け取る</string>
<string name="common_reject">拒否</string>
@ -628,6 +626,7 @@
<string name="send_transaction_success">取引は正常に署名され、ブロックチェーンノードに送信されました。ウォレットの残高はしばらくして更新されます。</string>
<string name="send_tron_account_activation_error">%1$sはTronネットワークのアセットです。手数料を計算して取引を行うには、アカウントにTronTRXを入金する必要があります。</string>
<string name="send_validation_invalid_address">無効なアドレス</string>
<string name="send_wallet_balance_format">%1$s ( %2$s )</string>
<string name="sent_transaction_sent_title">取引が送信されました</string>
<string name="settings_card_settings_footer">セットアップしたいカードをスキャンするために準備してください。</string>
<string name="settings_forget_wallet">ウォレット削除</string>
@ -637,7 +636,6 @@
<string name="staking_active_footer">資産のステーキングを解除するには、ここをクリックしてください。</string>
<string name="staking_amount_requirement_error">ステーキング金額は %s 以上である必要があります</string>
<string name="staking_claim_unstaked">ステーキング解除分を請求する</string>
<string name="staking_details_annual_percentage_rate">年率</string>
<string name="staking_details_annual_percentage_rate_info">ステーキングに参加することで得られる年間収益率。</string>
<string name="staking_details_apr">APR</string>
<string name="staking_details_available">利用可能</string>
@ -660,10 +658,6 @@
<string name="staking_details_warmup_period_info">ステーキングへの参加を有効にするために割り当てられた時間。</string>
<string name="staking_migrate">移行</string>
<string name="staking_native">ネイティブステーキング</string>
<string name="staking_notification_earn_rewards_text_period_day">ステーキングにより%1$sを獲得できます。ステーキング報酬は毎日受け取れます。</string>
<string name="staking_notification_earn_rewards_text_period_hour">ステーキングにより%1$sを獲得できます。ステーキング報酬は1時間ごとに受け取れます。</string>
<string name="staking_notification_earn_rewards_text_period_month">ステーキングにより%1$sを獲得できます。ステーキング報酬は毎月受け取れます。</string>
<string name="staking_notification_earn_rewards_text_period_week">ステーキングにより%1$sを獲得できます。ステーキング報酬は毎週受け取れます。</string>
<string name="staking_notification_earn_rewards_title">ステーキング報酬を獲得</string>
<string name="staking_notification_unstake_text">ステーキング解除後、報酬の獲得はすぐに停止します。ステーキング解除プロセスには%sかかります。</string>
<string name="staking_rebond">再結束</string>

View file

@ -2,8 +2,10 @@ package com.tangem.data.staking.converters
import com.tangem.datasource.api.stakekit.models.response.model.AddressArgumentDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.MetadataDTO.RewardScheduleDTO
import com.tangem.domain.staking.model.stakekit.AddressArgument
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.Yield.Metadata.RewardSchedule
import com.tangem.utils.converter.Converter
class YieldConverter(
@ -80,10 +82,10 @@ class YieldConverter(
token = tokenConverter.convert(metadataDTO.tokenDTO),
tokens = metadataDTO.tokensDTO.map { tokenConverter.convert(it) },
type = metadataDTO.type,
rewardSchedule = metadataDTO.rewardSchedule,
rewardSchedule = convertRewardSchedule(metadataDTO.rewardSchedule),
cooldownPeriod = convertPeriod(metadataDTO.cooldownPeriod),
warmupPeriod = convertPeriod(metadataDTO.warmupPeriod),
rewardClaiming = metadataDTO.rewardClaiming,
rewardClaiming = convertRewardClaiming(metadataDTO.rewardClaiming),
defaultValidator = metadataDTO.defaultValidator,
minimumStake = metadataDTO.minimumStake,
supportsMultipleValidators = metadataDTO.supportsMultipleValidators,
@ -127,6 +129,29 @@ class YieldConverter(
}
}
private fun convertRewardSchedule(rewardTypeDTO: RewardScheduleDTO): RewardSchedule {
return when (rewardTypeDTO) {
RewardScheduleDTO.BLOCK -> RewardSchedule.BLOCK
RewardScheduleDTO.WEEK -> RewardSchedule.WEEK
RewardScheduleDTO.HOUR -> RewardSchedule.HOUR
RewardScheduleDTO.DAY -> RewardSchedule.DAY
RewardScheduleDTO.MONTH -> RewardSchedule.MONTH
RewardScheduleDTO.ERA -> RewardSchedule.ERA
RewardScheduleDTO.EPOCH -> RewardSchedule.EPOCH
else -> RewardSchedule.UNKNOWN
}
}
private fun convertRewardClaiming(
rewardClaimingDTO: YieldDTO.MetadataDTO.RewardClaimingDTO,
): Yield.Metadata.RewardClaiming {
return when (rewardClaimingDTO) {
YieldDTO.MetadataDTO.RewardClaimingDTO.AUTO -> Yield.Metadata.RewardClaiming.AUTO
YieldDTO.MetadataDTO.RewardClaimingDTO.MANUAL -> Yield.Metadata.RewardClaiming.MANUAL
else -> Yield.Metadata.RewardClaiming.UNKNOWN
}
}
private fun convertArgType(value: String): Yield.Args.ArgType {
return when (value) {
"address" -> Yield.Args.ArgType.ADDRESS

View file

@ -74,10 +74,10 @@ data class Yield(
val token: Token,
val tokens: List<Token>,
val type: String,
val rewardSchedule: String,
val rewardSchedule: RewardSchedule,
val cooldownPeriod: Period,
val warmupPeriod: Period,
val rewardClaiming: String,
val rewardClaiming: RewardClaiming,
val defaultValidator: String?,
val minimumStake: Int?,
val supportsMultipleValidators: Boolean,
@ -94,6 +94,25 @@ data class Yield(
data class Enabled(
val enabled: Boolean,
)
enum class RewardSchedule {
BLOCK,
WEEK,
HOUR,
DAY,
MONTH,
ERA,
EPOCH,
UNKNOWN,
}
enum class RewardClaiming {
AUTO,
MANUAL,
UNKNOWN,
}
}
enum class RewardType {

View file

@ -93,10 +93,10 @@ class MockStakingRepository : StakingRepository {
),
tokens = listOf(),
type = "auto",
rewardSchedule = "1",
rewardSchedule = Yield.Metadata.RewardSchedule.DAY,
cooldownPeriod = Yield.Metadata.Period(days = 1),
warmupPeriod = Yield.Metadata.Period(days = 1),
rewardClaiming = "1",
rewardClaiming = Yield.Metadata.RewardClaiming.AUTO,
defaultValidator = null,
minimumStake = null,
supportsMultipleValidators = false,

View file

@ -4,6 +4,7 @@ import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.features.staking.impl.R
internal sealed class StakingNotification(val config: NotificationConfig) {
@ -49,12 +50,23 @@ internal sealed class StakingNotification(val config: NotificationConfig) {
),
) {
data class EarnRewards(
val subtitleResourceId: Int,
val currencyName: String,
val days: Int,
) : Warning(
title = resourceReference(R.string.staking_notification_earn_rewards_title),
subtitle = resourceReference(
R.string.common_error, // FIXME
subtitleResourceId,
wrappedList(currencyName),
),
)
data class Unstake(
val cooldownPeriodDays: Int,
) : Warning(
title = resourceReference(R.string.common_unstake),
subtitle = resourceReference(
R.string.staking_notification_unstake_text,
wrappedList(cooldownPeriodDays, cooldownPeriodDays),
),
)
}

View file

@ -7,6 +7,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
import com.tangem.features.staking.impl.presentation.state.transformers.SetButtonsStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.SetTitleTransformer
import com.tangem.utils.transformer.Transformer
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -25,20 +26,24 @@ internal class StakingStateController @Inject constructor() {
val uiState: StateFlow<StakingUiState> get() = mutableUiState.asStateFlow()
private val buttonsTransformer = SetButtonsStateTransformer()
private val titleTransformer = SetTitleTransformer
fun update(function: (StakingUiState) -> StakingUiState) {
mutableUiState.update(function = function)
mutableUiState.update(function = buttonsTransformer::transform)
mutableUiState.update(function = titleTransformer::transform)
}
fun update(transformer: Transformer<StakingUiState>) {
mutableUiState.update(function = transformer::transform)
mutableUiState.update(function = buttonsTransformer::transform)
mutableUiState.update(function = titleTransformer::transform)
}
fun clear() {
mutableUiState.update { getInitialState() }
mutableUiState.update(function = buttonsTransformer::transform)
mutableUiState.update(function = titleTransformer::transform)
}
private fun getInitialState(): StakingUiState {

View file

@ -5,6 +5,7 @@ import com.tangem.blockchain.common.AmountType.Coin
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.StakingNotification
import com.tangem.features.staking.impl.presentation.state.StakingStates
@ -81,7 +82,7 @@ internal object ConfirmationStatePreviewData {
notifications = persistentListOf(
StakingNotification.Warning.EarnRewards(
currencyName = "Solana",
days = 2,
subtitleResourceId = R.string.staking_notification_earn_rewards_text_period_day,
),
),
transactionDoneState = TransactionDoneState.Empty,

View file

@ -1,8 +1,11 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
internal class SetConfirmationStateLoadingTransformer(
@ -24,12 +27,7 @@ internal class SetConfirmationStateLoadingTransformer(
chosenValidator = chosenValidator,
availableValidators = yield.validators,
),
notifications = persistentListOf(
StakingNotification.Warning.EarnRewards(
currencyName = yield.token.name,
days = yield.metadata.cooldownPeriod.days,
),
),
notifications = getNotifications(prevState),
footerText = "",
transactionDoneState = TransactionDoneState.Empty,
pendingActions = persistentListOf(),
@ -38,4 +36,41 @@ internal class SetConfirmationStateLoadingTransformer(
),
)
}
private fun getNotifications(prevState: StakingUiState): ImmutableList<StakingNotification> {
return persistentListOf(
if (prevState.actionType == StakingActionCommonType.EXIT) {
StakingNotification.Warning.Unstake(
cooldownPeriodDays = yield.metadata.cooldownPeriod.days,
)
} else {
StakingNotification.Warning.EarnRewards(
currencyName = yield.token.name,
subtitleResourceId = getEarnRewardsPeriod(yield.metadata.rewardSchedule),
)
},
)
}
private fun getEarnRewardsPeriod(rewardSchedule: Yield.Metadata.RewardSchedule): Int {
return when (rewardSchedule) {
Yield.Metadata.RewardSchedule.BLOCK,
Yield.Metadata.RewardSchedule.DAY,
Yield.Metadata.RewardSchedule.ERA,
Yield.Metadata.RewardSchedule.EPOCH,
-> R.string.staking_notification_earn_rewards_text_period_day
Yield.Metadata.RewardSchedule.HOUR,
-> R.string.staking_notification_earn_rewards_text_period_hour
Yield.Metadata.RewardSchedule.WEEK,
-> R.string.staking_notification_earn_rewards_text_period_week
Yield.Metadata.RewardSchedule.MONTH,
-> R.string.staking_notification_earn_rewards_text_period_month
else
-> R.string.staking_notification_earn_rewards_text_period_day
}
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.core.serialization.SerializedBigDecimal
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
@ -61,6 +62,7 @@ internal class SetInitialDataStateTransformer(
R.string.staking_title_stake,
wrappedList(cryptoCurrencyStatusProvider().currency.name),
),
cryptoCurrencyName = cryptoCurrencyStatusProvider.invoke().currency.name,
clickIntents = clickIntents,
currentStep = StakingStep.InitialInfo,
initialInfoState = createInitialInfoState(),
@ -74,7 +76,7 @@ internal class SetInitialDataStateTransformer(
private fun createInitialInfoState(): StakingStates.InitialInfoState.Data {
return StakingStates.InitialInfoState.Data(
isPrimaryButtonEnabled = true,
aprRange = getAprRange(),
aprRange = getAprRange(yield.validators),
infoItems = getInfoItems(),
onInfoClick = clickIntents::onInfoClick,
yieldBalance = yieldBalancesConverter.convert(Unit),
@ -84,24 +86,26 @@ internal class SetInitialDataStateTransformer(
private fun getInfoItems(): PersistentList<RoundedListWithDividersItemData> {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance
return listOfNotNull(
createAnnualPercentageRateItem(),
createAnnualPercentageRateItem(yield.validators),
createAvailableItem(cryptoCurrencyStatus),
createUnbondingPeriodItem(),
createMinimumRequirementItem(cryptoCurrencyStatus),
createRewardClaimingItem(),
createWarmupPeriodItem(),
createRewardScheduleItem(),
createUnbondingPeriodItem(yield.metadata.cooldownPeriod.days),
createMinimumRequirementItem(
cryptoCurrencyStatus = cryptoCurrencyStatus,
minimumCryptoAmount = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum,
),
createRewardClaimingItem(yield.metadata.rewardClaiming),
createWarmupPeriodItem(yield.metadata.warmupPeriod.days),
createRewardScheduleItem(yield.metadata.rewardSchedule),
).toPersistentList()
}
private fun createAnnualPercentageRateItem(): RoundedListWithDividersItemData {
private fun createAnnualPercentageRateItem(validators: List<Yield.Validator>): RoundedListWithDividersItemData {
return RoundedListWithDividersItemData(
id = R.string.staking_details_annual_percentage_rate,
startText = TextReference.Res(R.string.staking_details_annual_percentage_rate),
endText = getAprRange(),
endText = getAprRange(validators),
iconClick = { clickIntents.onInfoClick(InfoType.ANNUAL_PERCENTAGE_RATE) },
)
}
@ -120,14 +124,14 @@ internal class SetInitialDataStateTransformer(
)
}
private fun createUnbondingPeriodItem(): RoundedListWithDividersItemData {
private fun createUnbondingPeriodItem(cooldownPeriodDays: Int): RoundedListWithDividersItemData {
return RoundedListWithDividersItemData(
id = R.string.staking_details_unbonding_period,
startText = TextReference.Res(R.string.staking_details_unbonding_period),
endText = pluralReference(
id = R.plurals.common_days,
count = yield.metadata.cooldownPeriod.days,
formatArgs = wrappedList(yield.metadata.cooldownPeriod.days),
count = cooldownPeriodDays,
formatArgs = wrappedList(cooldownPeriodDays),
),
iconClick = { clickIntents.onInfoClick(InfoType.UNBONDING_PERIOD) },
)
@ -135,53 +139,60 @@ internal class SetInitialDataStateTransformer(
private fun createMinimumRequirementItem(
cryptoCurrencyStatus: CryptoCurrencyStatus,
minimumCryptoAmount: SerializedBigDecimal?,
): RoundedListWithDividersItemData? {
val minimumCryptoAmount = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum
return minimumCryptoAmount?.let {
RoundedListWithDividersItemData(
id = R.string.staking_details_minimum_requirement,
startText = TextReference.Res(R.string.staking_details_minimum_requirement),
endText = TextReference.Str(
value = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = it,
cryptoCurrency = cryptoCurrencyStatus.currency.symbol,
decimals = cryptoCurrencyStatus.currency.decimals,
),
minimumCryptoAmount ?: return null
return RoundedListWithDividersItemData(
id = R.string.staking_details_minimum_requirement,
startText = TextReference.Res(R.string.staking_details_minimum_requirement),
endText = TextReference.Str(
value = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = minimumCryptoAmount,
cryptoCurrency = cryptoCurrencyStatus.currency.symbol,
decimals = cryptoCurrencyStatus.currency.decimals,
),
)
}
),
)
}
private fun createRewardClaimingItem(): RoundedListWithDividersItemData {
private fun createRewardClaimingItem(
rewardClaiming: Yield.Metadata.RewardClaiming,
): RoundedListWithDividersItemData? {
val endTextId = rewardClaimingResources[rewardClaiming] ?: return null
return RoundedListWithDividersItemData(
id = R.string.staking_details_reward_claiming,
startText = TextReference.Res(R.string.staking_details_reward_claiming),
endText = TextReference.Str(yield.metadata.rewardClaiming),
endText = TextReference.Res(endTextId),
iconClick = { clickIntents.onInfoClick(InfoType.REWARD_CLAIMING) },
)
}
private fun createWarmupPeriodItem(): RoundedListWithDividersItemData? {
val warmupPeriod = yield.metadata.warmupPeriod.days
if (warmupPeriod == 0) return null
private fun createWarmupPeriodItem(warmupPeriodDays: Int): RoundedListWithDividersItemData? {
if (warmupPeriodDays == 0) return null
return RoundedListWithDividersItemData(
id = R.string.staking_details_warmup_period,
startText = TextReference.Res(R.string.staking_details_warmup_period),
endText = pluralReference(
id = R.plurals.common_days,
count = yield.metadata.warmupPeriod.days,
formatArgs = wrappedList(yield.metadata.warmupPeriod.days),
count = warmupPeriodDays,
formatArgs = wrappedList(warmupPeriodDays),
),
iconClick = { clickIntents.onInfoClick(InfoType.WARMUP_PERIOD) },
)
}
private fun createRewardScheduleItem(): RoundedListWithDividersItemData {
private fun createRewardScheduleItem(
rewardSchedule: Yield.Metadata.RewardSchedule,
): RoundedListWithDividersItemData? {
val endTextId = rewardScheduleResources[rewardSchedule] ?: return null
return RoundedListWithDividersItemData(
id = R.string.staking_details_reward_schedule,
startText = TextReference.Res(R.string.staking_details_reward_schedule),
endText = TextReference.Str(yield.metadata.rewardSchedule),
endText = TextReference.Res(endTextId),
iconClick = { clickIntents.onInfoClick(InfoType.REWARD_SCHEDULE) },
)
}
@ -200,8 +211,8 @@ internal class SetInitialDataStateTransformer(
)
}
private fun getAprRange(): TextReference {
val aprValues = yield.validators.mapNotNull { it.apr }
private fun getAprRange(validators: List<Yield.Validator>): TextReference {
val aprValues = validators.mapNotNull { it.apr }
val minApr = aprValues.min()
val maxApr = aprValues.max()
@ -223,5 +234,22 @@ internal class SetInitialDataStateTransformer(
companion object {
private val EQUALITY_THRESHOLD = BigDecimal(1E-10)
private val rewardScheduleResources = mapOf(
Yield.Metadata.RewardSchedule.BLOCK to R.string.staking_reward_schedule_each_day,
Yield.Metadata.RewardSchedule.WEEK to R.string.staking_reward_schedule_week,
Yield.Metadata.RewardSchedule.HOUR to R.string.staking_reward_schedule_hour,
Yield.Metadata.RewardSchedule.DAY to R.string.staking_reward_schedule_each_day,
Yield.Metadata.RewardSchedule.MONTH to R.string.staking_reward_schedule_month,
Yield.Metadata.RewardSchedule.ERA to R.string.staking_reward_schedule_era,
Yield.Metadata.RewardSchedule.EPOCH to R.string.staking_reward_schedule_epoch,
Yield.Metadata.RewardSchedule.UNKNOWN to null,
)
private val rewardClaimingResources = mapOf(
Yield.Metadata.RewardClaiming.MANUAL to R.string.staking_reward_claiming_manual,
Yield.Metadata.RewardClaiming.AUTO to R.string.staking_reward_claiming_auto,
Yield.Metadata.RewardSchedule.UNKNOWN to null,
)
}
}

View file

@ -0,0 +1,41 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.utils.transformer.Transformer
internal object SetTitleTransformer : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val actionType = prevState.actionType
val currentStep = prevState.currentStep
val title = when {
currentStep == StakingStep.Amount -> {
resourceReference(R.string.send_amount_label)
}
currentStep == StakingStep.Validators -> {
resourceReference(R.string.staking_validators)
}
actionType == StakingActionCommonType.EXIT && currentStep != StakingStep.InitialInfo -> {
resourceReference(
R.string.staking_title_unstake,
wrappedList(prevState.cryptoCurrencyName),
)
}
else -> {
resourceReference(
R.string.staking_title_stake,
wrappedList(prevState.cryptoCurrencyName),
)
}
}
return prevState.copy(title = title)
}
}

View file

@ -31,7 +31,7 @@ internal fun StakingClaimRewardsValidatorContent(
if (state !is StakingStates.RewardsValidatorsState.Data) return
Column(
modifier = Modifier // Do not put fillMaxSize() in here
.background(TangemTheme.colors.background.tertiary)
.background(TangemTheme.colors.background.secondary)
.padding(horizontal = TangemTheme.dimens.spacing12)
.verticalScroll(rememberScrollState()),
) {

View file

@ -40,7 +40,7 @@ internal fun StakingConfirmationContent(
Column(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary)
.background(TangemTheme.colors.background.secondary)
.padding(horizontal = TangemTheme.dimens.spacing16)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),

View file

@ -17,6 +17,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.*
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
@ -107,14 +108,16 @@ internal fun StakingInitialInfoContent(state: StakingStates.InitialInfoState, cl
@Composable
private fun BannerBlock(onClick: () -> Unit) {
Box(
modifier = Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
onClick = onClick,
),
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
onClick = onClick,
),
) {
Image(
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.FillWidth,
painter = painterResource(R.drawable.img_staking_banner),
contentDescription = null,
)

View file

@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.tangem.common.ui.amountScreen.AmountScreenContent
import com.tangem.common.ui.bottomsheet.permission.GiveTxPermissionBottomSheet
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
@ -36,7 +35,7 @@ internal fun StakingScreen(uiState: StakingUiState) {
BackHandler(onBack = uiState.clickIntents::onPrevClick)
Column(
modifier = Modifier
.background(color = TangemTheme.colors.background.tertiary)
.background(color = TangemTheme.colors.background.secondary)
.fillMaxSize()
.imePadding()
.systemBarsPadding(),
@ -72,14 +71,6 @@ fun StakingBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
@Composable
private fun SendAppBar(uiState: StakingUiState) {
val titleRes = when (uiState.currentStep) {
StakingStep.Amount -> stringResource(id = R.string.send_amount_label)
StakingStep.InitialInfo,
StakingStep.RewardsValidators,
StakingStep.Validators,
StakingStep.Confirmation,
-> uiState.title.resolveReference()
}
val backIcon = when (uiState.currentStep) {
StakingStep.Amount,
StakingStep.Validators,
@ -94,10 +85,10 @@ private fun SendAppBar(uiState: StakingUiState) {
}
}
AppBarWithBackButtonAndIcon(
text = titleRes,
text = uiState.title.resolveReference(),
backIconRes = backIcon,
onBackClick = uiState.clickIntents::onBackClick,
backgroundColor = TangemTheme.colors.background.tertiary,
backgroundColor = TangemTheme.colors.background.secondary,
modifier = Modifier.height(TangemTheme.dimens.size56),
)
}

View file

@ -4,12 +4,10 @@ import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
@ -47,23 +45,6 @@ internal fun StakingValidatorListContent(
contentPadding = PaddingValues(bottom = bottomBarHeight),
modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing16),
) {
item(key = "HEADER") {
Text(
text = stringResource(R.string.staking_validator),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
.fillMaxWidth()
.clip(CornersToRound.TOP_2.getShape())
.background(TangemTheme.colors.background.action)
.padding(
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
top = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing8,
),
)
}
if (state is ValidatorState.Content) {
val validators = state.availableValidators
items(
@ -93,10 +74,10 @@ internal fun StakingValidatorListContent(
onSelect = { clickIntents.onValidatorSelect(item) },
modifier = Modifier
.clip(
if (index == validators.lastIndex) {
CornersToRound.BOTTOM_2
} else {
CornersToRound.ZERO
when (index) {
0 -> CornersToRound.TOP_2
validators.lastIndex -> CornersToRound.BOTTOM_2
else -> CornersToRound.ZERO
}.getShape(),
)
.background(TangemTheme.colors.background.action),

View file

@ -88,7 +88,7 @@ private fun BoxScope.FeeLoading(feeState: FeeState) {
RectangleShimmer(
radius = TangemTheme.dimens.radius3,
modifier = Modifier.size(
height = TangemTheme.dimens.size12,
height = TangemTheme.dimens.size24,
width = TangemTheme.dimens.size90,
),
)
@ -107,7 +107,7 @@ private fun BoxScope.FeeError(feeState: FeeState) {
Text(
text = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.body2,
style = TangemTheme.typography.body1,
)
}
}
@ -117,7 +117,7 @@ private fun BoxScope.FeeError(feeState: FeeState) {
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) value: FeeState.Content) {
private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) value: FeeState) {
TangemThemePreview {
StakingFeeBlock(
feeState = value,
@ -125,11 +125,13 @@ private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) va
}
}
private class FeeBlockPreviewProvider : PreviewParameterProvider<FeeState.Content> {
private class FeeBlockPreviewProvider : PreviewParameterProvider<FeeState> {
override val values: Sequence<FeeState.Content>
override val values: Sequence<FeeState>
get() = sequenceOf(
feeState,
contentState,
FeeState.Loading,
FeeState.Error,
)
private val fee = Fee.Common(
@ -141,7 +143,7 @@ private class FeeBlockPreviewProvider : PreviewParameterProvider<FeeState.Conten
),
)
private val feeState = FeeState.Content(
private val contentState = FeeState.Content(
fee = fee,
rate = BigDecimal.ONE,
appCurrency = AppCurrency.Default,

View file

@ -221,7 +221,7 @@ internal class StakingViewModel @Inject constructor(
}
override fun onInitialInfoBannerClick() {
innerRouter.openUrl(WHAT_IS_STAKING_ARTICLE_URL)
// innerRouter.openUrl(WHAT_IS_STAKING_ARTICLE_URL)
}
override fun onInfoClick(infoType: InfoType) {

View file

@ -91,19 +91,4 @@ internal sealed class TokenDetailsActionButton(val config: ActionButtonConfig) {
dimContent = dimContent,
),
)
/**
* Staking
*
* @property dimContent determines whether the button content will be dimmed
* @property onClick lambda be invoked when Swap button is clicked
*/
data class Stake(val dimContent: Boolean, override val onClick: () -> Unit) : TokenDetailsActionButton(
config = ActionButtonConfig(
text = TextReference.Res(id = R.string.common_stake),
iconResId = R.drawable.ic_staking_24,
onClick = onClick,
dimContent = dimContent,
),
)
}

View file

@ -38,12 +38,6 @@ internal class TokenDetailsActionButtonsConverter(
onLongClick = clickIntents::onCopyAddress,
)
}
is TokenActionsState.ActionState.Stake -> {
TokenDetailsActionButton.Stake(
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
onClick = { clickIntents.onStakeClick(action.unavailabilityReason) },
)
}
is TokenActionsState.ActionState.Sell -> {
TokenDetailsActionButton.Sell(
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,

View file

@ -33,7 +33,7 @@ internal fun StakingBalanceBlock(state: StakingBlockUM.Staked, modifier: Modifie
modifier = modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action)
.background(TangemTheme.colors.background.primary)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),