Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-06 18:40:51 +05:00
parent fcb3722ca0
commit df10b4fd21
17 changed files with 559 additions and 38 deletions

View file

@ -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),
)
}
},
),
)
}

View file

@ -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) },
)
}

View file

@ -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(

View file

@ -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")
}
}
}