Updated on 2026-08-14
This commit is contained in:
parent
a2e602f1f1
commit
db0c6cc933
11 changed files with 252 additions and 83 deletions
|
|
@ -14,10 +14,7 @@ import com.tangem.domain.tokens.repository.QuotesRepository
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flatMapMerge
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
class GetTokenListUseCase(
|
||||
internal val currenciesRepository: CurrenciesRepository,
|
||||
|
|
@ -27,8 +24,8 @@ class GetTokenListUseCase(
|
|||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = true): Flow<Either<TokenListError, TokenList>> {
|
||||
return getTokensStatuses(userWalletId, refresh).flatMapMerge flatMap@{ maybeTokens ->
|
||||
operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = false): Flow<Either<TokenListError, TokenList>> {
|
||||
return getTokensStatuses(userWalletId, refresh).flatMapMerge { maybeTokens ->
|
||||
maybeTokens.fold(
|
||||
ifLeft = { error ->
|
||||
flowOf(error.left())
|
||||
|
|
|
|||
|
|
@ -86,4 +86,15 @@ data class CryptoCurrencyStatus(
|
|||
override val priceChange: BigDecimal?,
|
||||
override val hasTransactionsInProgress: Boolean,
|
||||
) : Status()
|
||||
|
||||
/**
|
||||
* Represents a state where the token is available, but there is no current quote available for it.
|
||||
*
|
||||
* @property amount The amount of the token.
|
||||
* @property hasTransactionsInProgress Indicates if there are any transactions in progress related to the token.
|
||||
*/
|
||||
data class NoQuote(
|
||||
override val amount: BigDecimal,
|
||||
override val hasTransactionsInProgress: Boolean,
|
||||
) : Status()
|
||||
}
|
||||
|
|
@ -3,7 +3,8 @@ package com.tangem.domain.tokens.operations
|
|||
import arrow.core.*
|
||||
import arrow.core.raise.*
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import com.tangem.domain.tokens.models.Quote
|
||||
|
|
@ -36,25 +37,41 @@ internal class CurrenciesStatusesOperations(
|
|||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun getCurrenciesStatusesFlow(): Flow<Either<Error, List<CryptoCurrencyStatus>>> {
|
||||
return getMultiCurrencyWalletCurrencies().flatMapMerge flatMap@{ maybeCurrencies ->
|
||||
return getMultiCurrencyWalletCurrencies().transformLatest { maybeCurrencies ->
|
||||
val nonEmptyCurrencies = maybeCurrencies.fold(
|
||||
ifLeft = { error ->
|
||||
return@flatMap flowOf(error.left())
|
||||
emit(error.left())
|
||||
return@transformLatest
|
||||
},
|
||||
ifRight = { it.toNonEmptyListOrNull() },
|
||||
) ?: return@flatMap flowOf(emptyList<CryptoCurrencyStatus>().right())
|
||||
ifRight = List<CryptoCurrency>::toNonEmptyListOrNull,
|
||||
)
|
||||
|
||||
if (nonEmptyCurrencies == null) {
|
||||
val emptyCurrenciesStatuses = emptyList<CryptoCurrencyStatus>()
|
||||
|
||||
emit(emptyCurrenciesStatuses.right())
|
||||
return@transformLatest
|
||||
} else if (!refresh) {
|
||||
val maybeLoadingCurrenciesStatuses = createCurrenciesStatuses(
|
||||
currencies = nonEmptyCurrencies,
|
||||
maybeNetworkStatuses = null,
|
||||
maybeQuotes = null,
|
||||
)
|
||||
|
||||
emit(maybeLoadingCurrenciesStatuses)
|
||||
}
|
||||
|
||||
val (networksIds, currenciesIds) = getIds(nonEmptyCurrencies)
|
||||
|
||||
combine(
|
||||
val currenciesFlow = combine(
|
||||
getQuotes(currenciesIds),
|
||||
getNetworksStatuses(networksIds),
|
||||
) { maybeQuotes, maybeNetworksStatuses ->
|
||||
either {
|
||||
createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes.bind(), maybeNetworksStatuses.bind())
|
||||
}
|
||||
createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses)
|
||||
}
|
||||
}
|
||||
|
||||
emitAll(currenciesFlow)
|
||||
}.conflate()
|
||||
}
|
||||
|
||||
suspend fun getCurrencyStatusFlow(currencyId: CryptoCurrency.ID): Flow<Either<Error, CryptoCurrencyStatus>> {
|
||||
|
|
@ -76,14 +93,16 @@ internal class CurrenciesStatusesOperations(
|
|||
}
|
||||
|
||||
private fun getCurrencyStatusFlow(currency: CryptoCurrency): Flow<Either<Error, CryptoCurrencyStatus>> {
|
||||
val quoteFlow = getQuotes(nonEmptySetOf(currency.id))
|
||||
val (networksIds, currenciesIds) = getIds(nonEmptyListOf(currency))
|
||||
|
||||
val quoteFlow = getQuotes(currenciesIds)
|
||||
.map { maybeQuotes ->
|
||||
maybeQuotes.map { quotes ->
|
||||
quotes.singleOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
|
||||
}
|
||||
}
|
||||
|
||||
val statusFlow = getNetworksStatuses(nonEmptySetOf(currency.networkId))
|
||||
val statusFlow = getNetworksStatuses(networksIds)
|
||||
.map { maybeStatuses ->
|
||||
maybeStatuses.map { statuses ->
|
||||
statuses.singleOrNull { it.networkId == currency.networkId }
|
||||
|
|
@ -91,34 +110,58 @@ internal class CurrenciesStatusesOperations(
|
|||
}
|
||||
|
||||
return combine(quoteFlow, statusFlow) { maybeQuote, maybeNetworkStatus ->
|
||||
either {
|
||||
createStatus(currency, maybeQuote.bind(), maybeNetworkStatus.bind())
|
||||
}
|
||||
createStatus(currency, maybeQuote, maybeNetworkStatus)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCurrenciesStatuses(
|
||||
currencies: NonEmptyList<CryptoCurrency>,
|
||||
quotes: Set<Quote>,
|
||||
networkStatuses: Set<NetworkStatus>,
|
||||
): List<CryptoCurrencyStatus> {
|
||||
return currencies.map { currency ->
|
||||
val quote = quotes.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
|
||||
val networkStatus = networkStatuses.firstOrNull { it.networkId == currency.networkId }
|
||||
maybeQuotes: Either<Error, Set<Quote>>?,
|
||||
maybeNetworkStatuses: Either<Error, Set<NetworkStatus>>?,
|
||||
): Either<Error, List<CryptoCurrencyStatus>> = either {
|
||||
var quotesRetrievingFailed = false
|
||||
|
||||
createStatus(currency, quote, networkStatus)
|
||||
val networksStatuses = maybeNetworkStatuses?.bind()?.toNonEmptySetOrNull()
|
||||
val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) {
|
||||
quotesRetrievingFailed = true
|
||||
null
|
||||
}
|
||||
|
||||
currencies.map { currency ->
|
||||
val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
|
||||
val networkStatus = networksStatuses?.firstOrNull { it.networkId == currency.networkId }
|
||||
|
||||
createStatus(currency, quote, networkStatus, ignoreQuote = quotesRetrievingFailed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createStatus(
|
||||
token: CryptoCurrency,
|
||||
currency: CryptoCurrency,
|
||||
maybeQuote: Either<Error, Quote?>,
|
||||
maybeNetworkStatus: Either<Error, NetworkStatus?>,
|
||||
): Either<Error, CryptoCurrencyStatus> = either {
|
||||
var quoteRetrievingFailed = false
|
||||
|
||||
val networkStatus = maybeNetworkStatus.bind()
|
||||
val quote = recover({ maybeQuote.bind() }) {
|
||||
quoteRetrievingFailed = true
|
||||
null
|
||||
}
|
||||
|
||||
createStatus(currency, quote, networkStatus, ignoreQuote = quoteRetrievingFailed)
|
||||
}
|
||||
|
||||
private fun createStatus(
|
||||
currency: CryptoCurrency,
|
||||
quote: Quote?,
|
||||
networkStatus: NetworkStatus?,
|
||||
ignoreQuote: Boolean,
|
||||
): CryptoCurrencyStatus {
|
||||
val currencyStatusOperations = CurrencyStatusOperations(
|
||||
currency = token,
|
||||
currency = currency,
|
||||
quote = quote,
|
||||
networkStatus = networkStatus,
|
||||
ignoreQuote = ignoreQuote,
|
||||
)
|
||||
|
||||
return currencyStatusOperations.createTokenStatus()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ internal class CurrencyStatusOperations(
|
|||
private val currency: CryptoCurrency,
|
||||
private val quote: Quote?,
|
||||
private val networkStatus: NetworkStatus?,
|
||||
private val ignoreQuote: Boolean,
|
||||
) {
|
||||
|
||||
fun createTokenStatus(): CryptoCurrencyStatus = CryptoCurrencyStatus(currency, createStatus())
|
||||
|
|
@ -28,6 +29,10 @@ internal class CurrencyStatusOperations(
|
|||
val amount = status.amounts[currency.id] ?: return CryptoCurrencyStatus.Unreachable
|
||||
|
||||
return when {
|
||||
ignoreQuote -> CryptoCurrencyStatus.NoQuote(
|
||||
amount = amount,
|
||||
hasTransactionsInProgress = status.hasTransactionsInProgress,
|
||||
)
|
||||
currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom(
|
||||
amount = amount,
|
||||
fiatAmount = calculateFiatAmountOrNull(amount, quote?.fiatRate),
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ internal class TokenListFiatBalanceOperations(
|
|||
break
|
||||
}
|
||||
is CryptoCurrencyStatus.NoAccount -> {
|
||||
fiatBalance = recalculateBalanceForNoAccountStatus(fiatBalance)
|
||||
fiatBalance = recalculateBalanceWithoutQuote(fiatBalance)
|
||||
}
|
||||
is CryptoCurrencyStatus.Loaded -> {
|
||||
fiatBalance = recalculateBalance(status, fiatBalance)
|
||||
|
|
@ -35,13 +35,16 @@ internal class TokenListFiatBalanceOperations(
|
|||
is CryptoCurrencyStatus.Custom -> {
|
||||
fiatBalance = recalculateBalance(status, fiatBalance)
|
||||
}
|
||||
is CryptoCurrencyStatus.NoQuote -> {
|
||||
fiatBalance = recalculateBalanceWithoutQuote(fiatBalance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fiatBalance
|
||||
}
|
||||
|
||||
private fun recalculateBalanceForNoAccountStatus(currentBalance: TokenList.FiatBalance): TokenList.FiatBalance {
|
||||
private fun recalculateBalanceWithoutQuote(currentBalance: TokenList.FiatBalance): TokenList.FiatBalance {
|
||||
return with(currentBalance) {
|
||||
(this as? TokenList.FiatBalance.Loaded)?.copy(
|
||||
isAllAmountsSummarized = false,
|
||||
|
|
|
|||
|
|
@ -59,9 +59,9 @@ internal class GetPrimaryCurrencyUseCaseTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `when quotes getting failed then error should be received`() = runTest {
|
||||
fun `when quotes getting failed then currency with no quote status should be received`() = runTest {
|
||||
// Given
|
||||
val expectedResult = CurrencyError.DataError(DataError.NetworkError.NoInternetConnection).left()
|
||||
val expectedResult = MockTokensStates.noQuotesTokensStatuses.first().right()
|
||||
|
||||
val useCase = getUseCase(quotes = flowOf(DataError.NetworkError.NoInternetConnection.left()))
|
||||
|
||||
|
|
@ -100,8 +100,8 @@ internal class GetPrimaryCurrencyUseCaseTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `when quotes flow is empty then error should be received`() = runTest {
|
||||
val expectedResult = CurrencyError.UnableToCreateCurrency.left()
|
||||
fun `when quotes flow is empty then no quote status should be received`() = runTest {
|
||||
val expectedResult = MockTokensStates.noQuotesTokensStatuses.first().right()
|
||||
|
||||
val useCase = getUseCase(quotes = flowOf())
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.domain.tokens.mock.MockQuotes
|
|||
import com.tangem.domain.tokens.mock.MockTokenLists
|
||||
import com.tangem.domain.tokens.mock.MockTokens
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import com.tangem.domain.tokens.models.Quote
|
||||
|
|
@ -32,7 +33,10 @@ internal class GetTokenListUseCaseTest {
|
|||
@Test
|
||||
fun `when list ungrouped and unsorted then correct token list should be returned`() = runTest {
|
||||
// Given
|
||||
val expectedResult = MockTokenLists.failedUngroupedTokenList.right()
|
||||
val expectedResult = listOf(
|
||||
MockTokenLists.loadingUngroupedTokenList.right(),
|
||||
MockTokenLists.failedUngroupedTokenList.right(),
|
||||
)
|
||||
|
||||
val useCase = getUseCase(
|
||||
isGrouped = flowOf(false.right()),
|
||||
|
|
@ -40,7 +44,30 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list refreshed then correct token list should be returned`() = runTest {
|
||||
// Given
|
||||
val expectedResult = listOf(
|
||||
MockTokenLists.failedUngroupedTokenList.right(),
|
||||
)
|
||||
|
||||
val useCase = getUseCase(
|
||||
isGrouped = flowOf(false.right()),
|
||||
isSortedByBalance = flowOf(false.right()),
|
||||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId, refresh = true)
|
||||
.take(count = 1)
|
||||
.toList()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -61,14 +88,22 @@ internal class GetTokenListUseCaseTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `when quotes getting failed then error should be received`() = runTest {
|
||||
fun `when quotes getting failed then token list without quotes should be received`() = runTest {
|
||||
// Given
|
||||
val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left()
|
||||
val expectedResult = listOf(
|
||||
MockTokenLists.loadingUngroupedTokenList.right(),
|
||||
MockTokenLists.noQuotesUngroupedTokenList.right(),
|
||||
)
|
||||
|
||||
val useCase = getUseCase(quotes = flowOf(DataError.NetworkError.NoInternetConnection.left()))
|
||||
val useCase = getUseCase(
|
||||
quotes = flowOf(DataError.NetworkError.NoInternetConnection.left()),
|
||||
statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
|
||||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -99,7 +134,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(statuses = flowOf(DataError.NetworkError.NoInternetConnection.left()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase(userWalletId, refresh = true).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -138,6 +173,7 @@ internal class GetTokenListUseCaseTest {
|
|||
// Given
|
||||
val error = DataError.NetworkError.NoInternetConnection.left()
|
||||
val expectedResult = listOf(
|
||||
MockTokenLists.loadingUngroupedTokenList.right(),
|
||||
MockTokenLists.failedUngroupedTokenList.right(),
|
||||
TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left(),
|
||||
)
|
||||
|
|
@ -151,7 +187,7 @@ internal class GetTokenListUseCaseTest {
|
|||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
.take(count = 2)
|
||||
.take(count = 3)
|
||||
.toList()
|
||||
|
||||
// Then
|
||||
|
|
@ -160,12 +196,17 @@ internal class GetTokenListUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `when list grouped then correct token list should be received`() = runTest {
|
||||
val expectedResult = MockTokenLists.failedGroupedTokenList.right()
|
||||
val expectedResult = listOf(
|
||||
MockTokenLists.loadingGroupedTokenList.right(),
|
||||
MockTokenLists.failedGroupedTokenList.right(),
|
||||
)
|
||||
|
||||
val useCase = getUseCase(isGrouped = flowOf(true.right()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -189,7 +230,10 @@ internal class GetTokenListUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `when list is sorted and ungrouped then correct token list should be received`() = runTest {
|
||||
val expectedResult = MockTokenLists.sortedUngroupedTokenList.right()
|
||||
val expectedResult = listOf(
|
||||
MockTokenLists.loadingUngroupedTokenList.copy(sortedBy = TokenList.SortType.BALANCE).right(),
|
||||
MockTokenLists.sortedUngroupedTokenList.right(),
|
||||
)
|
||||
|
||||
val useCase = getUseCase(
|
||||
statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
|
||||
|
|
@ -198,7 +242,9 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -206,7 +252,10 @@ internal class GetTokenListUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `when list is sorted and grouped then correct token list should be received`() = runTest {
|
||||
val expectedResult = MockTokenLists.sortedGroupedTokenList.right()
|
||||
val expectedResult = listOf(
|
||||
MockTokenLists.loadingGroupedTokenList.copy(sortedBy = TokenList.SortType.BALANCE).right(),
|
||||
MockTokenLists.sortedGroupedTokenList.right(),
|
||||
)
|
||||
|
||||
val useCase = getUseCase(
|
||||
statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
|
||||
|
|
@ -215,7 +264,9 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -236,7 +287,10 @@ internal class GetTokenListUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `when networks is empty and list is grouped then ungrouped list should be received`() = runTest {
|
||||
val expectedResult = TokenListError.UnableToSortTokenList(MockTokenLists.failedUngroupedTokenList).left()
|
||||
val expectedResult = listOf(
|
||||
TokenListError.UnableToSortTokenList(MockTokenLists.loadingUngroupedTokenList).left(),
|
||||
TokenListError.UnableToSortTokenList(MockTokenLists.failedUngroupedTokenList).left(),
|
||||
)
|
||||
|
||||
val useCase = getUseCase(
|
||||
networks = emptySet<Network>().right(),
|
||||
|
|
@ -244,7 +298,9 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -265,12 +321,17 @@ internal class GetTokenListUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `when networks statuses flow is empty then error should be received`() = runTest {
|
||||
val expectedResult = TokenListError.EmptyTokens.left()
|
||||
val expectedResult = listOf(
|
||||
MockTokenLists.loadingUngroupedTokenList.right(),
|
||||
TokenListError.EmptyTokens.left(),
|
||||
)
|
||||
|
||||
val useCase = getUseCase(statuses = flowOf())
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -290,13 +351,21 @@ internal class GetTokenListUseCaseTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `when quotes flow is empty then error should be received`() = runTest {
|
||||
val expectedResult = TokenListError.EmptyTokens.left()
|
||||
fun `when quotes flow is empty then list without quotes should be received`() = runTest {
|
||||
val expectedResult = listOf(
|
||||
MockTokenLists.loadingUngroupedTokenList.right(),
|
||||
MockTokenLists.noQuotesUngroupedTokenList.right(),
|
||||
)
|
||||
|
||||
val useCase = getUseCase(quotes = flowOf())
|
||||
val useCase = getUseCase(
|
||||
statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
|
||||
quotes = flowOf(),
|
||||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
|
|||
|
|
@ -41,9 +41,14 @@ internal object MockTokenLists {
|
|||
sortedBy = TokenList.SortType.NONE,
|
||||
)
|
||||
|
||||
val noQuotesUngroupedTokenList = failedUngroupedTokenList.copy(
|
||||
totalFiatBalance = TokenList.FiatBalance.Loaded(amount = BigDecimal.ZERO, isAllAmountsSummarized = false),
|
||||
currencies = MockTokensStates.noQuotesTokensStatuses,
|
||||
)
|
||||
|
||||
val loadingUngroupedTokenList = with(failedUngroupedTokenList) {
|
||||
copy(
|
||||
currencies = currencies.map { it.copy(value = CryptoCurrencyStatus.Loading) }.toNonEmptyListOrNull()!!,
|
||||
currencies = currencies.map { it.copy(value = CryptoCurrencyStatus.Loading) },
|
||||
totalFiatBalance = TokenList.FiatBalance.Loading,
|
||||
)
|
||||
}
|
||||
|
|
@ -54,8 +59,7 @@ internal object MockTokenLists {
|
|||
groups = groups.map { group ->
|
||||
group.copy(
|
||||
currencies = group.currencies
|
||||
.map { it.copy(value = CryptoCurrencyStatus.Loading) }
|
||||
.toNonEmptyListOrNull()!!,
|
||||
.map { it.copy(value = CryptoCurrencyStatus.Loading) },
|
||||
)
|
||||
}.toNonEmptyListOrNull()!!,
|
||||
)
|
||||
|
|
@ -95,7 +99,6 @@ internal object MockTokenLists {
|
|||
get() {
|
||||
val tokens = MockTokensStates.loadedTokensStates
|
||||
.sortedByDescending { it.value.fiatAmount }
|
||||
.toNonEmptyListOrNull()!!
|
||||
|
||||
return unsortedUngroupedTokenList.copy(
|
||||
currencies = tokens,
|
||||
|
|
|
|||
|
|
@ -87,4 +87,13 @@ internal object MockTokensStates {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
val noQuotesTokensStatuses = loadedTokensStates.map { currency ->
|
||||
currency.copy(
|
||||
value = CryptoCurrencyStatus.NoQuote(
|
||||
amount = currency.value.amount!!,
|
||||
hasTransactionsInProgress = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -43,23 +43,14 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
|
|||
|
||||
private fun getMarketPriceState(status: CryptoCurrencyStatus.Status, currencyName: String): MarketPriceBlockState {
|
||||
return when (status) {
|
||||
is CryptoCurrencyStatus.Loaded -> MarketPriceBlockState.Content(
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
-> MarketPriceBlockState.Content(
|
||||
currencyName = currencyName,
|
||||
price = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = status.fiatRate,
|
||||
fiatCurrencyCode = fiatCurrencyCode,
|
||||
fiatCurrencySymbol = fiatCurrencySymbol,
|
||||
),
|
||||
price = formatPrice(status),
|
||||
priceChangeConfig = PriceChangeConfig(
|
||||
valueInPercent = BigDecimalFormatter.formatPercent(
|
||||
percent = status.priceChange,
|
||||
useAbsoluteValue = true,
|
||||
),
|
||||
type = if (status.priceChange > BigDecimal.ZERO) {
|
||||
PriceChangeConfig.Type.UP
|
||||
} else {
|
||||
PriceChangeConfig.Type.DOWN
|
||||
},
|
||||
valueInPercent = formatPriceChange(status),
|
||||
type = getPriceChangeType(status),
|
||||
),
|
||||
)
|
||||
is CryptoCurrencyStatus.Loading -> MarketPriceBlockState.Loading(currencyName)
|
||||
|
|
@ -77,7 +68,9 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
|
|||
): WalletsListConfig {
|
||||
val selectedWallet = state.walletsListConfig.wallets[state.walletsListConfig.selectedWalletIndex]
|
||||
val updatedWallet = when (status) {
|
||||
is CryptoCurrencyStatus.Loaded -> {
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
-> {
|
||||
WalletCardState.Content(
|
||||
id = selectedWallet.id,
|
||||
title = selectedWallet.title,
|
||||
|
|
@ -88,11 +81,7 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
|
|||
),
|
||||
imageResId = selectedWallet.imageResId,
|
||||
onClick = selectedWallet.onClick,
|
||||
balance = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = status.fiatAmount,
|
||||
fiatCurrencyCode = fiatCurrencyCode,
|
||||
fiatCurrencySymbol = fiatCurrencySymbol,
|
||||
),
|
||||
balance = formatFiatAmount(status),
|
||||
)
|
||||
}
|
||||
is CryptoCurrencyStatus.Loading -> {
|
||||
|
|
@ -124,4 +113,43 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
|
|||
.set(index = state.walletsListConfig.selectedWalletIndex, element = updatedWallet),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getPriceChangeType(status: CryptoCurrencyStatus.Status): PriceChangeConfig.Type {
|
||||
val priceChange = status.priceChange ?: return PriceChangeConfig.Type.DOWN
|
||||
|
||||
return if (priceChange > BigDecimal.ZERO) {
|
||||
PriceChangeConfig.Type.UP
|
||||
} else {
|
||||
PriceChangeConfig.Type.DOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatPriceChange(status: CryptoCurrencyStatus.Status): String {
|
||||
val priceChange = status.priceChange ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatPercent(
|
||||
percent = priceChange,
|
||||
useAbsoluteValue = true,
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatPrice(status: CryptoCurrencyStatus.Status): String {
|
||||
val fiatRate = status.fiatRate ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = fiatRate,
|
||||
fiatCurrencyCode = fiatCurrencyCode,
|
||||
fiatCurrencySymbol = fiatCurrencySymbol,
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatFiatAmount(status: CryptoCurrencyStatus.Status): String {
|
||||
val fiatAmount = status.fiatAmount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = fiatAmount,
|
||||
fiatCurrencyCode = fiatCurrencyCode,
|
||||
fiatCurrencySymbol = fiatCurrencySymbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ internal class CryptoCurrencyStatusToTokenItemConverter(
|
|||
is CryptoCurrencyStatus.Loading -> TokenItemState.Loading
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
-> value.mapToTokenItemState()
|
||||
// TODO: Add other token item states, currently not designed
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue