Updated on 2026-08-14
This commit is contained in:
parent
a335e8d8f4
commit
fa125539b4
7 changed files with 165 additions and 12 deletions
|
|
@ -8,9 +8,10 @@ import kotlinx.serialization.Serializable
|
|||
/**
|
||||
* Represents a list of account statuses associated with a user wallet
|
||||
*
|
||||
* @property userWallet the user wallet to which the account statuses belong
|
||||
* @property accountStatuses a set of account statuses associated with the user wallet
|
||||
* @property totalAccounts the total number of accounts
|
||||
* @property userWallet the user wallet to which the account statuses belong
|
||||
* @property accountStatuses a set of account statuses associated with the user wallet
|
||||
* @property totalAccounts the total number of accounts (including archived ones)
|
||||
* @property totalFiatBalance the total fiat balance across all accounts
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -19,7 +20,7 @@ data class AccountStatusList(
|
|||
val userWallet: UserWallet,
|
||||
val accountStatuses: Set<AccountStatus>,
|
||||
val totalAccounts: Int,
|
||||
val totalFiatBalance: TotalFiatBalance = TotalFiatBalance.Failed,
|
||||
val totalFiatBalance: TotalFiatBalance,
|
||||
) {
|
||||
|
||||
val mainAccount: AccountStatus
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.domain.core.utils.flatMap
|
|||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* A sealed class representing the three states of a data load operation: Loading, Content, and Error.
|
||||
|
|
@ -12,6 +13,7 @@ import com.tangem.domain.core.utils.lceLoading
|
|||
* @param E The type of the error object.
|
||||
* @param C The type of the content object.
|
||||
*/
|
||||
@Serializable
|
||||
sealed class Lce<out E : Any, out C : Any> {
|
||||
|
||||
/**
|
||||
|
|
@ -19,6 +21,7 @@ sealed class Lce<out E : Any, out C : Any> {
|
|||
*
|
||||
* @param partialContent The partial content that has been loaded so far, if any.
|
||||
*/
|
||||
@Serializable
|
||||
data class Loading<C : Any>(val partialContent: C?) : Lce<Nothing, C>()
|
||||
|
||||
/**
|
||||
|
|
@ -26,6 +29,7 @@ sealed class Lce<out E : Any, out C : Any> {
|
|||
*
|
||||
* @param content The loaded content.
|
||||
*/
|
||||
@Serializable
|
||||
data class Content<C : Any>(val content: C) : Lce<Nothing, C>()
|
||||
|
||||
/**
|
||||
|
|
@ -33,6 +37,7 @@ sealed class Lce<out E : Any, out C : Any> {
|
|||
*
|
||||
* @param error The error that occurred during loading.
|
||||
*/
|
||||
@Serializable
|
||||
data class Error<E : Any>(val error: E) : Lce<E, Nothing>()
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscatingVariants
|
||||
|
||||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
|
|
@ -13,6 +11,7 @@ tasks.withType<Test>().configureEach {
|
|||
|
||||
dependencies {
|
||||
api(projects.domain.visa.models)
|
||||
api(projects.domain.core)
|
||||
api(projects.core.utils)
|
||||
|
||||
implementation(tangemDeps.card.core)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.models.quote.PriceChange
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
@ -17,12 +19,14 @@ sealed interface AccountStatus {
|
|||
/**
|
||||
* Represents the status of a crypto portfolio account
|
||||
*
|
||||
* @property account the crypto portfolio account
|
||||
* @property tokenList the list of tokens associated with the account
|
||||
* @property account the crypto portfolio account
|
||||
* @property tokenList the list of tokens associated with the account
|
||||
* @property priceChangeLce the price change information wrapped in a Lce (Loading, Content, Error) state
|
||||
*/
|
||||
@Serializable
|
||||
data class CryptoPortfolio(
|
||||
override val account: Account.CryptoPortfolio,
|
||||
val tokenList: TokenList,
|
||||
val priceChangeLce: Lce<Unit, PriceChange>,
|
||||
) : AccountStatus
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
package com.tangem.domain.models.quote
|
||||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import java.math.BigDecimal
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents the price change of a cryptocurrency asset over a specific time period.
|
||||
|
|
@ -11,7 +12,8 @@ import java.math.BigDecimal
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Serializable
|
||||
data class PriceChange(
|
||||
val value: BigDecimal,
|
||||
val value: SerializedBigDecimal,
|
||||
val source: StatusSource,
|
||||
)
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.domain.tokens.operations
|
||||
|
||||
import arrow.core.NonEmptyList
|
||||
import arrow.core.toNonEmptyListOrNull
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.getResultStatusSource
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
|
@ -37,6 +39,26 @@ object TotalFiatBalanceCalculator {
|
|||
}
|
||||
}
|
||||
|
||||
fun calculate(balances: List<TotalFiatBalance>): TotalFiatBalance {
|
||||
val nonEmptyBalances = balances.toNonEmptyListOrNull()
|
||||
?: return TokenList.Empty.totalFiatBalance
|
||||
|
||||
val computationState = ComputationState.resolve(balances = nonEmptyBalances)
|
||||
|
||||
return when (computationState) {
|
||||
ComputationState.LOADING -> TotalFiatBalance.Loading
|
||||
ComputationState.NON_COMPUTABLE -> TotalFiatBalance.Failed
|
||||
ComputationState.COMPUTABLE -> {
|
||||
val loaded = balances.filterIsInstance<TotalFiatBalance.Loaded>()
|
||||
|
||||
TotalFiatBalance.Loaded(
|
||||
amount = loaded.sumOf(TotalFiatBalance.Loaded::amount),
|
||||
source = loaded.map(TotalFiatBalance.Loaded::source).getResultStatusSource(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun compute(statuses: NonEmptyList<CryptoCurrencyStatus>): TotalFiatBalance {
|
||||
var mutableBalance: TotalFiatBalance = TotalFiatBalance.Loading
|
||||
|
||||
|
|
@ -189,6 +211,18 @@ object TotalFiatBalanceCalculator {
|
|||
|
||||
return COMPUTABLE
|
||||
}
|
||||
|
||||
fun resolve(balances: List<TotalFiatBalance>): ComputationState {
|
||||
for (balance in balances) {
|
||||
when (balance) {
|
||||
TotalFiatBalance.Failed -> return NON_COMPUTABLE
|
||||
TotalFiatBalance.Loading -> return LOADING
|
||||
is TotalFiatBalance.Loaded -> continue
|
||||
}
|
||||
}
|
||||
|
||||
return COMPUTABLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ class TotalFiatBalanceCalculatorTest {
|
|||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class LoadingOrNonComputable {
|
||||
inner class CalculateCurrenciesLoadingOrNonComputable {
|
||||
|
||||
@Test
|
||||
fun `one token is Loading, total is Loading`() {
|
||||
|
|
@ -117,7 +117,7 @@ class TotalFiatBalanceCalculatorTest {
|
|||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Computable {
|
||||
inner class CalculateCurrenciesComputable {
|
||||
|
||||
@Test
|
||||
fun `Unreachable token isIncludeToBalanceOnError, total is Loaded`() {
|
||||
|
|
@ -377,6 +377,114 @@ class TotalFiatBalanceCalculatorTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class CalculateBalancesLoadingOrNonComputable {
|
||||
|
||||
@Test
|
||||
fun `one balance is Loading, total is Loading`() {
|
||||
// Arrange
|
||||
val balances = nonEmptyListOf(
|
||||
TotalFiatBalance.Loading,
|
||||
TotalFiatBalance.Failed,
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = TotalFiatBalanceCalculator.calculate(balances)
|
||||
|
||||
// Assert
|
||||
val expected = TotalFiatBalance.Loading
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `one balance is Failed, total is Failed`() {
|
||||
// Arrange
|
||||
val balances = nonEmptyListOf(
|
||||
TotalFiatBalance.Failed,
|
||||
TotalFiatBalance.Loaded(amount = BigDecimal.ONE, source = StatusSource.ACTUAL),
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = TotalFiatBalanceCalculator.calculate(balances)
|
||||
|
||||
// Assert
|
||||
val expected = TotalFiatBalance.Failed
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class CalculateBalancesComputable {
|
||||
|
||||
@Test
|
||||
fun `all balances are Loaded, total is Loaded`() {
|
||||
// Arrange
|
||||
val balances = nonEmptyListOf(
|
||||
TotalFiatBalance.Loaded(amount = BigDecimal.TEN, source = StatusSource.ACTUAL),
|
||||
TotalFiatBalance.Loaded(amount = BigDecimal.ONE, source = StatusSource.ACTUAL),
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = TotalFiatBalanceCalculator.calculate(balances)
|
||||
|
||||
// Assert
|
||||
val expected = TotalFiatBalance.Loaded(
|
||||
amount = BigDecimal(11),
|
||||
source = StatusSource.ACTUAL,
|
||||
)
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `StatusSource is Actual is all balances are Actual`() {
|
||||
// Arrange
|
||||
val balances = nonEmptyListOf(
|
||||
TotalFiatBalance.Loaded(amount = BigDecimal.TEN, source = StatusSource.ACTUAL),
|
||||
TotalFiatBalance.Loaded(amount = BigDecimal.ONE, source = StatusSource.ACTUAL),
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = (TotalFiatBalanceCalculator.calculate(balances) as TotalFiatBalance.Loaded).source
|
||||
|
||||
// Assert
|
||||
val expected = StatusSource.ACTUAL
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `StatusSource is Cache is any balance is Cache`() {
|
||||
// Arrange
|
||||
val balances = nonEmptyListOf(
|
||||
TotalFiatBalance.Loaded(amount = BigDecimal.TEN, source = StatusSource.ACTUAL),
|
||||
TotalFiatBalance.Loaded(amount = BigDecimal.ONE, source = StatusSource.CACHE),
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = (TotalFiatBalanceCalculator.calculate(balances) as TotalFiatBalance.Loaded).source
|
||||
|
||||
// Assert
|
||||
val expected = StatusSource.CACHE
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `StatusSource is ONLY_CACHE is any balance is ONLY_CACHE`() {
|
||||
// Arrange
|
||||
val balances = nonEmptyListOf(
|
||||
TotalFiatBalance.Loaded(amount = BigDecimal.TEN, source = StatusSource.ACTUAL),
|
||||
TotalFiatBalance.Loaded(amount = BigDecimal.ONE, source = StatusSource.ONLY_CACHE),
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = (TotalFiatBalanceCalculator.calculate(balances) as TotalFiatBalance.Loaded).source
|
||||
// Assert
|
||||
val expected = StatusSource.ONLY_CACHE
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLoading(currency: CryptoCurrency): CryptoCurrencyStatus {
|
||||
return CryptoCurrencyStatus(
|
||||
currency = currency,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue