From 78720c5cab895049e3c7e46caaab1d93b2f47e31 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Jul 2025 11:18:48 +0400 Subject: [PATCH] Updated on 2026-08-14 --- core/datasource/build.gradle.kts | 1 + .../token/converter/YieldBalanceConverter.kt | 64 +++--- .../DefaultSingleYieldBalanceProducer.kt | 7 +- .../store/DefaultYieldsBalancesStore.kt | 16 +- .../tangem/data/staking/YieldBalanceExt.kt | 2 +- .../DefaultSingleYieldBalanceProducerTest.kt | 4 +- .../YieldsBalancesStoreUpdateMethodsTest.kt | 7 +- domain/staking/build.gradle.kts | 1 + domain/staking/models/build.gradle.kts | 1 + .../tangem/domain/staking/model/StakingID.kt | 4 +- .../staking/model/stakekit/YieldBalance.kt | 203 +++++------------- .../model/stakekit/YieldBalanceItem.kt | 130 +++++++++++ .../BaseCurrencyStatusOperations.kt | 2 +- .../CachedCurrenciesStatusesOperations.kt | 16 +- .../operations/CurrencyStatusOperations.kt | 3 +- .../utils/CurrencyStatusProxyCreator.kt | 10 +- features/staking/impl/build.gradle.kts | 1 + .../state/converters/BalanceItemConverter.kt | 6 +- .../converters/YieldBalancesConverter.kt | 2 +- gradle/dependencies.toml | 2 + 20 files changed, 267 insertions(+), 215 deletions(-) create mode 100644 domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalanceItem.kt diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 7c44fe9963..0bd12e57fd 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -52,6 +52,7 @@ dependencies { /** Coroutines */ implementation(deps.kotlin.coroutines) implementation(deps.kotlin.coroutines.rx2) + implementation(deps.kotlin.datetime) /** Logging */ implementation(deps.timber) diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/YieldBalanceConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/YieldBalanceConverter.kt index de55373f39..5d86e9a0be 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/YieldBalanceConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/YieldBalanceConverter.kt @@ -1,53 +1,61 @@ package com.tangem.datasource.local.token.converter +import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO import com.tangem.domain.models.StatusSource +import com.tangem.domain.staking.model.StakingID import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.staking.model.stakekit.YieldBalanceItem import com.tangem.utils.converter.Converter +import kotlinx.datetime.Instant class YieldBalanceConverter( private val source: StatusSource, -) : Converter { +) : Converter { constructor(isCached: Boolean) : this(source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL) - override fun convert(value: YieldBalanceWrapperDTO): YieldBalance { + override fun convert(value: YieldBalanceWrapperDTO): YieldBalance? { + val stakingId = StakingID( + integrationId = value.integrationId ?: return null, + address = value.addresses.address, + ) + return if (value.balances.isEmpty()) { - YieldBalance.Empty( - integrationId = value.integrationId, - address = value.addresses.address, - source = source, - ) + YieldBalance.Empty(stakingId = stakingId, source = source) } else { YieldBalance.Data( - integrationId = value.integrationId, - address = value.addresses.address, + stakingId = stakingId, balance = YieldBalanceItem( - items = value.balances.map { item -> - BalanceItem( - groupId = item.groupId, - token = TokenConverter.convert(item.tokenDTO), - type = BalanceTypeConverter.convert(item.type), - amount = item.amount, - rawCurrencyId = item.tokenDTO.coinGeckoId, - // tron-specific. operates validatorAddresses instead of validatorAddress - validatorAddress = item.validatorAddress ?: item.validatorAddresses?.get(0), - date = item.date?.toDateTime(), - pendingActions = PendingActionConverter - .convertList(item.pendingActions) - .sortedBy { it.passthrough }, - pendingActionsConstraints = PendingActionConstraintsConverter - .convertList(item.pendingActionConstraints.orEmpty()), - isPending = false, - ) - } - .sortedWith(compareBy({ it.type }, { it.amount })), + items = value.balances + .map { item -> item.toBalanceItem() } + .sortedWith(comparator = compareBy({ it.type }, { it.amount })), integrationId = value.integrationId, ), source = source, ) } } + + private fun BalanceDTO.toBalanceItem(): BalanceItem { + val item = this + + return BalanceItem( + groupId = item.groupId, + token = TokenConverter.convert(item.tokenDTO), + type = BalanceTypeConverter.convert(item.type), + amount = item.amount, + rawCurrencyId = item.tokenDTO.coinGeckoId, + // tron-specific. operates validatorAddresses instead of validatorAddress + validatorAddress = item.validatorAddress ?: item.validatorAddresses?.get(0), + date = item.date?.toString()?.let { Instant.parse(it) }, + pendingActions = PendingActionConverter + .convertList(item.pendingActions) + .sortedBy { it.passthrough }, + pendingActionsConstraints = PendingActionConstraintsConverter + .convertList(item.pendingActionConstraints.orEmpty()), + isPending = false, + ) + } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducer.kt b/data/staking/src/main/java/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducer.kt index cac9b3c1a3..b84ffe3439 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducer.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducer.kt @@ -35,10 +35,7 @@ internal class DefaultSingleYieldBalanceProducer @AssistedInject constructor( ) : SingleYieldBalanceProducer { override val fallback: YieldBalance by lazy { - YieldBalance.Error( - integrationId = params.stakingId.integrationId, - address = params.stakingId.address, - ) + YieldBalance.Error(stakingId = params.stakingId) } override fun produce(): Flow { @@ -50,7 +47,7 @@ internal class DefaultSingleYieldBalanceProducer @AssistedInject constructor( .mapNotNull { balances -> val currentStakingId = params.stakingId - val currentBalances = balances.filter { it.getStakingId() == currentStakingId } + val currentBalances = balances.filter { it.stakingId == currentStakingId } if (currentBalances.size > 1) { analyticsExceptionHandler.sendException( diff --git a/data/staking/src/main/java/com/tangem/data/staking/store/DefaultYieldsBalancesStore.kt b/data/staking/src/main/java/com/tangem/data/staking/store/DefaultYieldsBalancesStore.kt index 10186038e7..797c65a1cc 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/store/DefaultYieldsBalancesStore.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/store/DefaultYieldsBalancesStore.kt @@ -46,6 +46,8 @@ internal class DefaultYieldsBalancesStore( value = cachedStatuses.map { (stringWalletId, wrappers) -> val key = UserWalletId(stringWalletId) val value = YieldBalanceConverter(isCached = true).convertSet(input = wrappers) + .filterNotNull() + .toSet() key to value } @@ -61,7 +63,7 @@ internal class DefaultYieldsBalancesStore( override suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingId: StakingID): YieldBalance? { return runtimeStore.getSyncOrNull() ?.get(userWalletId) - ?.firstOrNull { stakingId == it.getStakingId() } + ?.firstOrNull { it.stakingId == stakingId } } override suspend fun getAllSyncOrNull(userWalletId: UserWalletId): Set? { @@ -96,11 +98,13 @@ internal class DefaultYieldsBalancesStore( private suspend fun storeInRuntime(userWalletId: UserWalletId, values: Set) { val newBalances = YieldBalanceConverter(isCached = false).convertSet(input = values) + .filterNotNull() + .toSet() runtimeStore.update(default = emptyMap()) { saved -> saved.toMutableMap().apply { this[userWalletId] = saved[userWalletId] - ?.addOrReplace(newBalances) { old, new -> old.getStakingId() == new.getStakingId() } + ?.addOrReplace(newBalances) { old, new -> old.stakingId == new.stakingId } ?: newBalances } } @@ -135,7 +139,7 @@ internal class DefaultYieldsBalancesStore( val balances = stakingIds.mapNotNullTo(hashSetOf()) { stakingId -> val balance = portfolioBalances - .firstOrNull { stakingId == it.getStakingId() } + .firstOrNull { it.stakingId == stakingId } ?: ifNotFound(stakingId) ?: return@mapNotNullTo null @@ -143,7 +147,7 @@ internal class DefaultYieldsBalancesStore( } val updatedBalances = portfolioBalances.addOrReplace(items = balances) { old, new -> - old.getStakingId() == new.getStakingId() + old.stakingId == new.stakingId } put(key = userWalletId, value = updatedBalances) @@ -151,9 +155,7 @@ internal class DefaultYieldsBalancesStore( } } - private fun createErrorYieldBalance(id: StakingID): YieldBalance { - return YieldBalance.Error(integrationId = id.integrationId, address = id.address) - } + private fun createErrorYieldBalance(id: StakingID): YieldBalance = YieldBalance.Error(stakingId = id) private fun YieldBalanceWrapperDTO.getStakingId(): StakingID? { val integrationId = integrationId diff --git a/data/staking/src/test/kotlin/com/tangem/data/staking/YieldBalanceExt.kt b/data/staking/src/test/kotlin/com/tangem/data/staking/YieldBalanceExt.kt index c04560fbeb..6481744478 100644 --- a/data/staking/src/test/kotlin/com/tangem/data/staking/YieldBalanceExt.kt +++ b/data/staking/src/test/kotlin/com/tangem/data/staking/YieldBalanceExt.kt @@ -6,5 +6,5 @@ import com.tangem.domain.models.StatusSource import com.tangem.domain.staking.model.stakekit.YieldBalance internal fun YieldBalanceWrapperDTO.toDomain(source: StatusSource = StatusSource.CACHE): YieldBalance { - return YieldBalanceConverter(source = source).convert(this) + return YieldBalanceConverter(source = source).convert(this)!! } \ No newline at end of file diff --git a/data/staking/src/test/kotlin/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducerTest.kt b/data/staking/src/test/kotlin/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducerTest.kt index b6f14b7e6b..022f10c3fc 100644 --- a/data/staking/src/test/kotlin/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducerTest.kt +++ b/data/staking/src/test/kotlin/com/tangem/data/staking/single/DefaultSingleYieldBalanceProducerTest.kt @@ -84,7 +84,7 @@ internal class DefaultSingleYieldBalanceProducerTest { val producerFlow = producer.produceWithFallback() val balance = MockYieldBalanceWrapperDTOFactory.createWithBalance(tonId).toDomain() - val updatedBalance = YieldBalance.Error(integrationId = tonId.integrationId, address = tonId.address) + val updatedBalance = YieldBalance.Error(stakingId = tonId) // Act (first emit) multiFlow.emit(value = setOf(balance)) @@ -162,7 +162,7 @@ internal class DefaultSingleYieldBalanceProducerTest { val actual1 = getEmittedValues(flow = producerFlow) // Assert (first emit) - val fallbackStatus = YieldBalance.Error(integrationId = tonId.integrationId, address = "0x1") + val fallbackStatus = YieldBalance.Error(stakingId = tonId.copy(address = "0x1")) Truth.assertThat(actual1).hasSize(1) Truth.assertThat(actual1).containsExactly(fallbackStatus) diff --git a/data/staking/src/test/kotlin/com/tangem/data/staking/store/YieldsBalancesStoreUpdateMethodsTest.kt b/data/staking/src/test/kotlin/com/tangem/data/staking/store/YieldsBalancesStoreUpdateMethodsTest.kt index 9d80251ee9..1bece04b93 100644 --- a/data/staking/src/test/kotlin/com/tangem/data/staking/store/YieldsBalancesStoreUpdateMethodsTest.kt +++ b/data/staking/src/test/kotlin/com/tangem/data/staking/store/YieldsBalancesStoreUpdateMethodsTest.kt @@ -129,12 +129,7 @@ internal class YieldsBalancesStoreUpdateMethodsTest { store.storeError(userWalletId = userWalletId, stakingIds = setOf(stakingId)) val runtimeExpected = mapOf( - userWalletId to setOf( - YieldBalance.Error( - integrationId = stakingId.integrationId, - address = stakingId.address, - ), - ), + userWalletId to setOf(YieldBalance.Error(stakingId)), ) Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected) diff --git a/domain/staking/build.gradle.kts b/domain/staking/build.gradle.kts index 770bfe4838..7b65991ac1 100644 --- a/domain/staking/build.gradle.kts +++ b/domain/staking/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { api(projects.core.analytics) api(projects.core.utils) + implementation(deps.kotlin.datetime) implementation(deps.kotlin.serialization) implementation(deps.jodatime) diff --git a/domain/staking/models/build.gradle.kts b/domain/staking/models/build.gradle.kts index 10abc551b9..5d2fa9274e 100644 --- a/domain/staking/models/build.gradle.kts +++ b/domain/staking/models/build.gradle.kts @@ -11,6 +11,7 @@ dependencies { implementation(projects.domain.core) implementation(projects.domain.models) + implementation(deps.kotlin.datetime) implementation(deps.kotlin.serialization) implementation(deps.jodatime) diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingID.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingID.kt index 6313b9277c..59618e15b0 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingID.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingID.kt @@ -1,4 +1,6 @@ package com.tangem.domain.staking.model -// TODO: make part of YieldBalance in the future +import kotlinx.serialization.Serializable + +@Serializable data class StakingID(val integrationId: String, val address: String) \ No newline at end of file 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 44e0681f52..1dd03af2b9 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 @@ -2,166 +2,67 @@ package com.tangem.domain.staking.model.stakekit import com.tangem.domain.models.StatusSource import com.tangem.domain.staking.model.StakingID -import com.tangem.domain.staking.model.stakekit.action.StakingActionType -import org.joda.time.DateTime -import java.math.BigDecimal +import kotlinx.serialization.Serializable -sealed class YieldBalance { +/** + * Represents a yield balance in the staking system + */ +@Serializable +sealed interface YieldBalance { - abstract val integrationId: String? - abstract val address: String? - abstract val source: StatusSource + /** The unique identifier of the staking operation */ + val stakingId: StakingID + /** The source of the status information */ + val source: StatusSource + + /** + * Represents a yield balance with actual data + * + * @property stakingId the unique identifier of the staking operation + * @property source the source of the status information + * @property balance the balance details of the yield + */ + @Serializable + data class Data( + override val stakingId: StakingID, + override val source: StatusSource, + val balance: YieldBalanceItem, + ) : YieldBalance + + /** + * Represents an empty yield balance + * + * @property stakingId the unique identifier of the staking operation + * @property source the source of the status information + */ + @Serializable + data class Empty( + override val stakingId: StakingID, + override val source: StatusSource, + ) : YieldBalance + + /** + * Represents an error state for the yield balance + * + * @property stakingId the unique identifier of the staking operation + */ + @Serializable + data class Error(override val stakingId: StakingID) : YieldBalance { + override val source: StatusSource = StatusSource.ACTUAL + } + + /** + * Creates a copy of the current yield balance with a new status source + * + * @param source the new source of the status information + */ fun copySealed(source: StatusSource): YieldBalance { return when (this) { is Data -> copy(source = source) is Empty -> copy(source = source) is Error, - is Unsupported, -> this } } - - fun getStakingId(): StakingID? { - val integrationId = integrationId - val address = address - - if (integrationId == null || address == null) return null - - return StakingID(integrationId = integrationId, address = address) - } - - data class Data( - override val integrationId: String?, - override val address: String, - override val source: StatusSource, - val balance: YieldBalanceItem, - ) : YieldBalance() - - data class Empty( - override val integrationId: String?, - override val address: String, - override val source: StatusSource, - ) : YieldBalance() - - data object Unsupported : YieldBalance() { - override val integrationId: String? = null - override val address: String? = null - override val source: StatusSource = StatusSource.ACTUAL - } - - data class Error(override val integrationId: String?, override val address: String?) : YieldBalance() { - override val source: StatusSource = StatusSource.ACTUAL - } -} - -data class YieldBalanceItem( - val items: List, - val integrationId: String?, -) - -data class BalanceItem( - val groupId: String, - val token: Token, - val type: BalanceType, - val amount: BigDecimal, - val rawCurrencyId: String?, - val validatorAddress: String?, - val date: DateTime?, - val pendingActions: List, - val pendingActionsConstraints: List, - val isPending: Boolean, -) - -data class PendingActionConstraints( - val type: StakingActionType, - val amountArg: PendingAction.PendingActionArgs.Amount?, -) - -data class PendingAction( - val type: StakingActionType, - val passthrough: String, - val args: PendingActionArgs?, -) { - data class PendingActionArgs( - val amount: Amount?, - val duration: Duration?, - val validatorAddress: Boolean?, - val validatorAddresses: Boolean?, - val tronResource: TronResource?, - val signatureVerification: Boolean?, - ) { - data class Amount( - val required: Boolean, - val minimum: BigDecimal?, - val maximum: BigDecimal?, - ) - - data class Duration( - val required: Boolean, - val minimum: Int?, - val maximum: Int?, - ) - - data class TronResource( - val required: Boolean, - val options: List, - ) - } -} - -/** - * 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 { - fun BalanceType.isClickable() = when (this) { - STAKED, - UNSTAKED, - LOCKED, - -> true - AVAILABLE, - UNSTAKING, - PREPARING, - REWARDS, - UNLOCKING, - UNKNOWN, - -> false - } - } -} - -enum class RewardBlockType { - NoRewards, - Rewards, - RewardsRequirementsError, - RewardUnavailable, - ; - - /** - * Indicated whether action can be performed on available reward - * For example, pending action CLAIM_REWARDS could be called - */ - val isActionable: Boolean - get() = when (this) { - NoRewards, - RewardUnavailable, - -> false - RewardsRequirementsError, - Rewards, - -> true - } } \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalanceItem.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalanceItem.kt new file mode 100644 index 0000000000..b5bbd086b5 --- /dev/null +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalanceItem.kt @@ -0,0 +1,130 @@ +package com.tangem.domain.staking.model.stakekit + +import com.tangem.domain.core.serialization.SerializedBigDecimal +import com.tangem.domain.staking.model.stakekit.action.StakingActionType +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class YieldBalanceItem( + val items: List, + val integrationId: String, +) + +@Serializable +data class BalanceItem( + val groupId: String, + val token: Token, + val type: BalanceType, + val amount: SerializedBigDecimal, + val rawCurrencyId: String?, + val validatorAddress: String?, + val date: Instant?, + val pendingActions: List, + val pendingActionsConstraints: List, + val isPending: Boolean, +) + +@Serializable +data class PendingActionConstraints( + val type: StakingActionType, + val amountArg: PendingAction.PendingActionArgs.Amount?, +) + +@Serializable +data class PendingAction( + val type: StakingActionType, + val passthrough: String, + val args: PendingActionArgs?, +) { + + @Serializable + data class PendingActionArgs( + val amount: Amount?, + val duration: Duration?, + val validatorAddress: Boolean?, + val validatorAddresses: Boolean?, + val tronResource: TronResource?, + val signatureVerification: Boolean?, + ) { + + @Serializable + data class Amount( + val required: Boolean, + val minimum: SerializedBigDecimal?, + val maximum: SerializedBigDecimal?, + ) + + @Serializable + data class Duration( + val required: Boolean, + val minimum: Int?, + val maximum: Int?, + ) + + @Serializable + data class TronResource( + val required: Boolean, + val options: List, + ) + } +} + +/** + * IMPORTANT!!! + * Order is used to sort balances. + */ +@Serializable +@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 { + + fun BalanceType.isClickable() = when (this) { + STAKED, + UNSTAKED, + LOCKED, + -> true + AVAILABLE, + UNSTAKING, + PREPARING, + REWARDS, + UNLOCKING, + UNKNOWN, + -> false + } + } +} + +@Serializable +enum class RewardBlockType { + NoRewards, + Rewards, + RewardsRequirementsError, + RewardUnavailable, + ; + + /** + * Indicated whether action can be performed on available reward + * For example, pending action CLAIM_REWARDS could be called + */ + val isActionable: Boolean + get() = when (this) { + NoRewards, + RewardUnavailable, + -> false + RewardsRequirementsError, + Rewards, + -> true + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt index 9aaf902fa7..70f695f4ef 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt @@ -420,7 +420,7 @@ abstract class BaseCurrencyStatusOperations( params = MultiYieldBalanceProducer.Params(userWalletId = userWalletId), ) .orEmpty() - .filter { it.getStakingId() in stakingIds } + .filter { it.stakingId in stakingIds } ensure(balances.isNotEmpty()) { Error.EmptyYieldBalances } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt index 0948480baa..797a76ca5f 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt @@ -25,6 +25,7 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.quotes.single.SingleQuoteStatusProducer import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier import com.tangem.domain.staking.StakingIdFactory +import com.tangem.domain.staking.model.StakingID import com.tangem.domain.staking.model.StakingIntegrationID import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher @@ -277,12 +278,17 @@ class CachedCurrenciesStatusesOperations( ): YieldBalance? { if (yieldBalances.isNullOrEmpty()) return null - val supportedIntegration = StakingIntegrationID.create(currencyId = currency.id)?.value ?: return null - + val supportedIntegration = StakingIntegrationID.create(currencyId = currency.id)?.value val address = extractAddress(networkStatus) - return yieldBalances.firstOrNull { it.integrationId == supportedIntegration && it.address == address } - ?: YieldBalance.Error(integrationId = supportedIntegration, address = address) + return if (supportedIntegration != null && address != null) { + val stakingId = StakingID(integrationId = supportedIntegration, address = address) + + yieldBalances.firstOrNull { it.stakingId == stakingId } + ?: YieldBalance.Error(stakingId = stakingId) + } else { + null + } } private fun getCurrencies(userWalletId: UserWalletId): EitherFlow> { @@ -390,7 +396,7 @@ class CachedCurrenciesStatusesOperations( ) .onEach { balance -> state.update { loadedBalances -> - loadedBalances.addOrReplace(balance) { balance.getStakingId() == it } + loadedBalances.addOrReplace(balance) { balance.stakingId == it } } } .launchIn(scope = this) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt index 82c396b43b..ca1b7b0fcb 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt @@ -80,7 +80,8 @@ internal class CurrencyStatusOperations( val hasCurrentNetworkTransactions = networkStatusValue.pendingTransactions.isNotEmpty() val currentTransactions = networkStatusValue.pendingTransactions.getOrElse(currency.id, ::emptySet) val yieldBalanceData = yieldBalance as? YieldBalance.Data - val isCurrentAddressStaking = yieldBalanceData?.address == networkStatusValue.address.defaultAddress.value + val isCurrentAddressStaking = + yieldBalanceData?.stakingId?.address == networkStatusValue.address.defaultAddress.value val filteredTokenBalances = yieldBalanceData?.balance?.items?.filter { it.token.coinGeckoId == currency.id.rawCurrencyId?.value } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/CurrencyStatusProxyCreator.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/CurrencyStatusProxyCreator.kt index c1778e6af1..53b7db96ff 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/CurrencyStatusProxyCreator.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/CurrencyStatusProxyCreator.kt @@ -7,6 +7,7 @@ import arrow.core.toNonEmptySetOrNull import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.NetworkStatus import com.tangem.domain.models.quote.QuoteStatus +import com.tangem.domain.staking.model.StakingID import com.tangem.domain.staking.model.StakingIntegrationID import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -74,9 +75,12 @@ class CurrencyStatusProxyCreator { val address = extractAddress(networkStatus) val supportedIntegration = StakingIntegrationID.create(currencyId = currency.id)?.value - val yieldBalance = if (supportedIntegration != null) { - yieldBalances?.firstOrNull { it.integrationId == supportedIntegration && it.address == address } - ?: YieldBalance.Error(integrationId = supportedIntegration, address = address) + + val yieldBalance = if (supportedIntegration != null && address != null) { + val stakingId = StakingID(integrationId = supportedIntegration, address = address) + + yieldBalances?.firstOrNull { it.stakingId == stakingId } + ?: YieldBalance.Error(stakingId = stakingId) } else { null } diff --git a/features/staking/impl/build.gradle.kts b/features/staking/impl/build.gradle.kts index 762201aacb..67246e25f1 100644 --- a/features/staking/impl/build.gradle.kts +++ b/features/staking/impl/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { implementation(deps.androidx.paging.runtime) /** Other dependencies */ + implementation(deps.kotlin.datetime) implementation(deps.kotlin.immutable.collections) implementation(deps.material) implementation(deps.arrow.core) 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 index 6b747f4dbd..bdc0f43c77 100644 --- 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 @@ -22,7 +22,7 @@ import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import com.tangem.utils.extensions.orZero import kotlinx.collections.immutable.toPersistentList -import org.joda.time.DateTime +import kotlinx.datetime.Instant import java.math.BigDecimal import java.util.Calendar @@ -122,7 +122,7 @@ internal class BalanceItemConverter( -> null } - private fun getUnbondingDate(date: DateTime?): TextReference? { + private fun getUnbondingDate(date: Instant?): TextReference? { val unbondingPeriod = yield.metadata.cooldownPeriod?.days ?: return null if (date == null) { return combinedReference( @@ -136,7 +136,7 @@ internal class BalanceItemConverter( nowCalendar.resetHours() val endDate = Calendar.getInstance() - endDate.timeInMillis = date.millis + endDate.timeInMillis = date.toEpochMilliseconds() endDate.resetHours() val days = ((endDate.timeInMillis - nowCalendar.timeInMillis) / DAY_IN_MILLIS).toInt() 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 ee906adb33..f82c1ece1f 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 @@ -50,7 +50,7 @@ internal class YieldBalancesConverter( ?.firstOrNull { it.type == StakingActionType.CLAIM_REWARDS } InnerYieldBalanceState.Data( - integrationId = yieldBalance?.integrationId, + integrationId = yieldBalance?.stakingId?.integrationId, reward = YieldReward( rewardsCrypto = cryptoRewardsValue.format { crypto(cryptoCurrency) }, rewardsFiat = fiatRewardsValue.format { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 03d9b1f740..1f665af06c 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -73,6 +73,7 @@ viewBindingDelegate = "1.5.9" xmlShimmer = "1.1.3" zxingQrCode = "3.5.1" kotlinSerialization = "1.8.0" +kotlinDatetime = "0.6.2" arrow = "1.2.4" # 2.0.1 breaks the build reownCore = "1.1.2" reownWeb3 = "1.1.2" @@ -258,6 +259,7 @@ viewBindingDelegate = { module = "com.github.kirich1409:viewbindingpropertydeleg xmlShimmer = { module = "com.github.skydoves:androidveil", version.ref = "xmlShimmer" } zxing-qrCore = { module = "com.google.zxing:core", version.ref = "zxingQrCode" } kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" } +kotlin-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinDatetime" } arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } arrow-fx = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" } reownCore = { module = "com.reown:android-core", version.ref = "reownCore" }