Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-04 19:47:22 +05:00
parent 2cf60002a4
commit a7f5b54078
17 changed files with 319 additions and 336 deletions

View file

@ -115,10 +115,6 @@
<string name="common_go_to_provider">Zum Anbieter gehen</string>
<string name="common_go_to_token">Zum Token</string>
<string name="common_import">Importieren</string>
<plurals name="common_in_days">
<item quantity="one">in %d Tag</item>
<item quantity="other">in %d Tagen</item>
</plurals>
<string name="common_later">Später</string>
<string name="common_locked">Gesperrt</string>
<string name="common_main_network">Hauptnetz</string>
@ -641,8 +637,6 @@
<string name="settings_forget_wallet">Entferne diese Wallet</string>
<string name="settings_forget_wallet_footer">Hiermit wird die Wallet aus der Anwendung entfernt. Die Wallet selbst kann wieder hinzugefügt werden.</string>
<string name="settings_wallet_name_title">Name</string>
<string name="staking_active">Aktiv</string>
<string name="staking_active_footer">Um deine Vermögenswerte freizugeben, tippe auf den Block oben</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_details_annual_percentage_rate">Jährliche prozentuale Rendite</string>
@ -702,7 +696,6 @@
<string name="staking_unbonding">Lösen der Bindungen</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>
<string name="staking_validator">Validator/ Prüfer</string>
<string name="staking_validators">Validatoren</string>

View file

@ -115,10 +115,6 @@
<string name="common_go_to_provider">Aller au fournisseur</string>
<string name="common_go_to_token">Aller au jeton</string>
<string name="common_import">Importez</string>
<plurals name="common_in_days">
<item quantity="one">dans %d jour</item>
<item quantity="other">dans %d jours</item>
</plurals>
<string name="common_later">Plus tard</string>
<string name="common_locked">Verrouillé</string>
<string name="common_main_network">Réseau principal</string>
@ -637,8 +633,6 @@
<string name="settings_forget_wallet">Oublier le portefeuille</string>
<string name="settings_forget_wallet_footer">Cela supprimera le portefeuille de l\'application. Le portefeuille lui-même peut être ajouté à nouveau.</string>
<string name="settings_wallet_name_title">Nom</string>
<string name="staking_active">Actif</string>
<string name="staking_active_footer">Afin d\'unstaker vos actifs, appuyez sur le bloc ci-dessus</string>
<string name="staking_amount_requirement_error">Le montant à staker doit être au moins %s</string>
<string name="staking_claim_unstaked">Réclamation déstakée</string>
<string name="staking_details_annual_percentage_rate">Pourcentage de rendement annuel</string>
@ -697,7 +691,6 @@
<string name="staking_unbonding">Dissociation</string>
<string name="staking_unlocked_locked">Débloquer</string>
<string name="staking_unstaked">Non-staké</string>
<string name="staking_unstaked_footer">Vérifiez non-stakés pour réclamer vos actifs</string>
<string name="staking_unstaking">Unstaking</string>
<string name="staking_validator">Validateur</string>
<string name="staking_validators">Validateurs</string>

View file

