Updated on 2026-08-14
This commit is contained in:
parent
f000c0c687
commit
f990f3c833
9 changed files with 152 additions and 141 deletions
|
|
@ -11,7 +11,6 @@ data class YieldBoostStatusResponse(
|
||||||
@Json(name = "userAddress") val userAddress: String?,
|
@Json(name = "userAddress") val userAddress: String?,
|
||||||
@Json(name = "contractAddress") val contractAddress: String?,
|
@Json(name = "contractAddress") val contractAddress: String?,
|
||||||
@Json(name = "promoEnrollmentStatus") val promoEnrollmentStatus: String,
|
@Json(name = "promoEnrollmentStatus") val promoEnrollmentStatus: String,
|
||||||
@Json(name = "activationDate") val activationDate: String?,
|
|
||||||
@Json(name = "qualificationEndDate") val qualificationEndDate: String?,
|
@Json(name = "qualificationEndDate") val qualificationEndDate: String?,
|
||||||
@Json(name = "disqualificationReason") val disqualificationReason: String?,
|
@Json(name = "disqualificationReason") val disqualificationReason: String?,
|
||||||
)
|
)
|
||||||
|
|
@ -16,43 +16,26 @@ internal object YieldBoostStatusConverter {
|
||||||
private const val REASON_CLOSED = "closed"
|
private const val REASON_CLOSED = "closed"
|
||||||
|
|
||||||
fun convert(dto: YieldBoostStatusResponse): YieldBoostStatus = when (dto.promoEnrollmentStatus.lowercase()) {
|
fun convert(dto: YieldBoostStatusResponse): YieldBoostStatus = when (dto.promoEnrollmentStatus.lowercase()) {
|
||||||
STATUS_ACTIVE -> dto.toActive() ?: YieldBoostStatus.NotStarted
|
STATUS_ACTIVE, STATUS_COMPLETED -> dto.toEnrolled()
|
||||||
STATUS_COMPLETED -> dto.toCompleted() ?: YieldBoostStatus.NotStarted
|
|
||||||
STATUS_DISQUALIFIED -> YieldBoostStatus.Disqualified(reason = dto.disqualificationReason.toReason())
|
STATUS_DISQUALIFIED -> YieldBoostStatus.Disqualified(reason = dto.disqualificationReason.toReason())
|
||||||
STATUS_NOT_STARTED -> YieldBoostStatus.NotStarted
|
STATUS_NOT_STARTED -> YieldBoostStatus.NotStarted
|
||||||
else -> YieldBoostStatus.NotStarted // forward-compat: unknown status → treat as NotStarted
|
else -> YieldBoostStatus.NotStarted // forward-compat: unknown status → treat as NotStarted
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Backend `"active"` → [YieldBoostStatus.Active]. Returns `null` if mandatory dates can't be parsed. */
|
/**
|
||||||
private fun YieldBoostStatusResponse.toActive(): YieldBoostStatus.Active? {
|
* Backend `"active"` / `"completed"` → [YieldBoostStatus.Enrolled].
|
||||||
val activation = activationDate?.let { runCatching { Instant.parse(it) }.getOrNull() } ?: return null
|
*
|
||||||
val qualificationEnd =
|
* An unparseable / missing `qualificationEndDate` is kept as `null` (block hidden) — never downgraded to
|
||||||
qualificationEndDate?.let { runCatching { Instant.parse(it) }.getOrNull() } ?: return null
|
* [YieldBoostStatus.NotStarted], which would re-prompt an already-enrolled user to join.
|
||||||
return YieldBoostStatus.Active(
|
*/
|
||||||
tokenName = tokenName.orEmpty(),
|
private fun YieldBoostStatusResponse.toEnrolled(): YieldBoostStatus.Enrolled = YieldBoostStatus.Enrolled(
|
||||||
networkId = networkId.orEmpty(),
|
tokenName = tokenName.orEmpty(),
|
||||||
moduleAddress = moduleAddress.orEmpty(),
|
networkId = networkId.orEmpty(),
|
||||||
userAddress = userAddress.orEmpty(),
|
moduleAddress = moduleAddress.orEmpty(),
|
||||||
contractAddress = contractAddress.orEmpty(),
|
userAddress = userAddress.orEmpty(),
|
||||||
activationDate = activation,
|
contractAddress = contractAddress.orEmpty(),
|
||||||
qualificationEndDate = qualificationEnd,
|
qualificationEndDate = qualificationEndDate?.let { runCatching { Instant.parse(it) }.getOrNull() },
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
private fun YieldBoostStatusResponse.toCompleted(): YieldBoostStatus.Completed? {
|
|
||||||
val activation = activationDate?.let { runCatching { Instant.parse(it) }.getOrNull() } ?: return null
|
|
||||||
val qualificationEnd =
|
|
||||||
qualificationEndDate?.let { runCatching { Instant.parse(it) }.getOrNull() } ?: return null
|
|
||||||
return YieldBoostStatus.Completed(
|
|
||||||
tokenName = tokenName.orEmpty(),
|
|
||||||
networkId = networkId.orEmpty(),
|
|
||||||
moduleAddress = moduleAddress.orEmpty(),
|
|
||||||
userAddress = userAddress.orEmpty(),
|
|
||||||
contractAddress = contractAddress.orEmpty(),
|
|
||||||
activationDate = activation,
|
|
||||||
qualificationEndDate = qualificationEnd,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String?.toReason(): YieldBoostStatus.Disqualified.Reason = when (this?.lowercase()) {
|
private fun String?.toReason(): YieldBoostStatus.Disqualified.Reason = when (this?.lowercase()) {
|
||||||
REASON_FROD -> YieldBoostStatus.Disqualified.Reason.FROD
|
REASON_FROD -> YieldBoostStatus.Disqualified.Reason.FROD
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ package com.tangem.data.yield.supply.promo.converter
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import com.tangem.datasource.api.promotion.models.YieldBoostStatusResponse
|
import com.tangem.datasource.api.promotion.models.YieldBoostStatusResponse
|
||||||
import com.tangem.domain.yield.supply.models.YieldBoostStatus
|
import com.tangem.domain.yield.supply.models.YieldBoostStatus
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class YieldBoostStatusConverterTest {
|
class YieldBoostStatusConverterTest {
|
||||||
|
|
||||||
private val activation = "2026-05-01T00:00:00Z"
|
|
||||||
private val qualificationEnd = "2026-06-01T00:00:00Z"
|
private val qualificationEnd = "2026-06-01T00:00:00Z"
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -20,7 +20,7 @@ class YieldBoostStatusConverterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN active backend status with valid dates WHEN convert THEN returns Active`() {
|
fun `GIVEN active backend status with valid date WHEN convert THEN returns Enrolled`() {
|
||||||
val dto = dto(
|
val dto = dto(
|
||||||
promoEnrollmentStatus = "active",
|
promoEnrollmentStatus = "active",
|
||||||
tokenName = "USD Coin",
|
tokenName = "USD Coin",
|
||||||
|
|
@ -28,47 +28,49 @@ class YieldBoostStatusConverterTest {
|
||||||
moduleAddress = "0xmodule",
|
moduleAddress = "0xmodule",
|
||||||
userAddress = "0xuser",
|
userAddress = "0xuser",
|
||||||
contractAddress = "0xcontract",
|
contractAddress = "0xcontract",
|
||||||
activationDate = activation,
|
|
||||||
qualificationEndDate = qualificationEnd,
|
qualificationEndDate = qualificationEnd,
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = YieldBoostStatusConverter.convert(dto)
|
val result = YieldBoostStatusConverter.convert(dto)
|
||||||
|
|
||||||
assertThat(result).isInstanceOf(YieldBoostStatus.Active::class.java)
|
assertThat(result).isInstanceOf(YieldBoostStatus.Enrolled::class.java)
|
||||||
val active = result as YieldBoostStatus.Active
|
val enrolled = result as YieldBoostStatus.Enrolled
|
||||||
assertThat(active.tokenName).isEqualTo("USD Coin")
|
assertThat(enrolled.tokenName).isEqualTo("USD Coin")
|
||||||
assertThat(active.networkId).isEqualTo("ethereum")
|
assertThat(enrolled.networkId).isEqualTo("ethereum")
|
||||||
assertThat(active.contractAddress).isEqualTo("0xcontract")
|
assertThat(enrolled.contractAddress).isEqualTo("0xcontract")
|
||||||
|
assertThat(enrolled.qualificationEndDate).isEqualTo(Instant.parse(qualificationEnd))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN active status missing activationDate WHEN convert THEN falls back to NotStarted`() {
|
fun `GIVEN active status missing qualificationEndDate WHEN convert THEN returns Enrolled with null date`() {
|
||||||
val dto = dto(
|
val dto = dto(
|
||||||
promoEnrollmentStatus = "active",
|
promoEnrollmentStatus = "active",
|
||||||
activationDate = null,
|
contractAddress = "0xcontract",
|
||||||
qualificationEndDate = qualificationEnd,
|
qualificationEndDate = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = YieldBoostStatusConverter.convert(dto)
|
val result = YieldBoostStatusConverter.convert(dto)
|
||||||
|
|
||||||
assertThat(result).isEqualTo(YieldBoostStatus.NotStarted)
|
assertThat(result).isInstanceOf(YieldBoostStatus.Enrolled::class.java)
|
||||||
|
assertThat((result as YieldBoostStatus.Enrolled).qualificationEndDate).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN active status with malformed activationDate WHEN convert THEN falls back to NotStarted`() {
|
fun `GIVEN active status with malformed qualificationEndDate WHEN convert THEN returns Enrolled with null date`() {
|
||||||
val dto = dto(
|
val dto = dto(
|
||||||
promoEnrollmentStatus = "active",
|
promoEnrollmentStatus = "active",
|
||||||
activationDate = "not-an-iso",
|
contractAddress = "0xcontract",
|
||||||
qualificationEndDate = qualificationEnd,
|
qualificationEndDate = "not-an-iso",
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = YieldBoostStatusConverter.convert(dto)
|
val result = YieldBoostStatusConverter.convert(dto)
|
||||||
|
|
||||||
assertThat(result).isEqualTo(YieldBoostStatus.NotStarted)
|
assertThat(result).isInstanceOf(YieldBoostStatus.Enrolled::class.java)
|
||||||
|
assertThat((result as YieldBoostStatus.Enrolled).qualificationEndDate).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN completed status with valid dates WHEN convert THEN returns Completed`() {
|
fun `GIVEN completed status with valid date WHEN convert THEN returns Enrolled`() {
|
||||||
val dto = dto(
|
val dto = dto(
|
||||||
promoEnrollmentStatus = "completed",
|
promoEnrollmentStatus = "completed",
|
||||||
tokenName = "USDT",
|
tokenName = "USDT",
|
||||||
|
|
@ -76,13 +78,14 @@ class YieldBoostStatusConverterTest {
|
||||||
moduleAddress = "0xmodule",
|
moduleAddress = "0xmodule",
|
||||||
userAddress = "0xuser",
|
userAddress = "0xuser",
|
||||||
contractAddress = "0xcontract",
|
contractAddress = "0xcontract",
|
||||||
activationDate = "2026-04-01T00:00:00Z",
|
|
||||||
qualificationEndDate = "2026-05-01T00:00:00Z",
|
qualificationEndDate = "2026-05-01T00:00:00Z",
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = YieldBoostStatusConverter.convert(dto)
|
val result = YieldBoostStatusConverter.convert(dto)
|
||||||
|
|
||||||
assertThat(result).isInstanceOf(YieldBoostStatus.Completed::class.java)
|
assertThat(result).isInstanceOf(YieldBoostStatus.Enrolled::class.java)
|
||||||
|
assertThat((result as YieldBoostStatus.Enrolled).qualificationEndDate)
|
||||||
|
.isEqualTo(Instant.parse("2026-05-01T00:00:00Z"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -153,13 +156,12 @@ class YieldBoostStatusConverterTest {
|
||||||
moduleAddress = "0xmodule",
|
moduleAddress = "0xmodule",
|
||||||
userAddress = "0xuser",
|
userAddress = "0xuser",
|
||||||
contractAddress = "0xcontract",
|
contractAddress = "0xcontract",
|
||||||
activationDate = activation,
|
|
||||||
qualificationEndDate = qualificationEnd,
|
qualificationEndDate = qualificationEnd,
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = YieldBoostStatusConverter.convert(dto)
|
val result = YieldBoostStatusConverter.convert(dto)
|
||||||
|
|
||||||
assertThat(result).isInstanceOf(YieldBoostStatus.Active::class.java)
|
assertThat(result).isInstanceOf(YieldBoostStatus.Enrolled::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dto(
|
private fun dto(
|
||||||
|
|
@ -169,7 +171,6 @@ class YieldBoostStatusConverterTest {
|
||||||
moduleAddress: String? = null,
|
moduleAddress: String? = null,
|
||||||
userAddress: String? = null,
|
userAddress: String? = null,
|
||||||
contractAddress: String? = null,
|
contractAddress: String? = null,
|
||||||
activationDate: String? = null,
|
|
||||||
qualificationEndDate: String? = null,
|
qualificationEndDate: String? = null,
|
||||||
disqualificationReason: String? = null,
|
disqualificationReason: String? = null,
|
||||||
) = YieldBoostStatusResponse(
|
) = YieldBoostStatusResponse(
|
||||||
|
|
@ -179,7 +180,6 @@ class YieldBoostStatusConverterTest {
|
||||||
userAddress = userAddress,
|
userAddress = userAddress,
|
||||||
contractAddress = contractAddress,
|
contractAddress = contractAddress,
|
||||||
promoEnrollmentStatus = promoEnrollmentStatus,
|
promoEnrollmentStatus = promoEnrollmentStatus,
|
||||||
activationDate = activationDate,
|
|
||||||
qualificationEndDate = qualificationEndDate,
|
qualificationEndDate = qualificationEndDate,
|
||||||
disqualificationReason = disqualificationReason,
|
disqualificationReason = disqualificationReason,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,22 @@ sealed interface YieldBoostStatus {
|
||||||
|
|
||||||
data object NotStarted : YieldBoostStatus
|
data object NotStarted : YieldBoostStatus
|
||||||
|
|
||||||
/** User entered boost, qualification period is still running. */
|
/**
|
||||||
data class Active(
|
* User is enrolled in the boost (backend `active` or `completed`).
|
||||||
|
*
|
||||||
|
* The boost block on the active screen is driven entirely by [qualificationEndDate], which the backend
|
||||||
|
* computes as the end of the bonus-accrual period:
|
||||||
|
* - `null` — nothing is shown;
|
||||||
|
* - in the future — days left until the date;
|
||||||
|
* - reached / passed — awaiting payout.
|
||||||
|
*/
|
||||||
|
data class Enrolled(
|
||||||
val tokenName: String,
|
val tokenName: String,
|
||||||
val networkId: String,
|
val networkId: String,
|
||||||
val moduleAddress: String,
|
val moduleAddress: String,
|
||||||
val userAddress: String,
|
val userAddress: String,
|
||||||
val contractAddress: String,
|
val contractAddress: String,
|
||||||
val activationDate: Instant,
|
val qualificationEndDate: Instant?,
|
||||||
val qualificationEndDate: Instant,
|
|
||||||
) : YieldBoostStatus
|
|
||||||
|
|
||||||
/** Boost has finished (backend `completed`). */
|
|
||||||
data class Completed(
|
|
||||||
val tokenName: String,
|
|
||||||
val networkId: String,
|
|
||||||
val moduleAddress: String,
|
|
||||||
val userAddress: String,
|
|
||||||
val contractAddress: String,
|
|
||||||
val activationDate: Instant,
|
|
||||||
val qualificationEndDate: Instant,
|
|
||||||
) : YieldBoostStatus
|
) : YieldBoostStatus
|
||||||
|
|
||||||
data class Disqualified(val reason: Reason) : YieldBoostStatus {
|
data class Disqualified(val reason: Reason) : YieldBoostStatus {
|
||||||
|
|
|
||||||
|
|
@ -91,21 +91,10 @@ class IsYieldBoostPromoEnabledForTokenUseCaseTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN status is Active WHEN invoke THEN returns Right(false)`() = runTest {
|
fun `GIVEN status is Enrolled WHEN invoke THEN returns Right(false)`() = runTest {
|
||||||
val token = createToken()
|
val token = createToken()
|
||||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
||||||
coEvery { repository.getYieldBoostStatus(userWalletId, false) } returns activeStatus()
|
coEvery { repository.getYieldBoostStatus(userWalletId, false) } returns enrolledStatus()
|
||||||
|
|
||||||
val result = useCase(userWalletId, token)
|
|
||||||
|
|
||||||
assertThat(result.getOrNull()).isFalse()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `GIVEN status is Completed WHEN invoke THEN returns Right(false)`() = runTest {
|
|
||||||
val token = createToken()
|
|
||||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
|
||||||
coEvery { repository.getYieldBoostStatus(userWalletId, false) } returns completedStatus()
|
|
||||||
|
|
||||||
val result = useCase(userWalletId, token)
|
val result = useCase(userWalletId, token)
|
||||||
|
|
||||||
|
|
@ -162,26 +151,15 @@ class IsYieldBoostPromoEnabledForTokenUseCaseTest {
|
||||||
link = null,
|
link = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun activeStatus() = YieldBoostStatus.Active(
|
private fun enrolledStatus() = YieldBoostStatus.Enrolled(
|
||||||
tokenName = "USD Coin",
|
tokenName = "USD Coin",
|
||||||
networkId = networkRawId,
|
networkId = networkRawId,
|
||||||
moduleAddress = "0xmodule",
|
moduleAddress = "0xmodule",
|
||||||
userAddress = "0xuser",
|
userAddress = "0xuser",
|
||||||
contractAddress = contractAddress,
|
contractAddress = contractAddress,
|
||||||
activationDate = Instant.parse("2026-05-01T00:00:00Z"),
|
|
||||||
qualificationEndDate = Instant.parse("2026-06-01T00:00:00Z"),
|
qualificationEndDate = Instant.parse("2026-06-01T00:00:00Z"),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun completedStatus() = YieldBoostStatus.Completed(
|
|
||||||
tokenName = "USD Coin",
|
|
||||||
networkId = networkRawId,
|
|
||||||
moduleAddress = "0xmodule",
|
|
||||||
userAddress = "0xuser",
|
|
||||||
contractAddress = contractAddress,
|
|
||||||
activationDate = Instant.parse("2026-04-01T00:00:00Z"),
|
|
||||||
qualificationEndDate = Instant.parse("2026-05-01T00:00:00Z"),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun createToken(
|
private fun createToken(
|
||||||
contractAddress: String = this.contractAddress,
|
contractAddress: String = this.contractAddress,
|
||||||
networkRawId: String = this.networkRawId,
|
networkRawId: String = this.networkRawId,
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,9 @@ class ShouldShowYieldBoostMainBannerUseCaseTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN status is Active WHEN invoke THEN returns Right(false)`() = runTest {
|
fun `GIVEN status is Enrolled WHEN invoke THEN returns Right(false)`() = runTest {
|
||||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
||||||
coEvery { repository.getYieldBoostStatus(userWalletId, false) } returns activeStatus()
|
coEvery { repository.getYieldBoostStatus(userWalletId, false) } returns enrolledStatus()
|
||||||
|
|
||||||
val result = useCase(userWalletId)
|
val result = useCase(userWalletId)
|
||||||
|
|
||||||
|
|
@ -92,13 +92,12 @@ class ShouldShowYieldBoostMainBannerUseCaseTest {
|
||||||
link = null,
|
link = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun activeStatus() = YieldBoostStatus.Active(
|
private fun enrolledStatus() = YieldBoostStatus.Enrolled(
|
||||||
tokenName = "USD Coin",
|
tokenName = "USD Coin",
|
||||||
networkId = networkRawId,
|
networkId = networkRawId,
|
||||||
moduleAddress = "0xmodule",
|
moduleAddress = "0xmodule",
|
||||||
userAddress = "0xuser",
|
userAddress = "0xuser",
|
||||||
contractAddress = contractAddress,
|
contractAddress = contractAddress,
|
||||||
activationDate = Instant.parse("2026-05-01T00:00:00Z"),
|
|
||||||
qualificationEndDate = Instant.parse("2026-06-01T00:00:00Z"),
|
qualificationEndDate = Instant.parse("2026-06-01T00:00:00Z"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.tangem.features.yield.supply.impl.active.model
|
||||||
|
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
|
||||||
|
/** What the boost block on the active screen should display, derived solely from the qualification end date. */
|
||||||
|
internal sealed interface BoostBlockState {
|
||||||
|
|
||||||
|
/** Qualification period is still running — show the countdown. */
|
||||||
|
data class DaysLeft(val days: Int) : BoostBlockState
|
||||||
|
|
||||||
|
/** Qualification period is over — show the awaiting-payout copy. */
|
||||||
|
data object AwaitingPayout : BoostBlockState
|
||||||
|
|
||||||
|
/** No qualification end date — show nothing. */
|
||||||
|
data object Hidden : BoostBlockState
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun resolveBoostBlockState(qualificationEndDate: Instant?, now: Instant): BoostBlockState = when {
|
||||||
|
qualificationEndDate == null -> BoostBlockState.Hidden
|
||||||
|
now >= qualificationEndDate -> BoostBlockState.AwaitingPayout
|
||||||
|
else -> BoostBlockState.DaysLeft(days = (qualificationEndDate - now).inWholeDays.toInt())
|
||||||
|
}
|
||||||
|
|
@ -54,8 +54,6 @@ import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
|
|
||||||
@Suppress("LongParameterList", "LargeClass")
|
@Suppress("LongParameterList", "LargeClass")
|
||||||
@ModelScoped
|
@ModelScoped
|
||||||
|
|
@ -245,21 +243,18 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
||||||
modelScope.launch(dispatchers.io) {
|
modelScope.launch(dispatchers.io) {
|
||||||
val status = getYieldBoostStatusUseCase(userWalletId).getOrNull() ?: return@launch
|
val status = getYieldBoostStatusUseCase(userWalletId).getOrNull() ?: return@launch
|
||||||
val token = cryptoCurrency as? CryptoCurrency.Token ?: return@launch
|
val token = cryptoCurrency as? CryptoCurrency.Token ?: return@launch
|
||||||
when {
|
if (status !is YieldBoostStatus.Enrolled || !status.matches(token)) return@launch
|
||||||
status is YieldBoostStatus.Active && status.matches(token) -> {
|
|
||||||
uiState.update {
|
val state = resolveBoostBlockState(
|
||||||
it.copy(boostText = buildActiveBoostText(status), onBoostClick = ::onBoostClick)
|
qualificationEndDate = status.qualificationEndDate,
|
||||||
}
|
now = Clock.System.now(),
|
||||||
}
|
)
|
||||||
status is YieldBoostStatus.Completed && status.matches(token) -> {
|
val boostText = when (state) {
|
||||||
uiState.update {
|
is BoostBlockState.DaysLeft -> buildDaysLeftText(state.days)
|
||||||
it.copy(
|
BoostBlockState.AwaitingPayout -> resourceReference(CoreResR.string.yield_promo_completed)
|
||||||
boostText = resourceReference(CoreResR.string.yield_promo_completed),
|
BoostBlockState.Hidden -> return@launch
|
||||||
onBoostClick = ::onBoostClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
uiState.update { it.copy(boostText = boostText, onBoostClick = ::onBoostClick) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -274,32 +269,17 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildActiveBoostText(status: YieldBoostStatus.Active): TextReference {
|
private fun buildDaysLeftText(daysLeft: Int): TextReference = combinedReference(
|
||||||
val daysLeft = computeDaysLeft(status.qualificationEndDate.toEpochMilliseconds())
|
pluralReference(
|
||||||
return combinedReference(
|
id = CoreResR.plurals.common_days,
|
||||||
pluralReference(
|
count = daysLeft,
|
||||||
id = CoreResR.plurals.common_days,
|
formatArgs = wrappedList(daysLeft),
|
||||||
count = daysLeft,
|
),
|
||||||
formatArgs = wrappedList(daysLeft),
|
stringReference(" "),
|
||||||
),
|
resourceReference(CoreResR.string.yield_promo_left_title),
|
||||||
stringReference(" "),
|
)
|
||||||
resourceReference(CoreResR.string.yield_promo_left_title),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun computeDaysLeft(qualificationEndEpochMillis: Long): Int {
|
private fun YieldBoostStatus.Enrolled.matches(token: CryptoCurrency.Token): Boolean {
|
||||||
val nowMillis = Clock.System.now().toEpochMilliseconds()
|
|
||||||
val deltaMillis = max(qualificationEndEpochMillis - nowMillis, 0L)
|
|
||||||
return deltaMillis.milliseconds.inWholeDays.toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun YieldBoostStatus.Active.matches(token: CryptoCurrency.Token): Boolean =
|
|
||||||
matchesToken(contractAddress = contractAddress, networkId = networkId, token = token)
|
|
||||||
|
|
||||||
private fun YieldBoostStatus.Completed.matches(token: CryptoCurrency.Token): Boolean =
|
|
||||||
matchesToken(contractAddress = contractAddress, networkId = networkId, token = token)
|
|
||||||
|
|
||||||
private fun matchesToken(contractAddress: String, networkId: String, token: CryptoCurrency.Token): Boolean {
|
|
||||||
val shouldIgnoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId)
|
val shouldIgnoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId)
|
||||||
return contractAddress.equals(token.contractAddress, ignoreCase = shouldIgnoreCase) &&
|
return contractAddress.equals(token.contractAddress, ignoreCase = shouldIgnoreCase) &&
|
||||||
networkId == token.network.rawId
|
networkId == token.network.rawId
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.tangem.features.yield.supply.impl.active.model
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class BoostBlockStateTest {
|
||||||
|
|
||||||
|
private val now = Instant.parse("2026-05-28T00:00:00Z")
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN null qualificationEndDate WHEN resolve THEN Hidden`() {
|
||||||
|
val result = resolveBoostBlockState(qualificationEndDate = null, now = now)
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(BoostBlockState.Hidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN future qualificationEndDate WHEN resolve THEN DaysLeft with whole days`() {
|
||||||
|
val result = resolveBoostBlockState(
|
||||||
|
qualificationEndDate = Instant.parse("2026-06-01T00:00:00Z"),
|
||||||
|
now = now,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(BoostBlockState.DaysLeft(days = 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN qualificationEndDate less than a day away WHEN resolve THEN DaysLeft zero`() {
|
||||||
|
val result = resolveBoostBlockState(
|
||||||
|
qualificationEndDate = Instant.parse("2026-05-28T18:00:00Z"),
|
||||||
|
now = now,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(BoostBlockState.DaysLeft(days = 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN qualificationEndDate equal to now WHEN resolve THEN AwaitingPayout`() {
|
||||||
|
val result = resolveBoostBlockState(qualificationEndDate = now, now = now)
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(BoostBlockState.AwaitingPayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN past qualificationEndDate WHEN resolve THEN AwaitingPayout`() {
|
||||||
|
val result = resolveBoostBlockState(
|
||||||
|
qualificationEndDate = Instant.parse("2026-05-01T00:00:00Z"),
|
||||||
|
now = now,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(BoostBlockState.AwaitingPayout)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue