diff --git a/common/test/src/main/java/com/tangem/common/test/data/staking/MockYieldDTOFactory.kt b/common/test/src/main/java/com/tangem/common/test/data/staking/MockYieldDTOFactory.kt
index 52d77fa3a1..9ad0a6c814 100644
--- a/common/test/src/main/java/com/tangem/common/test/data/staking/MockYieldDTOFactory.kt
+++ b/common/test/src/main/java/com/tangem/common/test/data/staking/MockYieldDTOFactory.kt
@@ -73,7 +73,7 @@ object MockYieldDTOFactory {
type = "type",
rewardSchedule = YieldDTO.MetadataDTO.RewardScheduleDTO.DAY,
cooldownPeriod = null,
- warmupPeriod = YieldDTO.MetadataDTO.PeriodDTO(1),
+ warmupPeriod = YieldDTO.MetadataDTO.PeriodDTO(1, seconds = null),
rewardClaiming = YieldDTO.MetadataDTO.RewardClaimingDTO.AUTO,
defaultValidator = null,
minimumStake = null,
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt
index be0fb0cf91..67bae3eb18 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt
@@ -150,6 +150,8 @@ data class YieldDTO(
data class PeriodDTO(
@Json(name = "days")
val days: Int?,
+ @Json(name = "seconds")
+ val seconds: Int?,
)
@JsonClass(generateAdapter = true)
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index b8e2e9e0e4..5f7b583b7d 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -326,6 +326,12 @@
Скрыть
Удерживайте, чтобы %s
час
+
+ - %d час
+ - %d часa
+ - %d часов
+ - %d часа
+
- %dч назад
- %dч назад
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 403712fc0c..fe8fe609f3 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -217,6 +217,7 @@
Access denied
Account
Accounts
+ %s failed
Activate
Add
Add funds
@@ -233,6 +234,8 @@
Apply
Approval
Approve
+ Approved
+ Approving
Attention
Available networks
Backup
@@ -315,6 +318,10 @@
Hide
Hold to %s
hour
+
+ - %d hour
+ - %d hours
+
- %dh ago
- %dh ago
@@ -364,6 +371,8 @@
%1$s — %2$s
Read more
Receive
+ Received
+ Receiving
Recommended
Reject
Reload
@@ -382,6 +391,8 @@
Send
Send:
Failed to send transaction
+ Sending
+ Sent
The server is not available, please try again later
Share
Share Link
@@ -392,6 +403,7 @@
Skip
Something went wrong
Stake
+ Staked
Staking
Start
Submit
@@ -399,6 +411,8 @@
Support
Supported networks
Swap
+ Swapped
+ Swapping
Tangem
Tangem Wallet
Tap and hold
@@ -416,6 +430,7 @@
Transaction status
Transactions
Transfer
+ Transferred
Unable to load data…
I understand
I understand, continue
@@ -425,10 +440,12 @@
Unstake
Due to %1$s limitations only %2$d UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount.
Value copied
+ Voting
Wallets
Warning
week
with
+ Withdrawing
Yes
Yield Mode
Contract address copied!
@@ -590,6 +607,7 @@
Best rate
FCA Warning List
Fixed rate is unavailable
+ Provider for swap
Competitive rate
Provider in FCA warning list
Available up to %s
@@ -1334,6 +1352,8 @@
Target account is not created. Please change the amount to send.
The amount to send must be at least %s
Leave %s
+ A trustline for %s is required first.
+ Can\'t receive token
Reduce by %s
Reduce to %s
Kindly be aware that your transaction may experience delays under specific fee settings
@@ -1820,12 +1840,21 @@
Unavailable to sell
Unavailable for swap from %s
Unavailable for swap
+ Claiming reward
contract: %s
+ Disabling Yield mode
+ Earned from stake
You don\'t have any transactions yet
Failed to load transaction history.\nClick on reload button to update the information.
+ from: %%image%% %s
Multiple addresses
Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer.
Operation
+ Pending
+ Rewards restaked
+ Rewards restaking
+ Staking reward
+ to: %%image%% %s
for: %s
from: %s
to: %s
diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt
index 6c5be9aecd..85378d4bc1 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt
@@ -8,6 +8,8 @@ import org.joda.time.DateTimeZone
import org.joda.time.LocalDate
import org.joda.time.format.DateTimeFormatter
+const val SECONDS_IN_HOUR = 3600
+
/**
* If [this] timestamp is today or yesterday, returns relative date,
* otherwise returns formatting date.
diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt
index 9766779759..08217059f4 100644
--- a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt
+++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt
@@ -117,6 +117,7 @@ internal object YieldConverter : Converter {
private fun convertPeriod(periodDTO: YieldDTO.MetadataDTO.PeriodDTO): Yield.Metadata.Period {
return Yield.Metadata.Period(
days = periodDTO.days.asMandatory("days"),
+ seconds = periodDTO.seconds,
)
}
diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt
index e1cf32955b..fc9daf4a8e 100644
--- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt
+++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt
@@ -114,6 +114,7 @@ data class Yield(
@Serializable
data class Period(
val days: Int,
+ val seconds: Int?,
)
@Serializable
diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/CooldownPeriod.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/CooldownPeriod.kt
index bc66de0687..ea4ef686c0 100644
--- a/domain/staking/src/main/java/com/tangem/domain/staking/model/CooldownPeriod.kt
+++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/CooldownPeriod.kt
@@ -2,7 +2,7 @@ package com.tangem.domain.staking.model
sealed class CooldownPeriod {
- data class Fixed(val days: Int) : CooldownPeriod()
+ data class Fixed(val period: Period) : CooldownPeriod()
data class Range(val minDays: Int, val maxDays: Int) : CooldownPeriod()
}
\ No newline at end of file
diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/P2PEthPoolIntegration.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/P2PEthPoolIntegration.kt
index 7f779afa4e..cf61efdcff 100644
--- a/domain/staking/src/main/java/com/tangem/domain/staking/model/P2PEthPoolIntegration.kt
+++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/P2PEthPoolIntegration.kt
@@ -62,7 +62,7 @@ class P2PEthPoolIntegration(
// Metadata
- override val warmupPeriodDays: Int = 0
+ override val warmupPeriod: Period = Period.Days(0)
override val cooldownPeriod: CooldownPeriod = CooldownPeriod.Range(
minDays = MIN_COOLDOWN_DAYS,
diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/Period.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/Period.kt
new file mode 100644
index 0000000000..e54d402e00
--- /dev/null
+++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/Period.kt
@@ -0,0 +1,14 @@
+package com.tangem.domain.staking.model
+
+sealed class Period {
+
+ abstract val value: Int
+
+ data class Days(
+ override val value: Int,
+ ) : Period()
+
+ data class Seconds(
+ override val value: Int,
+ ) : Period()
+}
\ No newline at end of file
diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakeKitIntegration.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakeKitIntegration.kt
index 185d52ea8b..8f1017dddb 100644
--- a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakeKitIntegration.kt
+++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakeKitIntegration.kt
@@ -49,10 +49,22 @@ class StakeKitIntegration(
// Metadata
- override val warmupPeriodDays: Int = yield.metadata.warmupPeriod.days
+ override val warmupPeriod: Period = yield.metadata.warmupPeriod.let { period ->
+ period.seconds?.let {
+ Period.Seconds(it)
+ } ?: period.days.let {
+ Period.Days(it)
+ }
+ }
- override val cooldownPeriod: CooldownPeriod? = yield.metadata.cooldownPeriod?.days?.let {
- CooldownPeriod.Fixed(it)
+ override val cooldownPeriod: CooldownPeriod? = yield.metadata.cooldownPeriod?.let { period ->
+ CooldownPeriod.Fixed(
+ period.seconds?.let {
+ Period.Seconds(it)
+ } ?: period.days.let {
+ Period.Days(it)
+ },
+ )
}
override val rewardSchedule: RewardSchedule = yield.metadata.rewardSchedule.toRewardSchedule()
diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegration.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegration.kt
index 12895e0629..8acc7d79ca 100644
--- a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegration.kt
+++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegration.kt
@@ -43,7 +43,7 @@ sealed interface StakingIntegration {
// Metadata
- val warmupPeriodDays: Int
+ val warmupPeriod: Period
val cooldownPeriod: CooldownPeriod?
diff --git a/domain/staking/src/test/kotlin/com/tangem/domain/staking/model/StakeKitIntegrationTest.kt b/domain/staking/src/test/kotlin/com/tangem/domain/staking/model/StakeKitIntegrationTest.kt
new file mode 100644
index 0000000000..26f80a5941
--- /dev/null
+++ b/domain/staking/src/test/kotlin/com/tangem/domain/staking/model/StakeKitIntegrationTest.kt
@@ -0,0 +1,206 @@
+package com.tangem.domain.staking.model
+
+import com.google.common.truth.Truth.assertThat
+import com.tangem.domain.models.staking.NetworkType
+import com.tangem.domain.models.staking.YieldToken
+import com.tangem.domain.staking.model.stakekit.AddressArgument
+import com.tangem.domain.staking.model.stakekit.Yield
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import java.math.BigDecimal
+
+/**
+ * Tests for [StakeKitIntegration] — specifically the Period/CooldownPeriod mapping from [Yield.Metadata].
+ */
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+internal class StakeKitIntegrationTest {
+
+ // region helpers
+
+ private val dummyToken = YieldToken(
+ name = "Test Token",
+ network = NetworkType.SOLANA,
+ symbol = "SOL",
+ decimals = 9,
+ address = null,
+ coinGeckoId = null,
+ logoURI = null,
+ isPoints = false,
+ )
+
+ private val dummyEnter = Yield.Args.Enter(
+ addresses = Yield.Args.Enter.Addresses(
+ address = AddressArgument(required = false),
+ ),
+ args = emptyMap(),
+ )
+
+ private val dummyArgs = Yield.Args(enter = dummyEnter, exit = null)
+
+ private val dummyStatus = Yield.Status(enter = true, exit = null)
+
+ private val dummyEnabled = Yield.Metadata.Enabled(enabled = true)
+
+ private fun buildYield(
+ warmupPeriod: Yield.Metadata.Period,
+ cooldownPeriod: Yield.Metadata.Period?,
+ ): Yield {
+ return Yield(
+ id = "test-integration",
+ token = dummyToken,
+ tokens = emptyList(),
+ args = dummyArgs,
+ status = dummyStatus,
+ apy = BigDecimal("5.0"),
+ rewardRate = 5.0,
+ rewardType = com.tangem.domain.staking.model.common.RewardType.APY,
+ metadata = Yield.Metadata(
+ name = "Test Staking",
+ logoUri = "https://example.com/logo.png",
+ description = "Test staking integration",
+ documentation = null,
+ gasFeeToken = dummyToken,
+ token = dummyToken,
+ tokens = emptyList(),
+ type = "liquid",
+ rewardSchedule = Yield.Metadata.RewardSchedule.DAY,
+ cooldownPeriod = cooldownPeriod,
+ warmupPeriod = warmupPeriod,
+ rewardClaiming = Yield.Metadata.RewardClaiming.AUTO,
+ defaultValidator = null,
+ minimumStake = null,
+ supportsMultipleValidators = false,
+ revshare = dummyEnabled,
+ fee = dummyEnabled,
+ ),
+ validators = emptyList(),
+ isAvailable = true,
+ )
+ }
+
+ private fun buildIntegration(
+ warmupPeriod: Yield.Metadata.Period,
+ cooldownPeriod: Yield.Metadata.Period?,
+ ): StakeKitIntegration {
+ return StakeKitIntegration(
+ integrationId = StakingIntegrationID.StakeKit.Coin.Solana,
+ yield = buildYield(warmupPeriod = warmupPeriod, cooldownPeriod = cooldownPeriod),
+ )
+ }
+
+ // endregion
+
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ inner class `warmupPeriod mapping` {
+
+ @Test
+ fun `should produce Period Seconds when seconds is non-null`() {
+ // given
+ val warmup = Yield.Metadata.Period(days = 3, seconds = 7200)
+
+ // when
+ val integration = buildIntegration(warmupPeriod = warmup, cooldownPeriod = null)
+
+ // then
+ assertThat(integration.warmupPeriod).isEqualTo(Period.Seconds(7200))
+ }
+
+ @Test
+ fun `should produce Period Days when seconds is null`() {
+ // given
+ val warmup = Yield.Metadata.Period(days = 5, seconds = null)
+
+ // when
+ val integration = buildIntegration(warmupPeriod = warmup, cooldownPeriod = null)
+
+ // then
+ assertThat(integration.warmupPeriod).isEqualTo(Period.Days(5))
+ }
+
+ @Test
+ fun `should prefer seconds over days when both are present`() {
+ // given — days is non-zero but seconds takes priority
+ val warmup = Yield.Metadata.Period(days = 10, seconds = 3600)
+
+ // when
+ val integration = buildIntegration(warmupPeriod = warmup, cooldownPeriod = null)
+
+ // then
+ assertThat(integration.warmupPeriod).isInstanceOf(Period.Seconds::class.java)
+ assertThat((integration.warmupPeriod as Period.Seconds).value).isEqualTo(3600)
+ }
+
+ @Test
+ fun `should produce Period Days with zero value when days is zero and seconds is null`() {
+ // given
+ val warmup = Yield.Metadata.Period(days = 0, seconds = null)
+
+ // when
+ val integration = buildIntegration(warmupPeriod = warmup, cooldownPeriod = null)
+
+ // then
+ assertThat(integration.warmupPeriod).isEqualTo(Period.Days(0))
+ }
+ }
+
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ inner class `cooldownPeriod mapping` {
+
+ @Test
+ fun `should be null when yield cooldownPeriod is null`() {
+ // given
+ val warmup = Yield.Metadata.Period(days = 1, seconds = null)
+
+ // when
+ val integration = buildIntegration(warmupPeriod = warmup, cooldownPeriod = null)
+
+ // then
+ assertThat(integration.cooldownPeriod).isNull()
+ }
+
+ @Test
+ fun `should produce Fixed Period Seconds when cooldown seconds is non-null`() {
+ // given
+ val warmup = Yield.Metadata.Period(days = 1, seconds = null)
+ val cooldown = Yield.Metadata.Period(days = 2, seconds = 86400)
+
+ // when
+ val integration = buildIntegration(warmupPeriod = warmup, cooldownPeriod = cooldown)
+
+ // then
+ assertThat(integration.cooldownPeriod).isEqualTo(CooldownPeriod.Fixed(Period.Seconds(86400)))
+ }
+
+ @Test
+ fun `should produce Fixed Period Days when cooldown seconds is null`() {
+ // given
+ val warmup = Yield.Metadata.Period(days = 1, seconds = null)
+ val cooldown = Yield.Metadata.Period(days = 3, seconds = null)
+
+ // when
+ val integration = buildIntegration(warmupPeriod = warmup, cooldownPeriod = cooldown)
+
+ // then
+ assertThat(integration.cooldownPeriod).isEqualTo(CooldownPeriod.Fixed(Period.Days(3)))
+ }
+
+ @Test
+ fun `should prefer seconds over days in cooldown when both are present`() {
+ // given
+ val warmup = Yield.Metadata.Period(days = 1, seconds = null)
+ val cooldown = Yield.Metadata.Period(days = 7, seconds = 604800)
+
+ // when
+ val integration = buildIntegration(warmupPeriod = warmup, cooldownPeriod = cooldown)
+
+ // then
+ val period = integration.cooldownPeriod
+ assertThat(period).isInstanceOf(CooldownPeriod.Fixed::class.java)
+ assertThat((period as CooldownPeriod.Fixed).period).isInstanceOf(Period.Seconds::class.java)
+ assertThat((period.period as Period.Seconds).value).isEqualTo(604800)
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/StakingBalanceEntryConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/StakingBalanceEntryConverter.kt
index 2cee0bc82d..0417ba1bf3 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/StakingBalanceEntryConverter.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/StakingBalanceEntryConverter.kt
@@ -1,23 +1,16 @@
package com.tangem.features.staking.impl.presentation.state.converters
-import com.tangem.core.ui.extensions.TextReference
-import com.tangem.core.ui.extensions.pluralReference
-import com.tangem.core.ui.extensions.resourceReference
-import com.tangem.core.ui.extensions.stringReference
-import com.tangem.core.ui.extensions.wrappedList
+import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
+import com.tangem.core.ui.utils.SECONDS_IN_HOUR
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
-import com.tangem.domain.models.staking.BalanceType
-import com.tangem.domain.models.staking.PendingAction
-import com.tangem.domain.models.staking.StakingBalance
-import com.tangem.domain.models.staking.StakingBalanceEntry
-import com.tangem.domain.models.staking.StakingEntryActions
-import com.tangem.domain.models.staking.StakingEntryType
+import com.tangem.domain.models.staking.*
import com.tangem.domain.models.staking.action.StakingActionType
+import com.tangem.domain.staking.model.Period
import com.tangem.domain.staking.model.StakingIntegration
import com.tangem.domain.staking.model.StakingTarget
import com.tangem.features.staking.impl.R
@@ -125,12 +118,26 @@ internal class StakingBalanceEntryConverter(
}
}
StakingEntryType.PREPARING -> {
- val warmupPeriod = integration.warmupPeriodDays
+ val warmupPeriod = integration.warmupPeriod
TextReference.Combined(
wrappedList(
resourceReference(R.string.staking_details_warmup_period),
stringReference(" "),
- pluralReference(R.plurals.common_days, warmupPeriod, wrappedList(warmupPeriod)),
+ when (warmupPeriod) {
+ is Period.Days -> pluralReference(
+ id = R.plurals.common_days,
+ count = warmupPeriod.value,
+ formatArgs = wrappedList(warmupPeriod.value),
+ )
+ is Period.Seconds -> {
+ val hours = warmupPeriod.value / SECONDS_IN_HOUR
+ pluralReference(
+ id = R.plurals.common_hours,
+ count = hours,
+ formatArgs = wrappedList(hours),
+ )
+ }
+ },
),
)
}
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt
index 97e0137e20..bb57a75f24 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt
@@ -7,18 +7,20 @@ import com.tangem.common.ui.amountScreen.converters.AmountStateConverter
import com.tangem.common.ui.amountScreen.models.AmountParameters
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
-import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
+import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.percent
+import com.tangem.core.ui.utils.SECONDS_IN_HOUR
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.staking.StakingBalanceEntry
import com.tangem.domain.models.wallet.UserWallet
+import com.tangem.domain.staking.model.Period
import com.tangem.domain.staking.model.StakingIntegration
import com.tangem.domain.staking.model.StakingTarget
import com.tangem.domain.staking.model.common.RewardClaiming
@@ -199,17 +201,27 @@ internal class SetInitialDataStateTransformer(
}
private fun createWarmupPeriodItem(): RoundedListWithDividersItemData? {
- val warmupPeriodDays = integration.warmupPeriodDays
- if (warmupPeriodDays == 0) return null
+ val warmupPeriod = integration.warmupPeriod
+ if (warmupPeriod.value == 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 = warmupPeriodDays,
- formatArgs = wrappedList(warmupPeriodDays),
- ),
+ endText = when (warmupPeriod) {
+ is Period.Days -> pluralReference(
+ id = R.plurals.common_days,
+ count = warmupPeriod.value,
+ formatArgs = wrappedList(warmupPeriod.value),
+ )
+ is Period.Seconds -> {
+ val hours = warmupPeriod.value / SECONDS_IN_HOUR
+ pluralReference(
+ id = R.plurals.common_hours,
+ count = hours,
+ formatArgs = wrappedList(hours),
+ )
+ }
+ },
iconClick = { clickIntents.onInfoClick(InfoType.WARMUP_PERIOD) },
)
}
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/CooldownPeriodUtils.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/CooldownPeriodUtils.kt
index f31d5d3b6e..7237a3b641 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/CooldownPeriodUtils.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/CooldownPeriodUtils.kt
@@ -1,22 +1,30 @@
package com.tangem.features.staking.impl.presentation.state.utils
-import com.tangem.core.ui.extensions.TextReference
-import com.tangem.core.ui.extensions.combinedReference
-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.extensions.*
+import com.tangem.core.ui.utils.SECONDS_IN_HOUR
import com.tangem.domain.staking.model.CooldownPeriod
+import com.tangem.domain.staking.model.Period
import com.tangem.features.staking.impl.R
import com.tangem.utils.StringsSigns.MINUS
import com.tangem.utils.StringsSigns.NON_BREAKING_SPACE
internal fun CooldownPeriod.toTextReference(): TextReference {
return when (this) {
- is CooldownPeriod.Fixed -> pluralReference(
- id = R.plurals.common_days,
- count = days,
- formatArgs = wrappedList(days),
- )
+ is CooldownPeriod.Fixed -> when (period) {
+ is Period.Days -> pluralReference(
+ id = R.plurals.common_days,
+ count = period.value,
+ formatArgs = wrappedList(period.value),
+ )
+ is Period.Seconds -> {
+ val hours = period.value / SECONDS_IN_HOUR
+ pluralReference(
+ id = R.plurals.common_hours,
+ count = hours,
+ formatArgs = wrappedList(hours),
+ )
+ }
+ }
is CooldownPeriod.Range -> combinedReference(
stringReference("$minDays$MINUS$maxDays$NON_BREAKING_SPACE"),
pluralReference(
diff --git a/features/staking/impl/src/test/kotlin/com/tangem/features/staking/impl/presentation/state/utils/CooldownPeriodUtilsTest.kt b/features/staking/impl/src/test/kotlin/com/tangem/features/staking/impl/presentation/state/utils/CooldownPeriodUtilsTest.kt
new file mode 100644
index 0000000000..2c60ad2b9a
--- /dev/null
+++ b/features/staking/impl/src/test/kotlin/com/tangem/features/staking/impl/presentation/state/utils/CooldownPeriodUtilsTest.kt
@@ -0,0 +1,221 @@
+package com.tangem.features.staking.impl.presentation.state.utils
+
+import com.google.common.truth.Truth.assertThat
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.combinedReference
+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.SECONDS_IN_HOUR
+import com.tangem.domain.staking.model.CooldownPeriod
+import com.tangem.domain.staking.model.Period
+import com.tangem.features.staking.impl.R
+import com.tangem.utils.StringsSigns.MINUS
+import com.tangem.utils.StringsSigns.NON_BREAKING_SPACE
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+
+/**
+ * Tests for [CooldownPeriod.toTextReference] extension function.
+ */
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+internal class CooldownPeriodUtilsTest {
+
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ inner class `Fixed with Days` {
+
+ @Test
+ fun `should return plural days reference for Fixed Period Days`() {
+ // given
+ val cooldown = CooldownPeriod.Fixed(Period.Days(7))
+
+ // when
+ val result = cooldown.toTextReference()
+
+ // then
+ val expected = pluralReference(
+ id = R.plurals.common_days,
+ count = 7,
+ formatArgs = wrappedList(7),
+ )
+ assertThat(result).isEqualTo(expected)
+ }
+
+ @Test
+ fun `should return plural days with zero for Fixed Period Days zero`() {
+ // given
+ val cooldown = CooldownPeriod.Fixed(Period.Days(0))
+
+ // when
+ val result = cooldown.toTextReference()
+
+ // then
+ val expected = pluralReference(
+ id = R.plurals.common_days,
+ count = 0,
+ formatArgs = wrappedList(0),
+ )
+ assertThat(result).isEqualTo(expected)
+ }
+
+ @Test
+ fun `should pass day count as both count and format arg`() {
+ // given
+ val days = 14
+ val cooldown = CooldownPeriod.Fixed(Period.Days(days))
+
+ // when
+ val result = cooldown.toTextReference()
+
+ // then
+ assertThat(result).isInstanceOf(TextReference.PluralRes::class.java)
+ val plural = result as TextReference.PluralRes
+ assertThat(plural.count).isEqualTo(days)
+ assertThat(plural.formatArgs.first()).isEqualTo(days)
+ }
+ }
+
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ inner class `Fixed with Seconds` {
+
+ @Test
+ fun `should return plural hours reference for Fixed Period Seconds`() {
+ // given — 2 hours worth of seconds
+ val cooldown = CooldownPeriod.Fixed(Period.Seconds(2 * SECONDS_IN_HOUR))
+
+ // when
+ val result = cooldown.toTextReference()
+
+ // then
+ val expectedHours = 2
+ val expected = pluralReference(
+ id = R.plurals.common_hours,
+ count = expectedHours,
+ formatArgs = wrappedList(expectedHours),
+ )
+ assertThat(result).isEqualTo(expected)
+ }
+
+ @Test
+ fun `should convert exactly one hour worth of seconds to 1 hour`() {
+ // given
+ val cooldown = CooldownPeriod.Fixed(Period.Seconds(SECONDS_IN_HOUR))
+
+ // when
+ val result = cooldown.toTextReference()
+
+ // then
+ assertThat(result).isInstanceOf(TextReference.PluralRes::class.java)
+ val plural = result as TextReference.PluralRes
+ assertThat(plural.count).isEqualTo(1)
+ assertThat(plural.formatArgs.first()).isEqualTo(1)
+ }
+
+ @Test
+ fun `should floor to 1 hour when seconds are not divisible evenly`() {
+ // given — 5000 seconds = 1.388... hours → integer division → 1
+ val cooldown = CooldownPeriod.Fixed(Period.Seconds(5000))
+
+ // when
+ val result = cooldown.toTextReference()
+
+ // then
+ assertThat(result).isInstanceOf(TextReference.PluralRes::class.java)
+ val plural = result as TextReference.PluralRes
+ assertThat(plural.count).isEqualTo(1)
+ }
+
+ @Test
+ fun `should return 0 hours for zero seconds`() {
+ // given
+ val cooldown = CooldownPeriod.Fixed(Period.Seconds(0))
+
+ // when
+ val result = cooldown.toTextReference()
+
+ // then
+ assertThat(result).isInstanceOf(TextReference.PluralRes::class.java)
+ val plural = result as TextReference.PluralRes
+ assertThat(plural.count).isEqualTo(0)
+ assertThat(plural.formatArgs.first()).isEqualTo(0)
+ }
+
+ @Test
+ fun `should use R plurals common_hours resource id`() {
+ // given
+ val cooldown = CooldownPeriod.Fixed(Period.Seconds(SECONDS_IN_HOUR))
+
+ // when
+ val result = cooldown.toTextReference()
+
+ // then
+ assertThat(result).isInstanceOf(TextReference.PluralRes::class.java)
+ assertThat((result as TextReference.PluralRes).id).isEqualTo(R.plurals.common_hours)
+ }
+ }
+
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ inner class Range {
+
+ @Test
+ fun `should return combined reference for Range`() {
+ // given
+ val minDays = 2
+ val maxDays = 5
+ val cooldown = CooldownPeriod.Range(minDays = minDays, maxDays = maxDays)
+
+ // when
+ val result = cooldown.toTextReference()
+
+ // then
+ val expected = combinedReference(
+ stringReference("$minDays$MINUS$maxDays$NON_BREAKING_SPACE"),
+ pluralReference(
+ id = R.plurals.common_days_no_param,
+ count = maxDays,
+ ),
+ )
+ assertThat(result).isEqualTo(expected)
+ }
+
+ @Test
+ fun `should use maxDays as count for plural in Range`() {
+ // given
+ val maxDays = 10
+ val cooldown = CooldownPeriod.Range(minDays = 3, maxDays = maxDays)
+
+ // when
+ val result = cooldown.toTextReference()
+
+ // then
+ assertThat(result).isInstanceOf(TextReference.Combined::class.java)
+ val combined = result as TextReference.Combined
+ val pluralPart = combined.refs[1]
+ assertThat(pluralPart).isInstanceOf(TextReference.PluralRes::class.java)
+ assertThat((pluralPart as TextReference.PluralRes).count).isEqualTo(maxDays)
+ }
+
+ @Test
+ fun `should include minDays and maxDays with minus and non-breaking-space in string part`() {
+ // given
+ val minDays = 1
+ val maxDays = 7
+ val cooldown = CooldownPeriod.Range(minDays = minDays, maxDays = maxDays)
+
+ // when
+ val result = cooldown.toTextReference()
+
+ // then
+ assertThat(result).isInstanceOf(TextReference.Combined::class.java)
+ val combined = result as TextReference.Combined
+ val stringPart = combined.refs[0]
+ assertThat(stringPart).isInstanceOf(TextReference.Str::class.java)
+ assertThat((stringPart as TextReference.Str).value)
+ .isEqualTo("$minDays$MINUS$maxDays$NON_BREAKING_SPACE")
+ }
+ }
+}
\ No newline at end of file