@ -83,6 +83,7 @@
<string name="common_camera_denied_alert_message">カメラへのアクセスを許可していません。プライバシー設定を調整してください。</string>
<string name="common_cancel">キャンセル</string>
<string name="common_choose_action">アクションを選択</string>
<string name="common_claim">請求</string>
<string name="common_claim_rewards">報酬を受け取る</string>
<string name="common_close">閉じる</string>
<string name="common_continue">続ける</string>
@ -113,9 +114,6 @@
<string name="common_go_to_provider">プロバイダーへ移動</string>
<string name="common_go_to_token">トークンへ移動</string>
<string name="common_import">インポート</string>
<plurals name="common_in_days">
<item quantity="other">%d日で</item>
</plurals>
<string name="common_later">後で</string>
<string name="common_locked">ロックされています</string>
<string name="common_main_network">メインネットワーク</string>
@ -628,8 +626,6 @@
<string name="settings_forget_wallet">ウォレット削除</string>
<string name="settings_forget_wallet_footer">これにより、ウォレットがアプリから削除されます。ウォレットは再度追加できます。</string>
<string name="settings_wallet_name_title">名前</string>
<string name="staking_active">アクティブ</string>
<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>
@ -654,14 +650,17 @@
<string name="staking_details_unbonding_period_info">ステーキングから資金の引き出しを要求した後、トークンが利用可能になるまでの待機期間。</string>
<string name="staking_details_warmup_period">ウォームアップ期間</string>
<string name="staking_details_warmup_period_info">ステーキングへの参加を有効にするために割り当てられた時間。</string>
<string name="staking_legal">ステーキング機能を使用すると、プロバイダーの%1$sと%2$sに同意したことになります</string>
<string name="staking_locked">ロック中</string>
<string name="staking_migrate">移行</string>
<string name="staking_native">ネイティブステーキング</string>
<string name="staking_notification_claim_rewards_text">獲得した報酬はあなたのアドレスに直接送られます。受け取り後、資金は使用可能になります。</string>
<string name="staking_notification_earn_rewards_text_period_day">ステーキングにより%1$sを獲得できます。ステーキング報酬は毎日受け取れます。</string>
<string name="staking_notification_earn_rewards_text_period_hour">ステーキングにより%1$sを獲得できます。ステーキング報酬は毎時間受け取れます。</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_restake_rewards_text">獲得した報酬をステーキングに再投資し、潜在的な収益を増やします。</string>
<string name="staking_notification_unstake_text">ステーキング解除プロセスの開始後、報酬の獲得はすぐに停止します。ステーキング解除には%sかかります。</string>
<string name="staking_notification_withdraw_text">あなたの資金はステーキング解除されました。獲得した報酬とともに引き出すことができます。</string>
<string name="staking_preparing">準備中</string>
@ -684,13 +683,14 @@
<string name="staking_rewards">報酬</string>
<string name="staking_stake_locked">ステーキングはロックされています</string>
<string name="staking_stake_more">もっとステーキングする</string>
<string name="staking_summary_description_text">%1$sをステーキングし、年間 ~ %2$sを受け取ります</string>
<string name="staking_title_stake">%sをステーキングする</string>
<string name="staking_title_unstake">%sのステーキング解除</string>
<string name="staking_unbonding">ステーキング解約中</string>
<string name="staking_unlocked_locked">ステーキング解除はロックされています</string>
<string name="staking_unstaked">ステーキングされていない</string>
<string name="staking_unstaked_footer">資産を請求するために、unstakedを確認してください</string>
<string name="staking_unstaking">ステーキング解除</string>
<string name="staking_unstaking_item_name">ステーキング解除中の資産%s</string>
<string name="staking_validator">バリデーター</string>
<string name="staking_validators">バリデーター</string>
<string name="staking_vote">投票する</string>

View file

@ -117,12 +117,6 @@
<string name="common_go_to_provider">К провайдеру</string>
<string name="common_go_to_token">Перейти в токен</string>
<string name="common_import">Импортировать</string>
<plurals name="common_in_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_later">Позже</string>
<string name="common_locked">Заблокирован</string>
<string name="common_main_network">Основная сеть</string>
@ -645,8 +639,6 @@
<string name="settings_forget_wallet">Забыть кошелек</string>
<string name="settings_forget_wallet_footer">Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова.</string>
<string name="settings_wallet_name_title">Имя</string>
<string name="staking_active">Активно</string>
<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>
@ -701,7 +693,6 @@
<string name="staking_unbonding">Отзыв</string>
<string name="staking_unlocked_locked">Разблокировать</string>
<string name="staking_unstaked">Вывод из стейкинга</string>
<string name="staking_unstaked_footer">Проверьте процесс завершения стейкинга, чтобы вывести свои средства.</string>
<string name="staking_unstaking">Завершение стейкинга</string>
<string name="staking_unstaking_item_name">Завершение стейкинга %s</string>
<string name="staking_validator">Валидатор</string>

View file

@ -642,8 +642,6 @@
<string name="settings_forget_wallet">Забути гаманець</string>
<string name="settings_forget_wallet_footer">Це призведе до видалення гаманця з застосунку. Сам гаманець можна додати знову.</string>
<string name="settings_wallet_name_title">Ім\'я</string>
<string name="staking_active">Активний</string>
<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>
@ -696,7 +694,6 @@
<string name="staking_title_unstake">Зняти зі стейкінгу %s</string>
<string name="staking_unlocked_locked">Розблокувати</string>
<string name="staking_unstaked">Вивід зі стейкінгу</string>
<string name="staking_unstaked_footer">Перевірити незастейкані, щоб отримати свої активи</string>
<string name="staking_unstaking">Зняти зі стейкінгу</string>
<string name="staking_validator">Валідатор</string>
<string name="staking_vote">Проголосувати</string>

View file

@ -116,11 +116,8 @@
<string name="common_go_to_provider">Go to provider</string>
<string name="common_go_to_token">Go to token</string>
<string name="common_import">Import</string>
<plurals name="common_in_days">
<item quantity="one">in %d day</item>
<item quantity="other">in %d days</item>
</plurals>
<string name="common_later">Later</string>
<string name="common_left">%1$s left</string>
<string name="common_locked">Locked</string>
<string name="common_main_network">Main network</string>
<string name="common_network_fee_title">Network fee</string>
@ -414,6 +411,8 @@
<string name="markets_token_details_total_supply_description">The maximum number of coins or tokens that can ever exist for a particular cryptocurrency</string>
<string name="markets_token_details_trading_volume">Trading volume (24h)</string>
<string name="markets_token_details_trading_volume_24h_description">The total amount of a cryptocurrency that has been traded within the last 24 hours, indicating the level of activity and liquidity in the market</string>
<string name="markets_tooltip_message">Pull this up or tap the search bar to add tokens directly from the market</string>
<string name="markets_tooltip_title">Add tokens</string>
<string name="onboarding_access_code_feature_1_description">You have to set up a single access code to protect all your cards</string>
<string name="onboarding_access_code_feature_1_title">Protect</string>
<string name="onboarding_access_code_feature_2_description">You can set up an individual access code on each card later</string>
@ -638,8 +637,6 @@
<string name="settings_forget_wallet">Forget wallet</string>
<string name="settings_forget_wallet_footer">This will remove the wallet from the application. The wallet itself can be added again.</string>
<string name="settings_wallet_name_title">Name</string>
<string name="staking_active">Active</string>
<string name="staking_active_footer">To unstake your assets, tap the block above</string>
<string name="staking_amount_requirement_error">The amount to stake must be at least %s</string>
<string name="staking_claim_unstaked">Claim unstaked</string>
<string name="staking_details_annual_percentage_rate">Annual percentage rate</string>
@ -698,12 +695,13 @@
<string name="staking_stake_locked">Stake locked</string>
<string name="staking_stake_more">Stake more</string>
<string name="staking_summary_description_text">You stake %1$s and will be receiving ~%2$s yearly</string>
<string name="staking_tap_to_unlock">Tap to unlock</string>
<string name="staking_tap_to_withdraw">Tap to withdraw</string>
<string name="staking_title_stake">Stake %s</string>
<string name="staking_title_unstake">Unstake %s</string>
<string name="staking_unbonding">Unbonding</string>
<string name="staking_unlocked_locked">Unlock locked</string>
<string name="staking_unstaked">Unstaked</string>
<string name="staking_unstaked_footer">Check unstaked to claim your assets</string>
<string name="staking_unstaking">Unstaking</string>
<string name="staking_unstaking_item_name">Unstaking assets %s</string>
<string name="staking_validator">Validator</string>
@ -711,6 +709,7 @@
<string name="staking_vote">Vote</string>
<string name="staking_vote_locked">Vote locked</string>
<string name="staking_withdraw">Withdraw</string>
<string name="staking_your_stakes">Your stakes</string>
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
<string name="story_backup_description">Up to 3 physical cards to one wallet</string>

View file

