Updated on 2026-08-14
This commit is contained in:
parent
d1a09370c7
commit
3b33d4db0a
24 changed files with 363 additions and 484 deletions
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.domain.configurable.warningMessage
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.utils.extensions.removeByReplace
|
||||
import com.tangem.utils.extensions.removeBy
|
||||
import com.tangem.wallet.R
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
|
|
@ -44,12 +44,12 @@ class WarningMessagesManager {
|
|||
}
|
||||
|
||||
fun removeWarnings(origin: WarningMessage.Origin) {
|
||||
warningsList.removeByReplace { it.origin == origin }
|
||||
warningsList.removeBy { it.origin == origin }
|
||||
sortByPriority()
|
||||
}
|
||||
|
||||
fun removeWarnings(messageRes: Int) {
|
||||
warningsList.removeByReplace { it.messageResId == messageRes }
|
||||
warningsList.removeBy { it.messageResId == messageRes }
|
||||
}
|
||||
|
||||
fun containsWarning(warning: WarningMessage) = warning in warningsList
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository
|
||||
import com.tangem.tap.domain.userWalletList.utils.publicInformation
|
||||
import com.tangem.utils.extensions.plusOrReplace
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ internal class DefaultUserWalletsPublicInformationRepository(
|
|||
getAll()
|
||||
.flatMap { savedInformation ->
|
||||
val infoToSave = withContext(Dispatchers.Default) {
|
||||
savedInformation.plusOrReplace(userWallet.publicInformation) {
|
||||
savedInformation.addOrReplace(userWallet.publicInformation) {
|
||||
userWallet.walletId == it.walletId
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import com.tangem.tap.features.wallet.models.getPendingTransactions
|
|||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.plusOrReplace
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import timber.log.Timber
|
||||
|
|
@ -429,7 +429,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
withContext(Dispatchers.Default) {
|
||||
WalletManagerStorage.update { prevManagers ->
|
||||
val newManagersForUserWallet = prevManagers[userWalletId].orEmpty()
|
||||
.plusOrReplace(walletManager) {
|
||||
.addOrReplace(walletManager) {
|
||||
it.wallet.blockchain == walletManager.wallet.blockchain
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.tangem.blockchain.common.WalletManager
|
|||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.extensions.plusOrReplace
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
|
||||
internal class DefaultWalletManagersStore(
|
||||
dataStore: StringKeyDataStore<List<WalletManager>>,
|
||||
|
|
@ -32,7 +32,7 @@ internal class DefaultWalletManagersStore(
|
|||
val walletManagers = getSyncOrNull(userWalletId)
|
||||
|
||||
val updatedWalletManagers = walletManagers
|
||||
?.plusOrReplace(walletManager) {
|
||||
?.addOrReplace(walletManager) {
|
||||
it.wallet.blockchain == walletManager.wallet.blockchain &&
|
||||
it.wallet.publicKey == walletManager.wallet.publicKey
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,95 +18,4 @@ fun <T> Collection<T>.isSingleItem(): Boolean = this.size == 1
|
|||
*/
|
||||
fun <T> Collection<T>.copy(): Collection<T> {
|
||||
return this.map { it }
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified element to the collection or replaces an existing element.
|
||||
* The predicate defines the condition to replace the existing element.
|
||||
*
|
||||
* @param item The element to be added or replace the existing one.
|
||||
* @param predicate The condition to replace an existing element.
|
||||
* @return The modified [List] after adding or replacing the element.
|
||||
*/
|
||||
inline fun <T> Collection<T>.plusOrReplace(item: T, predicate: (T) -> Boolean): List<T> {
|
||||
val mutableList = this as? MutableList ?: ArrayList(this)
|
||||
|
||||
mutableList.addOrReplace(item, predicate)
|
||||
|
||||
return mutableList
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified element to the collection or replaces an existing element.
|
||||
* The predicate defines the condition to replace the existing element.
|
||||
*
|
||||
* @param item The element to be added or replace the existing one.
|
||||
* @param predicate The condition to replace an existing element.
|
||||
*/
|
||||
inline fun <T> MutableCollection<T>.addOrReplace(item: T, predicate: (T) -> Boolean) {
|
||||
val isReplaced = replaceBy(item, predicate)
|
||||
|
||||
if (!isReplaced) {
|
||||
add(item)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an element from the collection based on the provided predicate.
|
||||
* Uses iterator, avoid using it in COW collections
|
||||
*
|
||||
* @param predicate The condition to remove an element.
|
||||
* @return [Boolean] indicating whether an element was removed.
|
||||
*/
|
||||
inline fun <T> MutableCollection<T>.removeByIterate(predicate: (T) -> Boolean): Boolean {
|
||||
var removed = false
|
||||
val iterator = this.iterator()
|
||||
|
||||
for (e in iterator) {
|
||||
if (predicate(e)) {
|
||||
iterator.remove()
|
||||
removed = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return removed
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an element from the collection based on the provided predicate.
|
||||
* Uses removeAll() method and could be used for COW collections
|
||||
*
|
||||
* @param predicate The condition to remove an element.
|
||||
* @return [Boolean] indicating whether an element was removed.
|
||||
*/
|
||||
fun <T> MutableList<T>.removeByReplace(predicate: (T) -> Boolean): Boolean {
|
||||
val toRemove = this.filter(predicate)
|
||||
this.removeAll(toRemove)
|
||||
return toRemove.isNotEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an element in the collection with the provided item based on the predicate.
|
||||
*
|
||||
* @param item The element to replace the existing one.
|
||||
* @param predicate The condition to replace an existing element.
|
||||
* @return [Boolean] indicating whether an element was replaced.
|
||||
*/
|
||||
inline fun <T> MutableCollection<T>.replaceBy(item: T, predicate: (T) -> Boolean): Boolean {
|
||||
var replaced = false
|
||||
val mutableList = this as? MutableList ?: ArrayList(this)
|
||||
val iterator = mutableList.listIterator()
|
||||
|
||||
for (e in iterator) {
|
||||
if (predicate(e)) {
|
||||
iterator.set(item)
|
||||
replaced = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return replaced
|
||||
}
|
||||
51
core/utils/src/main/java/com/tangem/utils/extensions/List.kt
Normal file
51
core/utils/src/main/java/com/tangem/utils/extensions/List.kt
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.utils.extensions
|
||||
|
||||
/**
|
||||
* Removes an element from the collection based on the provided predicate.
|
||||
*
|
||||
* @param predicate The condition to remove an element.
|
||||
* @return [Boolean] indicating whether an element was removed.
|
||||
*/
|
||||
fun <T> MutableList<T>.removeBy(predicate: (T) -> Boolean): Boolean {
|
||||
val toRemove = this.filter(predicate)
|
||||
this.removeAll(toRemove)
|
||||
return toRemove.isNotEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an element in the list with the provided item based on the predicate.
|
||||
*
|
||||
* @param item The element to replace the existing one.
|
||||
* @param predicate The condition to replace an existing element.
|
||||
* @return [Boolean] indicating whether an element was replaced.
|
||||
*/
|
||||
inline fun <T> MutableList<T>.replaceBy(item: T, predicate: (T) -> Boolean): Boolean {
|
||||
val index = indexOfFirst(predicate)
|
||||
|
||||
if (index == -1) {
|
||||
return false
|
||||
}
|
||||
|
||||
this[index] = item
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified element to the list or replaces an existing element.
|
||||
* The predicate defines the condition to replace the existing element.
|
||||
*
|
||||
* @param item The element to be added or replace the existing one.
|
||||
* @param predicate The condition to replace an existing element.
|
||||
* @return The modified [List] after adding or replacing the element.
|
||||
*/
|
||||
inline fun <T> List<T>.addOrReplace(item: T, predicate: (T) -> Boolean): List<T> {
|
||||
val mutableList = this.toMutableList()
|
||||
val isReplaced = mutableList.replaceBy(item, predicate)
|
||||
|
||||
if (!isReplaced) {
|
||||
mutableList.add(item)
|
||||
}
|
||||
|
||||
return mutableList
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ internal class DefaultNetworksRepository(
|
|||
private val responseCurrenciesFactory by lazy { ResponseCurrenciesFactory(DemoConfig()) }
|
||||
private val networkStatusFactory by lazy { NetworkStatusFactory() }
|
||||
|
||||
private val networksStatuses: MutableStateFlow<HashSet<NetworkStatus>> = MutableStateFlow(hashSetOf())
|
||||
private val networksStatuses: MutableStateFlow<List<NetworkStatus>> = MutableStateFlow(emptyList())
|
||||
|
||||
override fun getNetworks(networksIds: Set<Network.ID>): Set<Network> {
|
||||
return networkConverter.convertSet(networksIds)
|
||||
|
|
@ -48,7 +48,9 @@ internal class DefaultNetworksRepository(
|
|||
refresh: Boolean,
|
||||
): Flow<Set<NetworkStatus>> = channelFlow {
|
||||
launch(dispatchers.io) {
|
||||
networksStatuses.collect(::send)
|
||||
networksStatuses.collect {
|
||||
send(it.toSet())
|
||||
}
|
||||
}
|
||||
|
||||
launch(dispatchers.io) {
|
||||
|
|
@ -82,17 +84,22 @@ internal class DefaultNetworksRepository(
|
|||
|
||||
private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, networkId: Network.ID) {
|
||||
val currencies = getCurrencies(userWalletId)
|
||||
.asSequence()
|
||||
.filter { it.networkId == networkId }
|
||||
|
||||
val result = walletManagersFacade.update(
|
||||
userWalletId = userWalletId,
|
||||
networkId = networkId,
|
||||
extraTokens = currencies.filterIsInstanceTo(hashSetOf()),
|
||||
)
|
||||
val networkStatus = networkStatusFactory.createNetworkStatus(networkId, result, currencies)
|
||||
val networkStatus = networkStatusFactory.createNetworkStatus(
|
||||
networkId = networkId,
|
||||
result = result,
|
||||
currencies = currencies.toSet(),
|
||||
)
|
||||
|
||||
networksStatuses.update { statuses ->
|
||||
statuses.apply {
|
||||
addOrReplace(networkStatus) { it.networkId == networkStatus.networkId }
|
||||
}
|
||||
statuses.addOrReplace(networkStatus) { it.networkId == networkStatus.networkId }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,21 +3,27 @@ package com.tangem.data.tokens.repository
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import java.math.BigDecimal
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class MockQuotesRepository : QuotesRepository {
|
||||
|
||||
override fun getQuotes(currenciesIds: Set<CryptoCurrency.ID>, refresh: Boolean): Flow<Set<Quote>> {
|
||||
return flowOf(
|
||||
currenciesIds.map {
|
||||
return channelFlow {
|
||||
val quotes = currenciesIds.map {
|
||||
Quote(
|
||||
currencyId = it,
|
||||
fiatRate = BigDecimal.ZERO,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
)
|
||||
}.toSet(),
|
||||
)
|
||||
}.toSet()
|
||||
|
||||
delay(Random.nextLong(from = 200, until = 2_000))
|
||||
|
||||
send(quotes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import com.tangem.domain.tokens.model.NetworkStatus
|
|||
import com.tangem.domain.tokens.models.Network
|
||||
import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount
|
||||
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class NetworkStatusFactory {
|
||||
|
|
@ -33,25 +32,20 @@ internal class NetworkStatusFactory {
|
|||
amounts: Set<CryptoCurrencyAmount>,
|
||||
currencies: Set<CryptoCurrency>,
|
||||
): Map<CryptoCurrency.ID, BigDecimal> {
|
||||
val formattedAmounts = hashMapOf<CryptoCurrency.ID, BigDecimal>()
|
||||
|
||||
currencies.forEach { currency ->
|
||||
val amount = when (currency) {
|
||||
is CryptoCurrency.Coin -> amounts.singleOrNull { it is CryptoCurrencyAmount.Coin }
|
||||
is CryptoCurrency.Token -> amounts.singleOrNull {
|
||||
it is CryptoCurrencyAmount.Token &&
|
||||
it.id == getTokenIdString(currency.id) &&
|
||||
it.tokenContractAddress == currency.contractAddress
|
||||
return amounts
|
||||
.asSequence()
|
||||
.mapNotNull { amount ->
|
||||
val currency = when (amount) {
|
||||
is CryptoCurrencyAmount.Coin -> currencies.singleOrNull { it is CryptoCurrency.Coin }
|
||||
is CryptoCurrencyAmount.Token -> currencies.firstOrNull {
|
||||
it is CryptoCurrency.Token &&
|
||||
getTokenIdString(it.id) == amount.id &&
|
||||
it.contractAddress == amount.tokenContractAddress
|
||||
}
|
||||
}
|
||||
}?.value
|
||||
|
||||
if (amount == null) {
|
||||
Timber.e("Unable to find a token amount for: ${currency.name}")
|
||||
} else {
|
||||
formattedAmounts[currency.id] = amount
|
||||
currency?.id?.let { it to amount.value }
|
||||
}
|
||||
}
|
||||
|
||||
return formattedAmounts
|
||||
.toMap()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package com.tangem.domain.core.raise
|
||||
|
||||
import arrow.core.raise.Raise
|
||||
|
||||
abstract class DelegatedRaise<Error, OtherError>(
|
||||
private val otherRaise: Raise<OtherError>,
|
||||
private val transformError: (Error) -> OtherError,
|
||||
) : Raise<Error> {
|
||||
|
||||
override fun raise(r: Error): Nothing {
|
||||
otherRaise.raise(transformError(r))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,8 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.recover
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.tokens.error.CurrencyError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToTokenError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
|
|
@ -15,9 +11,7 @@ import com.tangem.domain.tokens.repository.NetworksRepository
|
|||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Use case for fetching the status of a specific cryptocurrency associated with a user wallet.
|
||||
|
|
@ -47,36 +41,26 @@ class GetCurrencyUseCase(
|
|||
currencyId: CryptoCurrency.ID,
|
||||
refresh: Boolean = false,
|
||||
): Flow<Either<CurrencyError, CryptoCurrencyStatus>> {
|
||||
return channelFlow {
|
||||
recover(
|
||||
block = {
|
||||
getCurrency(userWalletId, currencyId, refresh).collectLatest { currencyStatus ->
|
||||
send(currencyStatus.right())
|
||||
}
|
||||
},
|
||||
recover = { error ->
|
||||
send(error.left())
|
||||
},
|
||||
)
|
||||
}
|
||||
return flow {
|
||||
emitAll(getCurrency(userWalletId, currencyId, refresh))
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
private suspend fun Raise<CurrencyError>.getCurrency(
|
||||
private suspend fun getCurrency(
|
||||
userWalletId: UserWalletId,
|
||||
currencyId: CryptoCurrency.ID,
|
||||
refresh: Boolean,
|
||||
): Flow<CryptoCurrencyStatus> {
|
||||
): Flow<Either<CurrencyError, CryptoCurrencyStatus>> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
userWalletId = userWalletId,
|
||||
refresh = refresh,
|
||||
dispatchers = dispatchers,
|
||||
raise = this,
|
||||
transformError = CurrenciesStatusesOperations.Error::mapToTokenError,
|
||||
)
|
||||
|
||||
return operations.getCurrencyStatusFlow(currencyId)
|
||||
return operations.getCurrencyStatusFlow(currencyId).map { maybeCurrency ->
|
||||
maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,8 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.recover
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.tokens.error.CurrencyError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToTokenError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -14,9 +10,7 @@ import com.tangem.domain.tokens.repository.NetworksRepository
|
|||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Use case for fetching the status of the primary cryptocurrency associated with a user wallet.
|
||||
|
|
@ -44,35 +38,25 @@ class GetPrimaryCurrencyUseCase(
|
|||
userWalletId: UserWalletId,
|
||||
refresh: Boolean = false,
|
||||
): Flow<Either<CurrencyError, CryptoCurrencyStatus>> {
|
||||
return channelFlow {
|
||||
recover(
|
||||
block = {
|
||||
getCurrency(userWalletId, refresh).collectLatest { currencyStatus ->
|
||||
send(currencyStatus.right())
|
||||
}
|
||||
},
|
||||
recover = { error ->
|
||||
send(error.left())
|
||||
},
|
||||
)
|
||||
}
|
||||
return flow {
|
||||
emitAll(getPrimaryCurrency(userWalletId, refresh))
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
private suspend fun Raise<CurrencyError>.getCurrency(
|
||||
private suspend fun getPrimaryCurrency(
|
||||
userWalletId: UserWalletId,
|
||||
refresh: Boolean,
|
||||
): Flow<CryptoCurrencyStatus> {
|
||||
): Flow<Either<CurrencyError, CryptoCurrencyStatus>> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
userWalletId = userWalletId,
|
||||
refresh = refresh,
|
||||
dispatchers = dispatchers,
|
||||
raise = this,
|
||||
transformError = CurrenciesStatusesOperations.Error::mapToTokenError,
|
||||
)
|
||||
|
||||
return operations.getPrimaryCurrencyStatusFlow()
|
||||
return operations.getPrimaryCurrencyStatusFlow().map { maybeCurrency ->
|
||||
maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,6 @@ package com.tangem.domain.tokens
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.recover
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -16,10 +13,11 @@ import com.tangem.domain.tokens.repository.NetworksRepository
|
|||
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.channelFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.flatMapConcat
|
||||
import kotlinx.coroutines.flow.flatMapMerge
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetTokenListUseCase(
|
||||
internal val currenciesRepository: CurrenciesRepository,
|
||||
|
|
@ -28,53 +26,48 @@ class GetTokenListUseCase(
|
|||
internal val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = true): Flow<Either<TokenListError, TokenList>> {
|
||||
return channelFlow {
|
||||
recover(
|
||||
block = {
|
||||
getTokenList(userWalletId, refresh).collectLatest { list ->
|
||||
send(list.right())
|
||||
}
|
||||
return getTokensStatuses(userWalletId, refresh).flatMapMerge flatMap@{ maybeTokens ->
|
||||
maybeTokens.fold(
|
||||
ifLeft = { error ->
|
||||
flowOf(error.left())
|
||||
},
|
||||
recover = { error ->
|
||||
send(error.left())
|
||||
ifRight = { tokens ->
|
||||
createTokenList(userWalletId, tokens)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
private fun Raise<TokenListError>.getTokenList(userWalletId: UserWalletId, refresh: Boolean): Flow<TokenList> {
|
||||
return getTokensStatuses(userWalletId, refresh).flatMapConcat { tokens ->
|
||||
createTokenList(userWalletId, tokens)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Raise<TokenListError>.getTokensStatuses(
|
||||
private fun getTokensStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
refresh: Boolean,
|
||||
): Flow<Set<CryptoCurrencyStatus>> {
|
||||
): Flow<Either<TokenListError, Set<CryptoCurrencyStatus>>> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
userWalletId = userWalletId,
|
||||
refresh = refresh,
|
||||
useCase = this@GetTokenListUseCase,
|
||||
raise = this,
|
||||
transformError = CurrenciesStatusesOperations.Error::mapToTokenListError,
|
||||
)
|
||||
|
||||
return operations.getCurrenciesStatusesFlow()
|
||||
.map { maybeCurrenciesStatuses ->
|
||||
maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Raise<TokenListError>.createTokenList(
|
||||
private fun createTokenList(
|
||||
userWalletId: UserWalletId,
|
||||
tokens: Set<CryptoCurrencyStatus>,
|
||||
): Flow<TokenList> {
|
||||
): Flow<Either<TokenListError, TokenList>> {
|
||||
val operations = TokenListOperations(
|
||||
userWalletId = userWalletId,
|
||||
tokens = tokens,
|
||||
useCase = this@GetTokenListUseCase,
|
||||
raise = this,
|
||||
transform = TokenListOperations.Error::mapToTokenListError,
|
||||
)
|
||||
|
||||
return operations.getTokenListFlow()
|
||||
return operations.getTokenListFlow().map { maybeTokenList ->
|
||||
maybeTokenList.mapLeft(TokenListOperations.Error::mapToTokenListError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import arrow.core.raise.*
|
||||
import com.tangem.domain.tokens.error.TokenListSortingError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToTokenListSortingError
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
|
|
@ -35,47 +32,40 @@ class ToggleTokenListGroupingUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun Raise<TokenListSortingError>.groupTokens(
|
||||
tokenList: TokenList.Ungrouped,
|
||||
): TokenList.GroupedByNetwork {
|
||||
val sortingOperations = getSortingOperations(tokenList)
|
||||
val tokens = sortingOperations.getTokens()
|
||||
private fun Raise<TokenListSortingError>.groupTokens(tokenList: TokenList.Ungrouped): TokenList.GroupedByNetwork {
|
||||
val sortingOperations = TokenListSortingOperations(tokenList)
|
||||
val tokens = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) {
|
||||
sortingOperations.getTokens().bind()
|
||||
}
|
||||
|
||||
val networks = getNetworks(tokens.map { it.currency.networkId }.toSet())
|
||||
return TokenList.GroupedByNetwork(
|
||||
groups = sortingOperations.getGroupedTokens(networks),
|
||||
groups = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) {
|
||||
sortingOperations.getGroupedTokens(networks).bind()
|
||||
},
|
||||
totalFiatBalance = tokenList.totalFiatBalance,
|
||||
sortedBy = sortingOperations.getSortType(),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun Raise<TokenListSortingError>.ungroupTokens(
|
||||
private fun Raise<TokenListSortingError>.ungroupTokens(
|
||||
tokenList: TokenList.GroupedByNetwork,
|
||||
): TokenList.Ungrouped {
|
||||
val sortingOperations = getSortingOperations(tokenList)
|
||||
val sortingOperations = TokenListSortingOperations(tokenList)
|
||||
|
||||
return TokenList.Ungrouped(
|
||||
currencies = sortingOperations.getTokens(),
|
||||
currencies = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) {
|
||||
sortingOperations.getTokens().bind()
|
||||
},
|
||||
totalFiatBalance = tokenList.totalFiatBalance,
|
||||
sortedBy = sortingOperations.getSortType(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Raise<TokenListSortingError>.getSortingOperations(tokenList: TokenList): TokenListSortingOperations<*> {
|
||||
return TokenListSortingOperations(
|
||||
tokenList = tokenList,
|
||||
dispatchers = dispatchers,
|
||||
raise = this,
|
||||
transformError = TokenListSortingOperations.Error::mapToTokenListSortingError,
|
||||
private fun Raise<TokenListSortingError>.getNetworks(networksIds: Set<Network.ID>): Set<Network> {
|
||||
return catch(
|
||||
block = { networksRepository.getNetworks(networksIds) },
|
||||
catch = { raise(TokenListSortingError.DataError(it)) },
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun Raise<TokenListSortingError>.getNetworks(networksIds: Set<Network.ID>): Set<Network> {
|
||||
return withContext(dispatchers.io) {
|
||||
catch(
|
||||
block = { networksRepository.getNetworks(networksIds) },
|
||||
catch = { raise(TokenListSortingError.DataError(it)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import arrow.core.Either
|
|||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import arrow.core.raise.withError
|
||||
import com.tangem.domain.tokens.error.TokenListSortingError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToTokenListSortingError
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
|
|
@ -31,36 +32,37 @@ class ToggleTokenListSortingUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun Raise<TokenListSortingError>.sortGroupedTokenList(
|
||||
private fun Raise<TokenListSortingError>.sortGroupedTokenList(
|
||||
tokenList: TokenList.GroupedByNetwork,
|
||||
): TokenList.GroupedByNetwork {
|
||||
val operations = getSortingOperations(tokenList)
|
||||
val networks = tokenList.groups.map { it.network }.toSet()
|
||||
|
||||
return tokenList.copy(
|
||||
groups = operations.getGroupedTokens(networks),
|
||||
groups = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) {
|
||||
operations.getGroupedTokens(networks).bind()
|
||||
},
|
||||
sortedBy = operations.getSortType(),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun Raise<TokenListSortingError>.sortUngroupedTokenList(
|
||||
private fun Raise<TokenListSortingError>.sortUngroupedTokenList(
|
||||
tokenList: TokenList.Ungrouped,
|
||||
): TokenList.Ungrouped {
|
||||
val operations = getSortingOperations(tokenList)
|
||||
|
||||
return tokenList.copy(
|
||||
currencies = operations.getTokens(),
|
||||
currencies = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) {
|
||||
operations.getTokens().bind()
|
||||
},
|
||||
sortedBy = operations.getSortType(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Raise<TokenListSortingError>.getSortingOperations(tokenList: TokenList): TokenListSortingOperations<*> {
|
||||
private fun getSortingOperations(tokenList: TokenList): TokenListSortingOperations {
|
||||
return TokenListSortingOperations(
|
||||
tokenList = tokenList,
|
||||
sortByBalance = tokenList.sortedBy != TokenList.SortType.BALANCE,
|
||||
dispatchers = dispatchers,
|
||||
raise = this,
|
||||
transformError = TokenListSortingOperations.Error::mapToTokenListSortingError,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.tangem.domain.tokens.error.mapper
|
|||
import com.tangem.domain.tokens.error.CurrencyError
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
|
||||
internal fun CurrenciesStatusesOperations.Error.mapToTokenError(): CurrencyError {
|
||||
internal fun CurrenciesStatusesOperations.Error.mapToCurrencyError(): CurrencyError {
|
||||
return when (this) {
|
||||
is CurrenciesStatusesOperations.Error.DataError -> CurrencyError.DataError(this.cause)
|
||||
is CurrenciesStatusesOperations.Error.EmptyNetworksStatuses,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package com.tangem.domain.tokens.operations
|
||||
|
||||
import arrow.core.*
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.catch
|
||||
import com.tangem.domain.core.raise.DelegatedRaise
|
||||
import arrow.core.raise.*
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
|
|
@ -11,97 +9,98 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
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.*
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class CurrenciesStatusesOperations<E>(
|
||||
internal class CurrenciesStatusesOperations(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val userWalletId: UserWalletId,
|
||||
private val refresh: Boolean,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
raise: Raise<E>,
|
||||
transformError: (Error) -> E,
|
||||
) : DelegatedRaise<CurrenciesStatusesOperations.Error, E>(raise, transformError) {
|
||||
) {
|
||||
|
||||
constructor(
|
||||
userWalletId: UserWalletId,
|
||||
refresh: Boolean,
|
||||
useCase: GetTokenListUseCase,
|
||||
raise: Raise<E>,
|
||||
transformError: (Error) -> E,
|
||||
) : this(
|
||||
currenciesRepository = useCase.currenciesRepository,
|
||||
quotesRepository = useCase.quotesRepository,
|
||||
networksRepository = useCase.networksRepository,
|
||||
userWalletId = userWalletId,
|
||||
refresh = refresh,
|
||||
dispatchers = useCase.dispatchers,
|
||||
raise = raise,
|
||||
transformError = transformError,
|
||||
)
|
||||
|
||||
fun getCurrenciesStatusesFlow(): Flow<Set<CryptoCurrencyStatus>> {
|
||||
return getMultiCurrencyWalletCurrencies().flatMapConcat {
|
||||
val currencies = it.toNonEmptySetOrNull()
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun getCurrenciesStatusesFlow(): Flow<Either<Error, Set<CryptoCurrencyStatus>>> {
|
||||
return getMultiCurrencyWalletCurrencies().flatMapMerge flatMap@{ maybeCurrencies ->
|
||||
val nonEmptyCurrencies = maybeCurrencies.fold(
|
||||
ifLeft = { error ->
|
||||
return@flatMap flowOf(error.left())
|
||||
},
|
||||
ifRight = { it.toNonEmptySetOrNull() },
|
||||
) ?: return@flatMap flowOf(emptySet<CryptoCurrencyStatus>().right())
|
||||
|
||||
if (currencies == null) {
|
||||
flowOf(emptySet())
|
||||
} else {
|
||||
val currencyIdToNetworkId = currencies.associate { currency ->
|
||||
currency.id to currency.networkId
|
||||
}
|
||||
val currenciesIds = requireNotNull(currencyIdToNetworkId.keys.toNonEmptySetOrNull()) {
|
||||
"Currencies IDs cannot be empty"
|
||||
}
|
||||
val networksIds = requireNotNull(currencyIdToNetworkId.values.toNonEmptySetOrNull()) {
|
||||
"Networks IDs cannot be empty"
|
||||
}
|
||||
val (networksIds, currenciesIds) = getIds(nonEmptyCurrencies)
|
||||
|
||||
combine(getQuotes(currenciesIds), getNetworksStatues(networksIds)) { quotes, networksStatuses ->
|
||||
createTokensStatuses(currencies, quotes, networksStatuses)
|
||||
combine(
|
||||
getQuotes(currenciesIds),
|
||||
getNetworksStatuses(networksIds),
|
||||
) { maybeQuotes, maybeNetworksStatuses ->
|
||||
either {
|
||||
createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes.bind(), maybeNetworksStatuses.bind())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getCurrencyStatusFlow(currencyId: CryptoCurrency.ID): Flow<CryptoCurrencyStatus> {
|
||||
val currency = getMultiCurrencyWalletCurrency(currencyId)
|
||||
suspend fun getCurrencyStatusFlow(currencyId: CryptoCurrency.ID): Flow<Either<Error, CryptoCurrencyStatus>> {
|
||||
val currency = recover(
|
||||
block = { getMultiCurrencyWalletCurrency(currencyId) },
|
||||
recover = { return flowOf(it.left()) },
|
||||
)
|
||||
|
||||
return getCurrencyStatusFlow(currency)
|
||||
}
|
||||
|
||||
suspend fun getPrimaryCurrencyStatusFlow(): Flow<CryptoCurrencyStatus> {
|
||||
val currency = getPrimaryCurrency()
|
||||
suspend fun getPrimaryCurrencyStatusFlow(): Flow<Either<Error, CryptoCurrencyStatus>> {
|
||||
val currency = recover(
|
||||
block = { getPrimaryCurrency() },
|
||||
recover = { return flowOf(it.left()) },
|
||||
)
|
||||
|
||||
return getCurrencyStatusFlow(currency)
|
||||
}
|
||||
|
||||
private fun getCurrencyStatusFlow(currency: CryptoCurrency): Flow<CryptoCurrencyStatus> {
|
||||
private fun getCurrencyStatusFlow(currency: CryptoCurrency): Flow<Either<Error, CryptoCurrencyStatus>> {
|
||||
val quoteFlow = getQuotes(nonEmptySetOf(currency.id))
|
||||
.map { quotes ->
|
||||
quotes.singleOrNull { it.currencyId == currency.id }
|
||||
.map { maybeQuotes ->
|
||||
maybeQuotes.map { quotes ->
|
||||
quotes.singleOrNull { it.currencyId == currency.id }
|
||||
}
|
||||
}
|
||||
|
||||
val statusFlow = getNetworksStatues(nonEmptySetOf(currency.networkId))
|
||||
.map { statuses ->
|
||||
statuses.singleOrNull { it.networkId == currency.networkId }
|
||||
val statusFlow = getNetworksStatuses(nonEmptySetOf(currency.networkId))
|
||||
.map { maybeStatuses ->
|
||||
maybeStatuses.map { statuses ->
|
||||
statuses.singleOrNull { it.networkId == currency.networkId }
|
||||
}
|
||||
}
|
||||
|
||||
return combine(quoteFlow, statusFlow) { quote, networkStatus ->
|
||||
createStatus(currency, quote, networkStatus)
|
||||
return combine(quoteFlow, statusFlow) { maybeQuote, maybeNetworkStatus ->
|
||||
either {
|
||||
createStatus(currency, maybeQuote.bind(), maybeNetworkStatus.bind())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createTokensStatuses(
|
||||
tokens: Set<CryptoCurrency>,
|
||||
private fun createCurrenciesStatuses(
|
||||
currencies: NonEmptySet<CryptoCurrency>,
|
||||
quotes: Set<Quote>,
|
||||
networkStatuses: Set<NetworkStatus>,
|
||||
): Set<CryptoCurrencyStatus> = withContext(dispatchers.default) {
|
||||
tokens.mapTo(hashSetOf()) { token ->
|
||||
): Set<CryptoCurrencyStatus> {
|
||||
return currencies.mapTo(hashSetOf()) { token ->
|
||||
val quote = quotes.firstOrNull { it.currencyId == token.id }
|
||||
val networkStatus = networkStatuses.firstOrNull { it.networkId == token.networkId }
|
||||
|
||||
|
|
@ -109,7 +108,7 @@ internal class CurrenciesStatusesOperations<E>(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun createStatus(
|
||||
private fun createStatus(
|
||||
token: CryptoCurrency,
|
||||
quote: Quote?,
|
||||
networkStatus: NetworkStatus?,
|
||||
|
|
@ -118,44 +117,58 @@ internal class CurrenciesStatusesOperations<E>(
|
|||
currency = token,
|
||||
quote = quote,
|
||||
networkStatus = networkStatus,
|
||||
dispatchers = dispatchers,
|
||||
raise = this,
|
||||
transformError = { Error.UnableToCreateCurrencyStatus },
|
||||
)
|
||||
|
||||
return currencyStatusOperations.createTokenStatus()
|
||||
}
|
||||
|
||||
private suspend fun getMultiCurrencyWalletCurrency(currencyId: CryptoCurrency.ID): CryptoCurrency {
|
||||
return catch(
|
||||
block = { currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, currencyId) },
|
||||
catch = { raise(Error.DataError(it)) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun getMultiCurrencyWalletCurrencies(): Flow<Set<CryptoCurrency>> {
|
||||
private fun getMultiCurrencyWalletCurrencies(): Flow<Either<Error, Set<CryptoCurrency>>> {
|
||||
return currenciesRepository.getMultiCurrencyWalletCurrencies(userWalletId, refresh)
|
||||
.catch { raise(Error.DataError(it)) }
|
||||
.onEmpty { raise(Error.EmptyCurrencies) }
|
||||
.map<Set<CryptoCurrency>, Either<Error, Set<CryptoCurrency>>> { it.right() }
|
||||
.catch { emit(Error.DataError(it).left()) }
|
||||
.onEmpty { emit(Error.EmptyCurrencies.left()) }
|
||||
}
|
||||
|
||||
private suspend fun getPrimaryCurrency(): CryptoCurrency {
|
||||
private suspend fun Raise<Error>.getMultiCurrencyWalletCurrency(currencyId: CryptoCurrency.ID): CryptoCurrency {
|
||||
return Either.catch { currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, currencyId) }
|
||||
.mapLeft { Error.DataError(it) }
|
||||
.bind()
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.getPrimaryCurrency(): CryptoCurrency {
|
||||
return catch(
|
||||
block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
|
||||
catch = { raise(Error.DataError(it)) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Set<Quote>> {
|
||||
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Either<Error, Set<Quote>>> {
|
||||
return quotesRepository.getQuotes(tokensIds, refresh)
|
||||
.catch { raise(Error.DataError(it)) }
|
||||
.onEmpty { raise(Error.EmptyQuotes) }
|
||||
.map<Set<Quote>, Either<Error, Set<Quote>>> { it.right() }
|
||||
.catch { emit(Error.DataError(it).left()) }
|
||||
.onEmpty { emit(Error.EmptyQuotes.left()) }
|
||||
}
|
||||
|
||||
private fun getNetworksStatues(networks: NonEmptySet<Network.ID>): Flow<Set<NetworkStatus>> {
|
||||
private fun getNetworksStatuses(networks: NonEmptySet<Network.ID>): Flow<Either<Error, Set<NetworkStatus>>> {
|
||||
return networksRepository.getNetworkStatuses(userWalletId, networks, refresh)
|
||||
.catch { raise(Error.DataError(it)) }
|
||||
.onEmpty { raise(Error.EmptyNetworksStatuses) }
|
||||
.map<Set<NetworkStatus>, Either<Error, Set<NetworkStatus>>> { it.right() }
|
||||
.catch { emit(Error.DataError(it).left()) }
|
||||
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
|
||||
}
|
||||
|
||||
private fun getIds(
|
||||
currencies: NonEmptySet<CryptoCurrency>,
|
||||
): Pair<NonEmptySet<Network.ID>, NonEmptySet<CryptoCurrency.ID>> {
|
||||
val currencyIdToNetworkId = currencies.associate { currency ->
|
||||
currency.id to currency.networkId
|
||||
}
|
||||
val currenciesIds = currencyIdToNetworkId.keys.toNonEmptySetOrNull()
|
||||
val networksIds = currencyIdToNetworkId.values.toNonEmptySetOrNull()
|
||||
|
||||
requireNotNull(currenciesIds) { "Currencies IDs cannot be empty" }
|
||||
requireNotNull(networksIds) { "Networks IDs cannot be empty" }
|
||||
|
||||
return networksIds to currenciesIds
|
||||
}
|
||||
|
||||
sealed class Error {
|
||||
|
|
|
|||
|
|
@ -1,28 +1,18 @@
|
|||
package com.tangem.domain.tokens.operations
|
||||
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import com.tangem.domain.core.raise.DelegatedRaise
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class CurrencyStatusOperations<OtherError>(
|
||||
internal class CurrencyStatusOperations(
|
||||
private val currency: CryptoCurrency,
|
||||
private val quote: Quote?,
|
||||
private val networkStatus: NetworkStatus?,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
raise: Raise<OtherError>,
|
||||
transformError: (Error) -> OtherError,
|
||||
) : DelegatedRaise<CurrencyStatusOperations.Error, OtherError>(raise, transformError) {
|
||||
) {
|
||||
|
||||
suspend fun createTokenStatus(): CryptoCurrencyStatus = withContext(dispatchers.default) {
|
||||
CryptoCurrencyStatus(currency, createStatus())
|
||||
}
|
||||
fun createTokenStatus(): CryptoCurrencyStatus = CryptoCurrencyStatus(currency, createStatus())
|
||||
|
||||
private fun createStatus(): CryptoCurrencyStatus.Status {
|
||||
return when (val status = networkStatus?.value) {
|
||||
|
|
@ -35,9 +25,7 @@ internal class CurrencyStatusOperations<OtherError>(
|
|||
}
|
||||
|
||||
private fun createStatus(status: NetworkStatus.Verified): CryptoCurrencyStatus.Status {
|
||||
val amount = ensureNotNull(status.amounts[currency.id]) {
|
||||
Error.UnableToFindAmount(currency.id)
|
||||
}
|
||||
val amount = status.amounts[currency.id] ?: return CryptoCurrencyStatus.Unreachable
|
||||
|
||||
return when {
|
||||
currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom(
|
||||
|
|
@ -67,9 +55,4 @@ internal class CurrencyStatusOperations<OtherError>(
|
|||
private fun calculateFiatAmount(amount: BigDecimal, fiatRate: BigDecimal): BigDecimal {
|
||||
return amount * fiatRate
|
||||
}
|
||||
|
||||
sealed class Error {
|
||||
|
||||
data class UnableToFindAmount(val currencyId: CryptoCurrency.ID) : Error()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,48 +3,44 @@ package com.tangem.domain.tokens.operations
|
|||
import arrow.core.NonEmptySet
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class TokenListFiatBalanceOperations(
|
||||
private val currencies: NonEmptySet<CryptoCurrencyStatus>,
|
||||
private val isAnyTokenLoading: Boolean,
|
||||
private val dispatcher: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
suspend fun calculateFiatBalance(): TokenList.FiatBalance {
|
||||
return withContext(dispatcher.single) {
|
||||
var fiatBalance: TokenList.FiatBalance = TokenList.FiatBalance.Loading
|
||||
if (isAnyTokenLoading) return@withContext fiatBalance
|
||||
fun calculateFiatBalance(): TokenList.FiatBalance {
|
||||
var fiatBalance: TokenList.FiatBalance = TokenList.FiatBalance.Loading
|
||||
if (isAnyTokenLoading) return fiatBalance
|
||||
|
||||
for (token in currencies) {
|
||||
when (val status = token.value) {
|
||||
is CryptoCurrencyStatus.Loading -> {
|
||||
fiatBalance = TokenList.FiatBalance.Loading
|
||||
break
|
||||
}
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
-> {
|
||||
fiatBalance = TokenList.FiatBalance.Failed
|
||||
break
|
||||
}
|
||||
is CryptoCurrencyStatus.NoAccount -> {
|
||||
fiatBalance = recalculateBalanceForNoAccountStatus(fiatBalance)
|
||||
}
|
||||
is CryptoCurrencyStatus.Loaded -> {
|
||||
fiatBalance = recalculateBalance(status, fiatBalance)
|
||||
}
|
||||
is CryptoCurrencyStatus.Custom -> {
|
||||
fiatBalance = recalculateBalance(status, fiatBalance)
|
||||
}
|
||||
for (token in currencies) {
|
||||
when (val status = token.value) {
|
||||
is CryptoCurrencyStatus.Loading -> {
|
||||
fiatBalance = TokenList.FiatBalance.Loading
|
||||
break
|
||||
}
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
-> {
|
||||
fiatBalance = TokenList.FiatBalance.Failed
|
||||
break
|
||||
}
|
||||
is CryptoCurrencyStatus.NoAccount -> {
|
||||
fiatBalance = recalculateBalanceForNoAccountStatus(fiatBalance)
|
||||
}
|
||||
is CryptoCurrencyStatus.Loaded -> {
|
||||
fiatBalance = recalculateBalance(status, fiatBalance)
|
||||
}
|
||||
is CryptoCurrencyStatus.Custom -> {
|
||||
fiatBalance = recalculateBalance(status, fiatBalance)
|
||||
}
|
||||
}
|
||||
|
||||
fiatBalance
|
||||
}
|
||||
|
||||
return fiatBalance
|
||||
}
|
||||
|
||||
private fun recalculateBalanceForNoAccountStatus(currentBalance: TokenList.FiatBalance): TokenList.FiatBalance {
|
||||
return with(currentBalance) {
|
||||
(this as? TokenList.FiatBalance.Loaded)?.copy(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
package com.tangem.domain.tokens.operations
|
||||
|
||||
import arrow.core.NonEmptySet
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import arrow.core.toNonEmptySetOrNull
|
||||
import com.tangem.domain.core.raise.DelegatedRaise
|
||||
import arrow.core.*
|
||||
import arrow.core.raise.*
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
|
|
@ -13,62 +9,55 @@ import com.tangem.domain.tokens.models.Network
|
|||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class TokenListOperations<E>(
|
||||
internal class TokenListOperations(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val userWalletId: UserWalletId,
|
||||
private val tokens: Set<CryptoCurrencyStatus>,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
raise: Raise<E>,
|
||||
transform: (Error) -> E,
|
||||
) : DelegatedRaise<TokenListOperations.Error, E>(raise, transform) {
|
||||
) {
|
||||
|
||||
constructor(
|
||||
userWalletId: UserWalletId,
|
||||
tokens: Set<CryptoCurrencyStatus>,
|
||||
useCase: GetTokenListUseCase,
|
||||
raise: Raise<E>,
|
||||
transform: (Error) -> E,
|
||||
) : this(
|
||||
currenciesRepository = useCase.currenciesRepository,
|
||||
networksRepository = useCase.networksRepository,
|
||||
userWalletId = userWalletId,
|
||||
tokens = tokens,
|
||||
dispatchers = useCase.dispatchers,
|
||||
raise = raise,
|
||||
transform = transform,
|
||||
)
|
||||
|
||||
fun getTokenListFlow(): Flow<TokenList> {
|
||||
return combine(getIsGrouped(), getIsSortedByBalance()) { isGrouped, isSortedByBalance ->
|
||||
createTokenList(isGrouped, isSortedByBalance)
|
||||
fun getTokenListFlow(): Flow<Either<Error, TokenList>> {
|
||||
return combine(
|
||||
getIsGrouped(),
|
||||
getIsSortedByBalance(),
|
||||
) { isGrouped, isSortedByBalance ->
|
||||
either {
|
||||
createTokenList(isGrouped.bind(), isSortedByBalance.bind())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList {
|
||||
return withContext(dispatchers.default) {
|
||||
val tokensNes = tokens.toNonEmptySetOrNull()
|
||||
?: return@withContext TokenList.NotInitialized
|
||||
private fun Raise<Error>.createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList {
|
||||
val tokensNes = tokens.toNonEmptySetOrNull()
|
||||
?: return TokenList.NotInitialized
|
||||
|
||||
val isAnyTokenLoading = tokensNes.any { it.value is CryptoCurrencyStatus.Loading }
|
||||
val fiatBalanceOperations = TokenListFiatBalanceOperations(tokensNes, isAnyTokenLoading, dispatchers)
|
||||
val isAnyTokenLoading = tokensNes.any { it.value is CryptoCurrencyStatus.Loading }
|
||||
val fiatBalanceOperations = TokenListFiatBalanceOperations(tokensNes, isAnyTokenLoading)
|
||||
|
||||
createTokenList(
|
||||
tokens = tokensNes,
|
||||
fiatBalance = fiatBalanceOperations.calculateFiatBalance(),
|
||||
isAnyTokenLoading = isAnyTokenLoading,
|
||||
isGrouped = isGrouped,
|
||||
isSortedByBalance = isSortedByBalance,
|
||||
)
|
||||
}
|
||||
return createTokenList(
|
||||
tokens = tokensNes,
|
||||
fiatBalance = fiatBalanceOperations.calculateFiatBalance(),
|
||||
isAnyTokenLoading = isAnyTokenLoading,
|
||||
isGrouped = isGrouped,
|
||||
isSortedByBalance = isSortedByBalance,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun createTokenList(
|
||||
private fun Raise<Error>.createTokenList(
|
||||
tokens: NonEmptySet<CryptoCurrencyStatus>,
|
||||
fiatBalance: TokenList.FiatBalance,
|
||||
isAnyTokenLoading: Boolean,
|
||||
|
|
@ -79,19 +68,14 @@ internal class TokenListOperations<E>(
|
|||
currencies = tokens,
|
||||
isAnyTokenLoading = isAnyTokenLoading,
|
||||
sortByBalance = isSortedByBalance,
|
||||
dispatchers = dispatchers,
|
||||
raise = this,
|
||||
transformError = { e ->
|
||||
Error.fromTokenListOperations(e) { createUnsortedUngroupedTokenList(tokens, fiatBalance) }
|
||||
},
|
||||
)
|
||||
|
||||
return createTokenList(tokens, sortingOperations, fiatBalance, isGrouped)
|
||||
}
|
||||
|
||||
private suspend fun createTokenList(
|
||||
private fun Raise<Error>.createTokenList(
|
||||
tokens: NonEmptySet<CryptoCurrencyStatus>,
|
||||
sortingOperations: TokenListSortingOperations<*>,
|
||||
sortingOperations: TokenListSortingOperations,
|
||||
fiatBalance: TokenList.FiatBalance,
|
||||
isGrouped: Boolean,
|
||||
): TokenList {
|
||||
|
|
@ -108,37 +92,46 @@ internal class TokenListOperations<E>(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getNetworks(tokensNes: NonEmptySet<CryptoCurrencyStatus>): Set<Network> {
|
||||
return withContext(dispatchers.io) {
|
||||
val networksIds = tokensNes.map { it.currency.networkId }.toNonEmptySet()
|
||||
catch(
|
||||
block = { networksRepository.getNetworks(networksIds) },
|
||||
catch = { raise(Error.DataError(it)) },
|
||||
)
|
||||
}
|
||||
private fun Raise<Error>.getNetworks(tokensNes: NonEmptySet<CryptoCurrencyStatus>): Set<Network> {
|
||||
val networksIds = tokensNes.map { it.currency.networkId }.toNonEmptySet()
|
||||
|
||||
return catch(
|
||||
block = { networksRepository.getNetworks(networksIds) },
|
||||
catch = { raise(Error.DataError(it)) },
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun createUngroupedTokenList(
|
||||
sortingOperations: TokenListSortingOperations<*>,
|
||||
private fun Raise<Error>.createUngroupedTokenList(
|
||||
sortingOperations: TokenListSortingOperations,
|
||||
fiatBalance: TokenList.FiatBalance,
|
||||
): TokenList.Ungrouped = TokenList.Ungrouped(
|
||||
sortedBy = sortingOperations.getSortType(),
|
||||
totalFiatBalance = fiatBalance,
|
||||
currencies = sortingOperations.getTokens(),
|
||||
currencies = withError(
|
||||
transform = { e ->
|
||||
Error.fromTokenListOperations(e) { createUnsortedUngroupedTokenList(tokens, fiatBalance) }
|
||||
},
|
||||
block = { sortingOperations.getTokens().bind() },
|
||||
),
|
||||
)
|
||||
|
||||
private suspend fun createGroupedTokenList(
|
||||
sortingOperations: TokenListSortingOperations<*>,
|
||||
private fun Raise<Error>.createGroupedTokenList(
|
||||
sortingOperations: TokenListSortingOperations,
|
||||
fiatBalance: TokenList.FiatBalance,
|
||||
networks: NonEmptySet<Network>,
|
||||
): TokenList.GroupedByNetwork = TokenList.GroupedByNetwork(
|
||||
sortedBy = sortingOperations.getSortType(),
|
||||
totalFiatBalance = fiatBalance,
|
||||
groups = sortingOperations.getGroupedTokens(networks),
|
||||
groups = withError(
|
||||
transform = { e ->
|
||||
Error.fromTokenListOperations(e) { createUnsortedUngroupedTokenList(tokens, fiatBalance) }
|
||||
},
|
||||
block = { sortingOperations.getGroupedTokens(networks).bind() },
|
||||
),
|
||||
)
|
||||
|
||||
private fun createUnsortedUngroupedTokenList(
|
||||
tokens: NonEmptySet<CryptoCurrencyStatus>,
|
||||
tokens: Set<CryptoCurrencyStatus>,
|
||||
fiatBalance: TokenList.FiatBalance,
|
||||
): TokenList.Ungrouped {
|
||||
return TokenList.Ungrouped(
|
||||
|
|
@ -148,18 +141,18 @@ internal class TokenListOperations<E>(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getIsGrouped(): Flow<Boolean> {
|
||||
private fun getIsGrouped(): Flow<Either<Error, Boolean>> {
|
||||
return currenciesRepository.isTokensGrouped(userWalletId)
|
||||
.catch { raise(Error.DataError(it)) }
|
||||
.onEmpty { emit(value = false) }
|
||||
.flowOn(dispatchers.io)
|
||||
.map<Boolean, Either<Error, Boolean>> { it.right() }
|
||||
.catch { emit(Error.DataError(it).left()) }
|
||||
.onEmpty { emit(value = false.right()) }
|
||||
}
|
||||
|
||||
private fun getIsSortedByBalance(): Flow<Boolean> {
|
||||
private fun getIsSortedByBalance(): Flow<Either<Error, Boolean>> {
|
||||
return currenciesRepository.isTokensSortedByBalance(userWalletId)
|
||||
.catch { raise(Error.DataError(it)) }
|
||||
.onEmpty { emit(value = false) }
|
||||
.flowOn(dispatchers.io)
|
||||
.map<Boolean, Either<Error, Boolean>> { it.right() }
|
||||
.catch { emit(Error.DataError(it).left()) }
|
||||
.onEmpty { emit(value = false.right()) }
|
||||
}
|
||||
|
||||
sealed class Error {
|
||||
|
|
|
|||
|
|
@ -1,33 +1,26 @@
|
|||
package com.tangem.domain.tokens.operations
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.NonEmptySet
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import arrow.core.toNonEmptySetOrNull
|
||||
import com.tangem.domain.core.raise.DelegatedRaise
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.NetworkGroup
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class TokenListSortingOperations<E>(
|
||||
internal class TokenListSortingOperations(
|
||||
private val currencies: Set<CryptoCurrencyStatus>,
|
||||
private val isAnyTokenLoading: Boolean,
|
||||
private val sortByBalance: Boolean,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
raise: Raise<E>,
|
||||
transformError: (Error) -> E,
|
||||
) : DelegatedRaise<TokenListSortingOperations.Error, E>(raise, transformError) {
|
||||
) {
|
||||
|
||||
constructor(
|
||||
tokenList: TokenList,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
raise: Raise<E>,
|
||||
transformError: (Error) -> E,
|
||||
sortByBalance: Boolean = tokenList.sortedBy == TokenList.SortType.BALANCE,
|
||||
isAnyTokenLoading: Boolean = tokenList.totalFiatBalance is TokenList.FiatBalance.Loading,
|
||||
) : this(
|
||||
|
|
@ -38,39 +31,32 @@ internal class TokenListSortingOperations<E>(
|
|||
},
|
||||
isAnyTokenLoading = isAnyTokenLoading,
|
||||
sortByBalance = sortByBalance,
|
||||
dispatchers = dispatchers,
|
||||
raise = raise,
|
||||
transformError = transformError,
|
||||
)
|
||||
|
||||
suspend fun getGroupedTokens(networks: Set<Network>): NonEmptySet<NetworkGroup> {
|
||||
return withContext(dispatchers.default) {
|
||||
ensure(currencies.isNotEmpty()) { Error.EmptyTokens }
|
||||
val networksNes = ensureNotNull(networks.toNonEmptySetOrNull()) {
|
||||
Error.EmptyNetworks
|
||||
}
|
||||
fun getGroupedTokens(networks: Set<Network>): Either<Error, NonEmptySet<NetworkGroup>> = either {
|
||||
ensure(currencies.isNotEmpty()) { Error.EmptyTokens }
|
||||
val networksNes = ensureNotNull(networks.toNonEmptySetOrNull()) {
|
||||
Error.EmptyNetworks
|
||||
}
|
||||
|
||||
if (sortByBalance) {
|
||||
groupAndSortTokensByBalance(networksNes)
|
||||
} else {
|
||||
groupTokens(networksNes)
|
||||
}
|
||||
if (sortByBalance) {
|
||||
groupAndSortTokensByBalance(networksNes)
|
||||
} else {
|
||||
groupTokens(networksNes)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getTokens(): NonEmptySet<CryptoCurrencyStatus> {
|
||||
return withContext(dispatchers.default) {
|
||||
val tokensNes = ensureNotNull(currencies.toNonEmptySetOrNull()) {
|
||||
Error.EmptyTokens
|
||||
}
|
||||
|
||||
if (sortByBalance) sortTokensByBalance(tokensNes) else tokensNes
|
||||
fun getTokens(): Either<Error, NonEmptySet<CryptoCurrencyStatus>> = either {
|
||||
val tokensNes = ensureNotNull(currencies.toNonEmptySetOrNull()) {
|
||||
Error.EmptyTokens
|
||||
}
|
||||
|
||||
if (sortByBalance) sortTokensByBalance(tokensNes) else tokensNes
|
||||
}
|
||||
|
||||
fun getSortType() = if (sortByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE
|
||||
fun getSortType(): TokenList.SortType = if (sortByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE
|
||||
|
||||
private fun groupTokens(networks: NonEmptySet<Network>): NonEmptySet<NetworkGroup> {
|
||||
private fun Raise<Error>.groupTokens(networks: NonEmptySet<Network>): NonEmptySet<NetworkGroup> {
|
||||
val groupedTokens = currencies
|
||||
.groupBy { it.currency.networkId }
|
||||
.map { (networkId, tokens) ->
|
||||
|
|
@ -88,7 +74,7 @@ internal class TokenListSortingOperations<E>(
|
|||
return ensureNotNull(groupedTokens) { Error.EmptyTokens }
|
||||
}
|
||||
|
||||
private fun groupAndSortTokensByBalance(networks: NonEmptySet<Network>): NonEmptySet<NetworkGroup> {
|
||||
private fun Raise<Error>.groupAndSortTokensByBalance(networks: NonEmptySet<Network>): NonEmptySet<NetworkGroup> {
|
||||
val groupsWithSortedTokens = groupTokens(networks)
|
||||
.map { group ->
|
||||
val tokens = group.currencies as? NonEmptySet<CryptoCurrencyStatus>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.domain.tokens.repository.MockQuotesRepository
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -145,7 +146,7 @@ internal class GetTokenListUseCaseTest {
|
|||
tokens = flowOf(
|
||||
MockTokens.tokens.right(),
|
||||
error,
|
||||
),
|
||||
).map { delay(timeMillis = 1_000); it },
|
||||
)
|
||||
|
||||
// When
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ internal class CryptoCurrencyStatusToTokenItemConverter(
|
|||
private val CryptoCurrencyStatus.networkIconResId: Int?
|
||||
@DrawableRes get() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
return if (currency is CryptoCurrency.Token) null else R.drawable.img_eth_22
|
||||
return if (currency is CryptoCurrency.Coin) null else R.drawable.img_eth_22
|
||||
}
|
||||
|
||||
private val CryptoCurrencyStatus.tokenIconResId: Int
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ appsflyer = "6.5.1"
|
|||
armadillo = "0.9.0"
|
||||
coil = "2.1.0"
|
||||
compose-shimmer = "1.0.3"
|
||||
coroutine = "1.5.2"
|
||||
coroutine = "1.7.2"
|
||||
desugarJdkLibs = "1.1.5"
|
||||
firebase = "26.0.0"
|
||||
googleMaterialComponent = "1.6.1"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue