diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 2f740dd9d7..a50e97f246 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -115,10 +115,6 @@ Zum Anbieter gehen Zum Token Importieren - - in %d Tag - in %d Tagen - Später Gesperrt Hauptnetz @@ -641,8 +637,6 @@ Entferne diese Wallet Hiermit wird die Wallet aus der Anwendung entfernt. Die Wallet selbst kann wieder hinzugefügt werden. Name - Aktiv - Um deine Vermögenswerte freizugeben, tippe auf den Block oben Die Anzahl der zu stakenden Krypros muss mindesten %s betragen Nicht gestakte beanspruche Jährliche prozentuale Rendite @@ -702,7 +696,6 @@ Lösen der Bindungen Gelocktes unlocken Unstaken - Prüfe, was nicht eingesetzt wurde, um dein Vermögen zu beanspruchen Staking beenden Validator/ Prüfer Validatoren diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 87595dbdf6..dd3a253e57 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -115,10 +115,6 @@ Aller au fournisseur Aller au jeton Importez - - dans %d jour - dans %d jours - Plus tard Verrouillé Réseau principal @@ -637,8 +633,6 @@ Oublier le portefeuille Cela supprimera le portefeuille de l\'application. Le portefeuille lui-même peut être ajouté à nouveau. Nom - Actif - Afin d\'unstaker vos actifs, appuyez sur le bloc ci-dessus Le montant à staker doit être au moins %s Réclamation déstakée Pourcentage de rendement annuel @@ -697,7 +691,6 @@ Dissociation Débloquer Non-staké - Vérifiez non-stakés pour réclamer vos actifs Unstaking Validateur Validateurs diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 33f3202506..5e94e5d5f8 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -83,6 +83,7 @@ カメラへのアクセスを許可していません。プライバシー設定を調整してください。 キャンセル アクションを選択 + 請求 報酬を受け取る 閉じる 続ける @@ -113,9 +114,6 @@ プロバイダーへ移動 トークンへ移動 インポート - - %d日で - 後で ロックされています メインネットワーク @@ -628,8 +626,6 @@ ウォレット削除 これにより、ウォレットがアプリから削除されます。ウォレットは再度追加できます。 名前 - アクティブ - 資産のステーキングを解除するには、上のブロックをタップしてください ステーキング金額は %s 以上である必要があります ステーキング解除分を請求する 年率 @@ -654,14 +650,17 @@ ステーキングから資金の引き出しを要求した後、トークンが利用可能になるまでの待機期間。 ウォームアップ期間 ステーキングへの参加を有効にするために割り当てられた時間。 + ステーキング機能を使用すると、プロバイダーの%1$sと%2$sに同意したことになります ロック中 移行 ネイティブステーキング + 獲得した報酬はあなたのアドレスに直接送られます。受け取り後、資金は使用可能になります。 ステーキングにより%1$sを獲得できます。ステーキング報酬は毎日受け取れます。 ステーキングにより%1$sを獲得できます。ステーキング報酬は毎時間受け取れます。 ステーキングにより%1$sを獲得できます。ステーキング報酬は毎月受け取れます。 ステーキングにより%1$sを獲得できます。ステーキング報酬は毎週受け取れます。 ステーキング報酬を獲得 + 獲得した報酬をステーキングに再投資し、潜在的な収益を増やします。 ステーキング解除プロセスの開始後、報酬の獲得はすぐに停止します。ステーキング解除には%sかかります。 あなたの資金はステーキング解除されました。獲得した報酬とともに引き出すことができます。 準備中 @@ -684,13 +683,14 @@ 報酬 ステーキングはロックされています もっとステーキングする + %1$sをステーキングし、年間 ~ %2$sを受け取ります %sをステーキングする %sのステーキング解除 ステーキング解約中 ステーキング解除はロックされています ステーキングされていない - 資産を請求するために、unstakedを確認してください ステーキング解除 + ステーキング解除中の資産%s バリデーター バリデーター 投票する diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 4d2fc19ca5..9011127a90 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -117,12 +117,6 @@ К провайдеру Перейти в токен Импортировать - - через %d день - через %d дня - через %d дней - через %d дней - Позже Заблокирован Основная сеть @@ -645,8 +639,6 @@ Забыть кошелек Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова. Имя - Активно - Для завершения стейкинга нажмите на блок выше Сумма для стейкинга должна быть не менее %s Забрать средства Процентная ставка @@ -701,7 +693,6 @@ Отзыв Разблокировать Вывод из стейкинга - Проверьте процесс завершения стейкинга, чтобы вывести свои средства. Завершение стейкинга Завершение стейкинга %s Валидатор diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index ca0bc61e34..96f929750f 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -642,8 +642,6 @@ Забути гаманець Це призведе до видалення гаманця з застосунку. Сам гаманець можна додати знову. Ім\'я - Активний - Для завершення стейкінгу натисніть на блок вище Сума для стейкінгу має бути не менше %s Зняти кошти Процентна ставка @@ -696,7 +694,6 @@ Зняти зі стейкінгу %s Розблокувати Вивід зі стейкінгу - Перевірити незастейкані, щоб отримати свої активи Зняти зі стейкінгу Валідатор Проголосувати diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index b89491b528..6c64f890e2 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -116,11 +116,8 @@ Go to provider Go to token Import - - in %d day - in %d days - Later + %1$s left Locked Main network Network fee @@ -414,6 +411,8 @@ The maximum number of coins or tokens that can ever exist for a particular cryptocurrency Trading volume (24h) 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 + Pull this up or tap the search bar to add tokens directly from the market + Add tokens You have to set up a single access code to protect all your cards Protect You can set up an individual access code on each card later @@ -638,8 +637,6 @@ Forget wallet This will remove the wallet from the application. The wallet itself can be added again. Name - Active - To unstake your assets, tap the block above The amount to stake must be at least %s Claim unstaked Annual percentage rate @@ -698,12 +695,13 @@ Stake locked Stake more You stake %1$s and will be receiving ~%2$s yearly + Tap to unlock + Tap to withdraw Stake %s Unstake %s Unbonding Unlock locked Unstaked - Check unstaked to claim your assets Unstaking Unstaking assets %s Validator @@ -711,6 +709,7 @@ Vote Vote locked Withdraw + Your stakes Store your crypto assets secure while keeping private keys contained in your card Revolutionary Hardware Wallet Up to 3 physical cards to one wallet diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt index fd4913770c..0991dcdf5a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt @@ -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(), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt index 16ea439603..19bb005a00 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt @@ -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, diff --git a/core/ui/src/main/res/drawable/ic_connection_18.xml b/core/ui/src/main/res/drawable/ic_connection_18.xml new file mode 100644 index 0000000000..80d76d5158 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_connection_18.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt index 24eae9474a..70577561ed 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt @@ -18,6 +18,7 @@ internal class YieldBalanceConverter : Converter BalanceItem( + id = item.groupId, type = BalanceType.valueOf(item.type.name), amount = item.amount, pricePerShare = item.pricePerShare, diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt index 60cfb91e45..c789da63b3 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt @@ -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 { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt index 41fa1c3862..c989de7fb1 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt @@ -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, + val balance: ImmutableList, ) : InnerYieldBalanceState() data object Empty : InnerYieldBalanceState() } -// TODO staking get rid of unstable types @Immutable -data class BalanceGroupedState( - val items: ImmutableList, - 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, ) \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt new file mode 100644 index 0000000000..c6317bf05d --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt @@ -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, + private val appCurrencyProvider: Provider, + private val yield: Yield, +) : Converter { + 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 + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt index 987866f39b..e3cc2f8e07 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt @@ -1,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, ): 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, ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index 856bec78cc..d948580b77 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -1,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, private val appCurrencyProvider: Provider, private val yield: Yield, ) : Converter { + + 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.mapBalances() = sortedBy { it.type.order } + .filterNot { it.amount.isZero() || it.type == BalanceType.REWARDS } + .mapNotNull(balanceItemConverter::convert) .toPersistentList() - private fun List.mapBalances(balanceType: BalanceType): List { - 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 - } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt index a6ac4963e9..c23cb74fda 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt @@ -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, ), ), ), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 5e2f5c710b..530716b3fe 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -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, onClick: (BalanceState) -> Unit) { +private fun ActiveStakingBlock(balances: ImmutableList, 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(