@ -1,7 +1,9 @@
package com.tangem.core.ui.components.inputrow
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@ -42,11 +44,13 @@ import com.tangem.core.ui.res.TangemThemePreview
@Composable
internal fun InputRowImageBase(
subtitle: TextReference,
imageUrl: String?,
modifier: Modifier = Modifier,
caption: TextReference? = null,
imageUrl: String? = null,
@DrawableRes iconRes: Int? = null,
subtitleColor: Color = TangemTheme.colors.text.primary1,
captionColor: Color = TangemTheme.colors.text.tertiary,
iconTint: Color = TangemTheme.colors.icon.informative,
isGrayscaleImage: Boolean = false,
iconEndRes: Int? = null,
extraContent: (@Composable RowScope.() -> Unit)? = null,
@ -65,6 +69,23 @@ internal fun InputRowImageBase(
)
SpacerW12()
}
if (iconRes != null) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(TangemTheme.dimens.spacing36)
.clip(TangemTheme.shapes.roundedCornersXLarge)
.background(iconTint.copy(alpha = 0.08f)),
) {
Icon(
painter = rememberVectorPainter(image = ImageVector.vectorResource(id = iconRes)),
tint = iconTint,
contentDescription = null,
modifier = Modifier.size(TangemTheme.dimens.size18),
)
}
SpacerW12()
}
Column {
Text(
text = subtitle.resolveReference(),

View file

@ -1,6 +1,7 @@
package com.tangem.core.ui.components.inputrow
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
@ -40,15 +41,17 @@ import com.tangem.core.ui.res.TangemThemePreview
fun InputRowImageInfo(
subtitle: TextReference,
infoTitle: TextReference,
imageUrl: String?,
modifier: Modifier = Modifier,
title: TextReference? = null,
caption: TextReference? = null,
infoSubtitle: TextReference? = null,
imageUrl: String? = null,
@DrawableRes iconRes: Int? = null,
subtitleColor: Color = TangemTheme.colors.text.primary1,
captionColor: Color = TangemTheme.colors.text.tertiary,
iconTint: Color = TangemTheme.colors.icon.informative,
isGrayscaleImage: Boolean = false,
iconEndRes: Int? = null,
@DrawableRes iconEndRes: Int? = null,
) {
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6),
@ -66,6 +69,8 @@ fun InputRowImageInfo(
subtitle = subtitle,
caption = caption,
imageUrl = imageUrl,
iconRes = iconRes,
iconTint = iconTint,
subtitleColor = subtitleColor,
captionColor = captionColor,
isGrayscaleImage = isGrayscaleImage,

View file

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="18dp" android:viewportHeight="18" android:viewportWidth="18" android:width="18dp">
<group>
<clip-path android:pathData="M0,0h18v18h-18z"/>
<path android:fillColor="#00000000" android:pathData="M14.25,3.75L16.5,1.5" android:strokeColor="#0099FF" android:strokeLineJoin="round" android:strokeWidth="1.5"/>
<path android:fillColor="#00000000" android:pathData="M1.5,16.5L3.75,14.25" android:strokeColor="#0099FF" android:strokeLineJoin="round" android:strokeWidth="1.5"/>
<path android:fillColor="#00000000" android:pathData="M4.725,15.225C4.892,15.393 5.091,15.526 5.31,15.617C5.529,15.708 5.763,15.754 6,15.754C6.237,15.754 6.472,15.708 6.69,15.617C6.909,15.526 7.108,15.393 7.275,15.225L9,13.5L4.5,9L2.775,10.725C2.607,10.892 2.474,11.091 2.383,11.31C2.292,11.528 2.246,11.763 2.246,12C2.246,12.237 2.292,12.472 2.383,12.69C2.474,12.909 2.607,13.108 2.775,13.275L4.725,15.225Z" android:strokeColor="#0099FF" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.5"/>
<path android:fillColor="#00000000" android:pathData="M6,10L8,8" android:strokeColor="#0099FF" android:strokeLineJoin="round" android:strokeWidth="1.5"/>
<path android:fillColor="#00000000" android:pathData="M8,12L10,10" android:strokeColor="#0099FF" android:strokeLineJoin="round" android:strokeWidth="1.5"/>
<path android:fillColor="#00000000" android:pathData="M9,4.5L13.5,9L15.225,7.275C15.393,7.107 15.526,6.909 15.617,6.69C15.708,6.471 15.754,6.236 15.754,6C15.754,5.763 15.708,5.528 15.617,5.309C15.526,5.09 15.393,4.892 15.225,4.725L13.275,2.775C13.108,2.607 12.909,2.474 12.69,2.383C12.472,2.292 12.237,2.245 12,2.245C11.763,2.245 11.528,2.292 11.31,2.383C11.091,2.474 10.892,2.607 10.725,2.775L9,4.5Z" android:strokeColor="#0099FF" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.5"/>
</group>
</vector>

View file

@ -18,6 +18,7 @@ internal class YieldBalanceConverter : Converter<YieldBalanceWrapperDTO, YieldBa
balance = YieldBalanceItem(
items = value.balances.map { item ->
BalanceItem(
id = item.groupId,
type = BalanceType.valueOf(item.type.name),
amount = item.amount,
pricePerShare = item.pricePerShare,

View file

@ -34,6 +34,7 @@ data class YieldBalanceItem(
)
data class BalanceItem(
val id: String,
val type: BalanceType,
val amount: BigDecimal,
val pricePerShare: BigDecimal,
@ -76,16 +77,21 @@ data class PendingAction(
}
}
enum class BalanceType {
AVAILABLE,
STAKED,
PREPARING,
UNSTAKING,
UNLOCKING,
UNSTAKED,
LOCKED,
REWARDS,
UNKNOWN,
/**
* IMPORTANT!!!
* Order is used to sort balances.
*/
@Suppress("MagicNumber")
enum class BalanceType(val order: Int) {
AVAILABLE(1),
STAKED(2),
PREPARING(3),
LOCKED(4),
UNSTAKING(5),
UNLOCKING(6),
UNSTAKED(7),
REWARDS(8),
UNKNOWN(9),
;
companion object {

View file

@ -9,37 +9,29 @@ import com.tangem.domain.staking.model.stakekit.Yield
import kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal
sealed class InnerYieldBalanceState {
internal sealed class InnerYieldBalanceState {
data class Data(
val rewardsCrypto: String,
val rewardsFiat: String,
val rewardBlockType: RewardBlockType,
val balance: ImmutableList<BalanceGroupedState>,
val balance: ImmutableList<BalanceState>,
) : InnerYieldBalanceState()
data object Empty : InnerYieldBalanceState()
}
// TODO staking get rid of unstable types
@Immutable
data class BalanceGroupedState(
val items: ImmutableList<BalanceState>,
val footer: TextReference?,
internal data class BalanceState(
val id: String,
val title: TextReference,
val type: BalanceType,
val subtitle: TextReference?,
val isClickable: Boolean,
)
@Immutable
data class BalanceState(
val validator: Yield.Validator?,
val title: TextReference,
val cryptoValue: String,
val cryptoDecimal: BigDecimal,
val cryptoAmount: TextReference,
val fiatAmount: TextReference,
val rawCurrencyId: String?,
val unbondingPeriod: TextReference,
val warmupPeriod: TextReference,
val validator: Yield.Validator?,
val pendingActions: ImmutableList<PendingAction>,
)

View file

@ -0,0 +1,125 @@
package com.tangem.features.staking.impl.presentation.state.converters
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
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.BalanceType.Companion.isClickable
import com.tangem.domain.staking.model.stakekit.Yield
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.utils.Provider
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toPersistentList
import org.joda.time.DateTime
import java.util.Calendar
internal class BalanceItemConverter(
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val yield: Yield,
) : Converter<BalanceItem, BalanceState?> {
override fun convert(value: BalanceItem): BalanceState? {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val appCurrency = appCurrencyProvider()
val cryptoCurrency = cryptoCurrencyStatus.currency
val validator = yield.validators.firstOrNull {
value.validatorAddress?.contains(it.address, ignoreCase = true) == true
}
val cryptoAmount = value.amount
val fiatAmount = cryptoCurrencyStatus.value.fiatRate?.times(cryptoAmount)
val title = value.type.getTitle(validator?.name)
return title?.let {
BalanceState(
id = value.id,
validator = validator,
title = title,
subtitle = getSubtitle(value),
type = value.type,
cryptoValue = cryptoAmount.parseBigDecimal(cryptoCurrency.decimals),
cryptoDecimal = cryptoAmount,
cryptoAmount = stringReference(
BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = cryptoAmount,
cryptoCurrency = cryptoCurrency,
),
),
fiatAmount = stringReference(
BigDecimalFormatter.formatFiatAmount(
fiatAmount = fiatAmount,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
),
),
rawCurrencyId = value.rawCurrencyId,
pendingActions = value.pendingActions.toPersistentList(),
isClickable = value.type.isClickable(),
)
}
}
private fun BalanceType.getTitle(validatorName: String?) = when (this) {
BalanceType.STAKED -> validatorName?.let { stringReference(it) }
BalanceType.PREPARING -> resourceReference(R.string.staking_preparing)
BalanceType.UNSTAKED -> resourceReference(R.string.staking_unstaked)
BalanceType.UNSTAKING -> resourceReference(R.string.staking_unstaking)
BalanceType.LOCKED -> resourceReference(R.string.staking_locked)
BalanceType.AVAILABLE,
BalanceType.REWARDS,
BalanceType.UNLOCKING,
BalanceType.UNKNOWN,
-> null
}
private fun getSubtitle(balance: BalanceItem) = when (balance.type) {
BalanceType.UNSTAKING -> getUnbondingDate(balance.date)
BalanceType.UNSTAKED -> resourceReference(R.string.staking_tap_to_withdraw)
BalanceType.LOCKED -> resourceReference(R.string.staking_tap_to_unlock)
BalanceType.AVAILABLE,
BalanceType.STAKED,
BalanceType.PREPARING,
BalanceType.UNLOCKING,
BalanceType.REWARDS,
BalanceType.UNKNOWN,
-> null
}
private fun getUnbondingDate(date: DateTime?): TextReference {
val now = DateTime.now().millis
val nowCalendar = Calendar.getInstance()
nowCalendar.resetHours()
val endDate = Calendar.getInstance()
endDate.timeInMillis = date?.millis ?: now
endDate.resetHours()
val days = ((endDate.timeInMillis - nowCalendar.timeInMillis) / DAY_IN_MILLIS).toInt()
return if (days > 0) {
resourceReference(
R.string.common_left,
wrappedList(
pluralReference(R.plurals.common_days, days, wrappedList(days)),
),
)
} else {
resourceReference(R.string.common_today)
}
}
private fun Calendar.resetHours() {
this[Calendar.HOUR_OF_DAY] = 0
this[Calendar.MINUTE] = 0
this[Calendar.SECOND] = 0
this[Calendar.MILLISECOND] = 0
}
private companion object {
const val DAY_IN_MILLIS = 24 * 60 * 60 * 1000
}
}

View file

@ -1,19 +1,18 @@
package com.tangem.features.staking.impl.presentation.state.converters
import com.tangem.core.ui.extensions.pluralReference
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
import com.tangem.domain.staking.model.stakekit.*
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.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.converter.Converter
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
import java.math.BigDecimal
@ -49,23 +48,21 @@ internal class RewardsValidatorStateConverter(
val fiatValue = cryptoCurrencyStatus.value.fiatRate?.times(cryptoValue)
validator?.toBalanceState(
balance = balance,
cryptoCurrencyStatus = cryptoCurrencyStatus,
cryptoValue = cryptoValue,
fiatValue = fiatValue,
pendingActions = balance.pendingActions.toPersistentList(),
)
}
private fun Yield.Validator.toBalanceState(
balance: BalanceItem,
cryptoCurrencyStatus: CryptoCurrencyStatus,
cryptoValue: BigDecimal,
fiatValue: BigDecimal?,
pendingActions: ImmutableList<PendingAction>,
): BalanceState {
val appCurrency = appCurrencyProvider()
val cryptoCurrency = cryptoCurrencyStatus.currency
val unbondingPeriod = yield.metadata.cooldownPeriod.days
val warmupPeriod = yield.metadata.warmupPeriod.days
val cryptoAmount = stringReference(
BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = cryptoValue,
@ -81,24 +78,18 @@ internal class RewardsValidatorStateConverter(
)
return BalanceState(
id = balance.id,
validator = this,
title = stringReference(this.name),
subtitle = null,
cryptoValue = cryptoValue.parseBigDecimal(cryptoCurrency.decimals),
cryptoDecimal = cryptoValue,
cryptoAmount = cryptoAmount,
fiatAmount = fiatAmount,
rawCurrencyId = cryptoCurrency.id.rawCurrencyId,
unbondingPeriod = pluralReference(
id = R.plurals.common_days,
count = unbondingPeriod,
formatArgs = wrappedList(unbondingPeriod),
),
warmupPeriod = pluralReference(
id = R.plurals.common_days,
count = unbondingPeriod,
formatArgs = wrappedList(warmupPeriod),
),
pendingActions = pendingActions,
pendingActions = balance.pendingActions.toPersistentList(),
isClickable = true,
type = balance.type,
)
}
}

View file

@ -1,29 +1,26 @@
package com.tangem.features.staking.impl.presentation.state.converters
import com.tangem.common.extensions.isZero
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
import com.tangem.domain.staking.model.stakekit.*
import com.tangem.domain.staking.model.stakekit.BalanceType.Companion.isClickable
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.R
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.lib.crypto.BlockchainUtils.isSolana
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toPersistentList
import org.joda.time.DateTime
import java.util.Calendar
internal class YieldBalancesConverter(
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val yield: Yield,
) : Converter<Unit, InnerYieldBalanceState> {
private val balanceItemConverter by lazy(LazyThreadSafetyMode.NONE) {
BalanceItemConverter(cryptoCurrencyStatusProvider, appCurrencyProvider, yield)
}
override fun convert(value: Unit): InnerYieldBalanceState {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val appCurrency = appCurrencyProvider()
@ -34,7 +31,6 @@ internal class YieldBalancesConverter(
return if (yieldBalance is YieldBalance.Data) {
val cryptoRewardsValue = yieldBalance.getRewardStakingBalance()
val fiatRewardsValue = cryptoCurrencyStatus.value.fiatRate?.times(cryptoRewardsValue)
val groupedBalances = getGroupedBalance(yieldBalance.balance)
InnerYieldBalanceState.Data(
rewardsCrypto = BigDecimalFormatter.formatCryptoAmount(
@ -47,121 +43,18 @@ internal class YieldBalancesConverter(
fiatCurrencySymbol = appCurrency.symbol,
),
rewardBlockType = getRewardBlockType(),
balance = groupedBalances,
balance = yieldBalance.balance.items.mapBalances(),
)
} else {
InnerYieldBalanceState.Empty
}
}
private fun getGroupedBalance(balance: YieldBalanceItem) = balance.items
.sortedBy { it.type }
.groupBy { it.type.toGroup() }
.mapNotNull { item ->
val (title, footer) = getGroupTitle(item.key)
val isClickable = item.key.isClickable()
title?.let {
BalanceGroupedState(
items = item.value.mapBalances(item.key).toPersistentList(),
footer = footer,
title = it,
type = item.key,
isClickable = isClickable,
)
}
}
.filterNot { it.items.isEmpty() }
private fun List<BalanceItem>.mapBalances() = sortedBy { it.type.order }
.filterNot { it.amount.isZero() || it.type == BalanceType.REWARDS }
.mapNotNull(balanceItemConverter::convert)
.toPersistentList()
private fun List<BalanceItem>.mapBalances(balanceType: BalanceType): List<BalanceState> {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val appCurrency = appCurrencyProvider()
val cryptoCurrency = cryptoCurrencyStatus.currency
var incrementingIndex = 1
return this
.filterNot { it.amount.isZero() }
.mapNotNull { balance ->
val validator = yield.validators.firstOrNull {
balance.validatorAddress?.contains(it.address, ignoreCase = true) == true
}
val cryptoAmount = balance.amount
val fiatAmount = cryptoCurrencyStatus.value.fiatRate?.times(cryptoAmount)
val unbonding = getUnbondingDate(balance.date)
val warmupPeriod = yield.metadata.warmupPeriod.days
val title = when {
validator != null -> stringReference(validator.name)
balanceType == BalanceType.UNSTAKING -> {
resourceReference(R.string.staking_unstaking_item_name, wrappedList(incrementingIndex++))
}
else -> null
}
title?.let {
BalanceState(
validator = validator,
title = title,
cryptoValue = cryptoAmount.parseBigDecimal(cryptoCurrency.decimals),
cryptoDecimal = cryptoAmount,
cryptoAmount = stringReference(
BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = cryptoAmount,
cryptoCurrency = cryptoCurrency,
),
),
fiatAmount = stringReference(
BigDecimalFormatter.formatFiatAmount(
fiatAmount = fiatAmount,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
),
),
rawCurrencyId = balance.rawCurrencyId,
unbondingPeriod = unbonding,
warmupPeriod = pluralReference(R.plurals.common_days, warmupPeriod, wrappedList(warmupPeriod)),
pendingActions = balance.pendingActions.toPersistentList(),
)
}
}
}
private fun BalanceType.toGroup() = when (this) {
BalanceType.REWARDS,
BalanceType.UNKNOWN,
-> BalanceType.UNKNOWN
else -> this
}
private fun getGroupTitle(type: BalanceType) = when (type) {
BalanceType.STAKED -> resourceReference(R.string.staking_active) to
resourceReference(R.string.staking_active_footer)
BalanceType.UNSTAKED -> resourceReference(R.string.staking_unstaked) to
resourceReference(R.string.staking_unstaked_footer)
BalanceType.UNSTAKING -> resourceReference(R.string.staking_unstaking) to null
BalanceType.LOCKED -> resourceReference(R.string.staking_locked) to null
BalanceType.AVAILABLE -> null to null
BalanceType.PREPARING -> resourceReference(R.string.staking_preparing) to null
BalanceType.REWARDS -> null to null
BalanceType.UNLOCKING -> null to null
BalanceType.UNKNOWN -> null to null
}
private fun getUnbondingDate(date: DateTime?): TextReference {
val now = DateTime.now().millis
val nowCalendar = Calendar.getInstance()
nowCalendar.resetHours()
val endDate = Calendar.getInstance()
endDate.timeInMillis = date?.millis ?: now
endDate.resetHours()
val days = ((endDate.timeInMillis - nowCalendar.timeInMillis) / DAY_IN_MILLIS).toInt()
return if (days > 0) {
pluralReference(R.plurals.common_in_days, days, wrappedList(days))
} else {
resourceReference(R.string.common_today)
}
}
private fun getRewardBlockType(): RewardBlockType {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
@ -178,15 +71,4 @@ internal class YieldBalancesConverter(
else -> RewardBlockType.NoRewards
}
}
private fun Calendar.resetHours() {
this[Calendar.HOUR_OF_DAY] = 0
this[Calendar.MINUTE] = 0
this[Calendar.SECOND] = 0
this[Calendar.MILLISECOND] = 0
}
private companion object {
const val DAY_IN_MILLIS = 24 * 60 * 60 * 1000
}
}

View file

@ -6,9 +6,9 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.RewardBlockType
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.Yield.Validator.ValidatorStatus
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.*
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 kotlinx.collections.immutable.persistentListOf
@ -63,36 +63,30 @@ internal object InitialStakingStatePreview {
rewardsCrypto = "100 SOL",
rewardBlockType = RewardBlockType.RewardUnavailable,
balance = persistentListOf(
BalanceGroupedState(
title = stringReference("Staked"),
footer = null,
type = BalanceType.STAKED,
isClickable = true,
items = persistentListOf(
BalanceState(
title = stringReference("Binance"),
cryptoValue = "100",
cryptoAmount = stringReference("100 SOL"),
cryptoDecimal = "100".toBigDecimal(),
fiatAmount = stringReference("100 $"),
rawCurrencyId = null,
validator = Yield.Validator(
address = "address",
status = ValidatorStatus.ACTIVE,
name = "Binance",
image = null,
website = null,
apr = "5".toBigDecimal(),
commission = null,
stakedBalance = null,
votingPower = null,
preferred = false,
),
unbondingPeriod = stringReference("3 days"),
pendingActions = persistentListOf(),
warmupPeriod = stringReference("3 days"),
),
BalanceState(
id = "id",
title = stringReference("Binance"),
cryptoValue = "100",
cryptoAmount = stringReference("100 SOL"),
cryptoDecimal = "100".toBigDecimal(),
fiatAmount = stringReference("100 $"),
rawCurrencyId = null,
validator = Yield.Validator(
address = "address",
status = Yield.Validator.ValidatorStatus.ACTIVE,
name = "Binance",
image = null,
website = null,
apr = "5".toBigDecimal(),
commission = null,
stakedBalance = null,
votingPower = null,
preferred = false,
),
pendingActions = persistentListOf(),
isClickable = true,
type = BalanceType.STAKED,
subtitle = null,
),
),
),

View file

@ -32,7 +32,6 @@ import androidx.compose.ui.unit.Density
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.containers.FooterContainer
import com.tangem.core.ui.components.inputrow.InputRowDefault
import com.tangem.core.ui.components.inputrow.InputRowImageInfo
import com.tangem.core.ui.components.list.roundedListWithDividersItems
@ -44,7 +43,6 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.RewardBlockType
import com.tangem.features.staking.impl.R
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
@ -198,53 +196,43 @@ private fun StakingRewardBlock(
}
@Composable
private fun ActiveStakingBlock(groups: ImmutableList<BalanceGroupedState>, onClick: (BalanceState) -> Unit) {
private fun ActiveStakingBlock(balances: ImmutableList<BalanceState>, onClick: (BalanceState) -> Unit) {
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
modifier = Modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action),
) {
groups.forEach { group ->
key(group.title) {
FooterContainer(
footer = group.footer?.resolveReference(),
modifier = Modifier,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action),
) {
Text(
text = group.title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.padding(
top = TangemTheme.dimens.spacing12,
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
),
)
group.items.forEachIndexed { index, balance ->
key(balance.title.resolveReference() + index) {
InputRowImageInfo(
subtitle = balance.title,
caption = getCaption(group.type, balance),
isGrayscaleImage = !group.isClickable,
infoTitle = balance.fiatAmount,
infoSubtitle = balance.cryptoAmount,
imageUrl = balance.validator?.image,
iconEndRes = R.drawable.ic_chevron_right_24.takeIf { group.isClickable },
modifier = Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = group.isClickable,
onClick = { onClick(balance) },
),
)
}
}
}
}
Text(
text = stringResource(id = R.string.staking_your_stakes),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.padding(
top = TangemTheme.dimens.spacing12,
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing4,
),
)
balances.forEach { balance ->
key(balance.id) {
val (icon, iconTint) = balance.type.getIcon()
InputRowImageInfo(
subtitle = balance.title,
caption = balance.subtitle ?: balance.getAprText(),
isGrayscaleImage = !balance.isClickable,
infoTitle = balance.fiatAmount,
infoSubtitle = balance.cryptoAmount,
imageUrl = balance.getImage(),
iconRes = icon,
iconTint = iconTint,
modifier = Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
enabled = balance.isClickable,
onClick = { onClick(balance) },
),
)
}
}
}
@ -266,51 +254,35 @@ private fun StakeButtonBlock(buttonState: NavigationButtonsState) {
}
@Composable
private fun getCaption(balanceType: BalanceType, balance: BalanceState): TextReference {
return when (balanceType) {
BalanceType.UNSTAKING -> {
combinedReference(
resourceReference(R.string.staking_unbonding),
annotatedReference {
appendSpace()
appendColored(
text = balance.unbondingPeriod.resolveReference(),
color = TangemTheme.colors.text.accent,
)
},
)
}
BalanceType.UNSTAKED -> {
resourceReference(R.string.staking_ready_to_withdraw)
}
BalanceType.PREPARING -> {
combinedReference(
resourceReference(R.string.staking_details_warmup_period),
annotatedReference {
appendSpace()
appendColored(
text = balance.warmupPeriod.resolveReference(),
color = TangemTheme.colors.text.accent,
)
},
)
}
else -> {
combinedReference(
resourceReference(R.string.staking_details_apr),
annotatedReference {
appendSpace()
appendColored(
text = BigDecimalFormatter.formatPercent(
percent = balance.validator?.apr.orZero(),
useAbsoluteValue = true,
),
color = TangemTheme.colors.text.accent,
)
},
)
}
}
private fun BalanceState.getAprText() = combinedReference(
resourceReference(R.string.staking_details_apr),
annotatedReference {
appendSpace()
appendColored(
text = BigDecimalFormatter.formatPercent(
percent = validator?.apr.orZero(),
useAbsoluteValue = true,
),
color = TangemTheme.colors.text.accent,
)
},
)
@Composable
private fun BalanceType.getIcon() = when (this) {
BalanceType.UNSTAKING -> R.drawable.ic_connection_18 to TangemTheme.colors.icon.accent
BalanceType.UNSTAKED -> R.drawable.ic_connection_18 to TangemTheme.colors.icon.informative
BalanceType.LOCKED -> R.drawable.ic_lock_24 to TangemTheme.colors.icon.informative
else -> null to TangemTheme.colors.icon.informative
}
@Composable
private fun BalanceState.getImage() = when (type) {
BalanceType.UNSTAKING,
BalanceType.UNSTAKED,
BalanceType.LOCKED,
-> null
else -> validator?.image
}
private val textGradientColors = listOf(