Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-01 13:56:39 +03:00
parent 642bcca31f
commit 81a16c8bb1
46 changed files with 610 additions and 615 deletions

View file

@ -28,13 +28,13 @@ internal object TokensDomainModule {
@Provides
@ViewModelScoped
fun provideGetTokenUseCase(
fun provideGetPrimaryCurrencyUseCase(
tokensRepository: TokensRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetTokenUseCase {
return GetTokenUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers)
): GetPrimaryCurrencyUseCase {
return GetPrimaryCurrencyUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers)
}
@Provides

View file

@ -7,61 +7,61 @@ import java.math.BigDecimal
internal object MockQuotes {
val quote1 = Quote(
tokenId = MockTokens.token1.id,
currencyId = MockTokens.token1.id,
fiatRate = BigDecimal("1.23"),
priceChange = BigDecimal("0.01"),
)
val quote2 = Quote(
tokenId = MockTokens.token2.id,
currencyId = MockTokens.token2.id,
fiatRate = BigDecimal("2.34"),
priceChange = BigDecimal("-0.02"),
)
val quote3 = Quote(
tokenId = MockTokens.token3.id,
currencyId = MockTokens.token3.id,
fiatRate = BigDecimal("3.45"),
priceChange = BigDecimal("0.03"),
)
val quote4 = Quote(
tokenId = MockTokens.token4.id,
currencyId = MockTokens.token4.id,
fiatRate = BigDecimal("4.56"),
priceChange = BigDecimal("-0.04"),
)
val quote5 = Quote(
tokenId = MockTokens.token5.id,
currencyId = MockTokens.token5.id,
fiatRate = BigDecimal("5.67"),
priceChange = BigDecimal("0.05"),
)
val quote6 = Quote(
tokenId = MockTokens.token6.id,
currencyId = MockTokens.token6.id,
fiatRate = BigDecimal("6.78"),
priceChange = BigDecimal("-0.06"),
)
val quote7 = Quote(
tokenId = MockTokens.token7.id,
currencyId = MockTokens.token7.id,
fiatRate = BigDecimal("7.89"),
priceChange = BigDecimal("0.07"),
)
val quote8 = Quote(
tokenId = MockTokens.token8.id,
currencyId = MockTokens.token8.id,
fiatRate = BigDecimal("8.90"),
priceChange = BigDecimal("-0.08"),
)
val quote9 = Quote(
tokenId = MockTokens.token9.id,
currencyId = MockTokens.token9.id,
fiatRate = BigDecimal("9.01"),
priceChange = BigDecimal("0.09"),
)
val quote10 = Quote(
tokenId = MockTokens.token10.id,
currencyId = MockTokens.token10.id,
fiatRate = BigDecimal("10.12"),
priceChange = BigDecimal("-0.10"),
)

View file

@ -1,25 +1,23 @@
package com.tangem.data.tokens.mock
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal object MockTokens {
val token1
get() = Token(
id = Token.ID("token1"),
get() = CryptoCurrency.Coin(
id = CryptoCurrency.ID("token1"),
networkId = MockNetworks.network1.id,
name = "Token 1",
symbol = "T1",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = null,
derivationPath = null,
)
val token2
get() = Token(
id = Token.ID("token2"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token2"),
networkId = MockNetworks.network1.id,
name = "Token 2",
symbol = "T2",
@ -30,8 +28,8 @@ internal object MockTokens {
derivationPath = null,
)
val token3
get() = Token(
id = Token.ID("token3"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token3"),
networkId = MockNetworks.network1.id,
name = "Token 3",
symbol = "T3",
@ -42,20 +40,18 @@ internal object MockTokens {
derivationPath = null,
)
val token4
get() = Token(
id = Token.ID("token4"),
get() = CryptoCurrency.Coin(
id = CryptoCurrency.ID("token4"),
networkId = MockNetworks.network2.id,
name = "Token 4",
symbol = "T4",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = null,
derivationPath = null,
)
val token5
get() = Token(
id = Token.ID("token5"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token5"),
networkId = MockNetworks.network2.id,
name = "Token 5",
symbol = "T5",
@ -66,8 +62,8 @@ internal object MockTokens {
derivationPath = null,
)
val token6
get() = Token(
id = Token.ID("token6"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token6"),
networkId = MockNetworks.network2.id,
name = "Token 6",
symbol = "T6",
@ -78,20 +74,18 @@ internal object MockTokens {
derivationPath = null,
)
val token7
get() = Token(
id = Token.ID("token7"),
get() = CryptoCurrency.Coin(
id = CryptoCurrency.ID("token7"),
networkId = MockNetworks.network3.id,
name = "Token 7",
symbol = "T7",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = null,
derivationPath = null,
)
val token8
get() = Token(
id = Token.ID("token8"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token8"),
networkId = MockNetworks.network3.id,
name = "Token 8",
symbol = "T8",
@ -102,8 +96,8 @@ internal object MockTokens {
derivationPath = null,
)
val token9
get() = Token(
id = Token.ID("token9"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token9"),
networkId = MockNetworks.network3.id,
name = "Token 9",
symbol = "T9",
@ -114,8 +108,8 @@ internal object MockTokens {
derivationPath = null,
)
val token10
get() = Token(
id = Token.ID("token10"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token10"),
networkId = MockNetworks.network3.id,
name = "Token 10",
symbol = "T10",

View file

@ -1,15 +1,15 @@
package com.tangem.data.tokens.repository
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.tokens.utils.CardTokensFactory
import com.tangem.data.tokens.utils.ResponseTokensFactory
import com.tangem.data.tokens.utils.CardCurrenciesFactory
import com.tangem.data.tokens.utils.ResponseCurrenciesFactory
import com.tangem.data.tokens.utils.UserTokensResponseFactory
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
@ -27,18 +27,18 @@ internal class DefaultTokensRepository(
) : TokensRepository {
private val demoConfig = DemoConfig()
private val responseTokensFactory = ResponseTokensFactory(demoConfig)
private val cardTokensFactory = CardTokensFactory(demoConfig)
private val responseCurrenciesFactory = ResponseCurrenciesFactory(demoConfig)
private val cardCurrenciesFactory = CardCurrenciesFactory(demoConfig)
private val userTokensResponseFactory = UserTokensResponseFactory()
override suspend fun saveTokens(
userWalletId: UserWalletId,
tokens: Set<Token>,
currencies: Set<CryptoCurrency>,
isGroupedByNetwork: Boolean,
isSortedByBalance: Boolean,
) = withContext(dispatchers.io) {
val response = userTokensResponseFactory.createUserTokensResponse(
tokens = tokens,
currencies = currencies,
isGroupedByNetwork = isGroupedByNetwork,
isSortedByBalance = isSortedByBalance,
)
@ -46,7 +46,7 @@ internal class DefaultTokensRepository(
storeAndPushTokens(userWalletId, response)
}
override suspend fun getSingleCurrencyWalletToken(userWalletId: UserWalletId): Token {
override suspend fun getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency {
val userWallet = withContext(dispatchers.io) {
requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find a user wallet with provided ID: $userWalletId"
@ -56,11 +56,13 @@ internal class DefaultTokensRepository(
"Single currency wallet excepted, but multi currency wallet was found: $userWalletId"
}
return cardTokensFactory
.createPrimaryTokenForSingleCurrencyCard(userWallet.scanResponse)
return cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
}
override fun getMultiCurrencyWalletTokens(userWalletId: UserWalletId, refresh: Boolean): Flow<Set<Token>> {
override fun getMultiCurrencyWalletCurrencies(
userWalletId: UserWalletId,
refresh: Boolean,
): Flow<Set<CryptoCurrency>> {
return channelFlow {
val userWallet = withContext(dispatchers.io) {
requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
@ -72,7 +74,7 @@ internal class DefaultTokensRepository(
}
launch(dispatchers.io) {
getMultiCurrencyWalletTokens(userWallet).collectLatest(::send)
getMultiCurrencyWalletCurrencies(userWallet).collectLatest(::send)
}
launch(dispatchers.io) {
@ -93,9 +95,9 @@ internal class DefaultTokensRepository(
.flowOn(dispatchers.io)
}
private fun getMultiCurrencyWalletTokens(userWallet: UserWallet): Flow<Set<Token>> {
private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow<Set<CryptoCurrency>> {
return userTokensStore.get(userWallet.walletId).map { storedTokens ->
responseTokensFactory.createTokens(
responseCurrenciesFactory.createTokens(
response = storedTokens,
card = userWallet.scanResponse.card,
)
@ -131,7 +133,9 @@ internal class DefaultTokensRepository(
if (NOT_FOUND_HTTP_CODE in errorMessage) {
val response = userTokensStore.getSyncOrNull(userWallet.walletId)
?: userTokensResponseFactory.createUserTokensResponse(
tokens = cardTokensFactory.createDefaultTokensForMultiCurrencyCard(userWallet.scanResponse.card),
currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(
userWallet.scanResponse.card,
),
isGroupedByNetwork = false,
isSortedByBalance = false,
)

View file

@ -1,9 +1,9 @@
package com.tangem.data.tokens.repository
import com.tangem.data.tokens.mock.MockNetworks
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
@ -19,7 +19,7 @@ internal class MockNetworksRepository : NetworksRepository {
override fun getNetworkStatuses(
userWalletId: UserWalletId,
networks: Map<Network.ID, Set<Token.ID>>,
networks: Map<Network.ID, Set<CryptoCurrency.ID>>,
refresh: Boolean,
): Flow<Set<NetworkStatus>> {
return flowOf(

View file

@ -1,18 +1,18 @@
package com.tangem.data.tokens.repository
import com.tangem.data.tokens.mock.MockQuotes
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.repository.QuotesRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
internal class MockQuotesRepository : QuotesRepository {
override fun getQuotes(tokensIds: Set<Token.ID>, refresh: Boolean): Flow<Set<Quote>> {
override fun getQuotes(tokensIds: Set<CryptoCurrency.ID>, refresh: Boolean): Flow<Set<Quote>> {
return flowOf(
MockQuotes.quotes
.filter { it.tokenId in tokensIds }
.filter { it.currencyId in tokensIds }
.toSet(),
)
}

View file

@ -6,13 +6,13 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import timber.log.Timber
import com.tangem.blockchain.common.Token as SdkToken
import com.tangem.domain.tokens.model.Token as DomainToken
internal class CardTokensFactory(private val demoConfig: DemoConfig) {
internal class CardCurrenciesFactory(private val demoConfig: DemoConfig) {
fun createDefaultTokensForMultiCurrencyCard(card: CardDTO): Set<DomainToken> {
fun createDefaultCoinsForMultiCurrencyCard(card: CardDTO): Set<CryptoCurrency.Coin> {
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
demoConfig.demoBlockchains
} else {
@ -26,7 +26,7 @@ internal class CardTokensFactory(private val demoConfig: DemoConfig) {
return blockchains.mapNotNull { createCoin(it, card) }.toSet()
}
fun createPrimaryTokenForSingleCurrencyCard(scanResponse: ScanResponse): DomainToken {
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
val card = scanResponse.card
val resolver = scanResponse.cardTypesResolver
val blockchain = resolver.getBlockchain()
@ -41,13 +41,13 @@ internal class CardTokensFactory(private val demoConfig: DemoConfig) {
return primaryToken ?: coin
}
private fun createToken(sdkToken: SdkToken, blockchain: Blockchain, card: CardDTO): DomainToken? {
private fun createToken(sdkToken: SdkToken, blockchain: Blockchain, card: CardDTO): CryptoCurrency.Token? {
if (blockchain != Blockchain.Unknown) {
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
return null
}
return DomainToken(
return CryptoCurrency.Token(
id = getTokenId(blockchain, sdkToken),
networkId = getNetworkId(blockchain),
name = sdkToken.name,
@ -60,21 +60,19 @@ internal class CardTokensFactory(private val demoConfig: DemoConfig) {
)
}
private fun createCoin(blockchain: Blockchain, card: CardDTO): DomainToken? {
private fun createCoin(blockchain: Blockchain, card: CardDTO): CryptoCurrency.Coin? {
if (blockchain != Blockchain.Unknown) {
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
return null
}
return DomainToken(
return CryptoCurrency.Coin(
id = getCoinId(blockchain),
networkId = getNetworkId(blockchain),
name = blockchain.fullName,
symbol = blockchain.currency,
iconUrl = getCoinIconUrl(blockchain),
decimals = blockchain.decimals(),
isCustom = false,
contractAddress = null,
derivationPath = getDerivationPath(blockchain, card),
)
}

View file

@ -0,0 +1,77 @@
package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.tokens.model.CryptoCurrency
import timber.log.Timber
import com.tangem.blockchain.common.Token as SdkToken
internal class ResponseCurrenciesFactory(private val demoConfig: DemoConfig) {
fun createTokens(response: UserTokensResponse, card: CardDTO): Set<CryptoCurrency> {
return response.tokens.mapNotNull { createToken(it, card) }.toSet()
}
private fun createToken(responseToken: UserTokensResponse.Token, card: CardDTO): CryptoCurrency? {
var blockchain = Blockchain.fromNetworkId(responseToken.networkId)
if (blockchain == null || blockchain == Blockchain.Unknown) {
Timber.e("Unable to find a blockchain with the network ID: ${responseToken.networkId}")
return null
}
if (demoConfig.isDemoCardId(card.cardId)) {
blockchain = blockchain.getTestnetVersion() ?: blockchain
}
val sdkToken = createSdkToken(responseToken)
return if (sdkToken == null) {
createCoin(blockchain, responseToken)
} else {
createToken(blockchain, sdkToken, responseToken.derivationPath)
}
}
private fun createSdkToken(token: UserTokensResponse.Token): SdkToken? {
return token.contractAddress?.let { contractAddress ->
SdkToken(
name = token.name,
symbol = token.symbol,
contractAddress = contractAddress,
decimals = token.decimals,
id = token.id,
)
}
}
private fun createCoin(blockchain: Blockchain, responseToken: UserTokensResponse.Token): CryptoCurrency.Coin {
return CryptoCurrency.Coin(
id = getCoinId(blockchain),
networkId = getNetworkId(blockchain),
name = responseToken.name,
symbol = responseToken.symbol,
decimals = responseToken.decimals,
derivationPath = responseToken.derivationPath,
iconUrl = getCoinIconUrl(blockchain),
)
}
private fun createToken(blockchain: Blockchain, sdkToken: Token, derivationPath: String?): CryptoCurrency.Token {
val id = getTokenId(blockchain, sdkToken)
return CryptoCurrency.Token(
id = id,
networkId = getNetworkId(blockchain),
name = sdkToken.name,
symbol = sdkToken.symbol,
decimals = sdkToken.decimals,
derivationPath = derivationPath,
iconUrl = getTokenIconUrl(blockchain, sdkToken),
contractAddress = sdkToken.contractAddress,
isCustom = isCustomToken(id),
)
}
}

View file

@ -1,60 +0,0 @@
package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.tokens.model.Token
import timber.log.Timber
import com.tangem.blockchain.common.Token as SdkToken
internal class ResponseTokensFactory(private val demoConfig: DemoConfig) {
fun createTokens(response: UserTokensResponse, card: CardDTO): Set<Token> {
return response.tokens.mapNotNull { createToken(it, card) }.toSet()
}
private fun createToken(token: UserTokensResponse.Token, card: CardDTO): Token? {
var blockchain = Blockchain.fromNetworkId(token.networkId)
if (blockchain == null) {
Timber.e("Unable to find a blockchain with the network ID: ${token.networkId}")
return null
}
if (demoConfig.isDemoCardId(card.cardId)) {
blockchain = blockchain.getTestnetVersion() ?: blockchain
}
val sdkToken = createSdkToken(token)
val (tokenId, iconUrl) = if (sdkToken == null) {
getCoinId(blockchain) to getCoinIconUrl(blockchain)
} else {
getTokenId(blockchain, sdkToken) to getTokenIconUrl(blockchain, sdkToken)
}
return Token(
id = tokenId,
networkId = getNetworkId(blockchain),
name = token.name,
symbol = token.symbol,
decimals = token.decimals,
iconUrl = iconUrl,
contractAddress = token.contractAddress,
derivationPath = token.derivationPath,
isCustom = isCustomToken(tokenId),
)
}
private fun createSdkToken(token: UserTokensResponse.Token): SdkToken? {
return token.contractAddress?.let { contractAddress ->
SdkToken(
name = token.name,
symbol = token.symbol,
contractAddress = contractAddress,
decimals = token.decimals,
id = token.id,
)
}
}
}

View file

@ -6,9 +6,9 @@ import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.blockchain.common.Token as SdkToken
import com.tangem.domain.tokens.model.Token as DomainToken
private const val DEFAULT_TOKENS_ICONS_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins"
private const val TOKEN_ICON_SIZE = "large"
@ -19,7 +19,7 @@ private const val TOKEN_ID_PREFIX = "token_"
private const val CUSTOM_TOKEN_ID_PREFIX = "custom_token_"
private const val TOKEN_ID_DELIMITER = '#'
internal fun isCustomToken(tokenId: DomainToken.ID): Boolean {
internal fun isCustomToken(tokenId: CryptoCurrency.ID): Boolean {
return tokenId.value.startsWith(CUSTOM_TOKEN_ID_PREFIX)
}
@ -41,17 +41,17 @@ internal fun getNetworkId(blockchain: Blockchain): Network.ID {
return Network.ID(value)
}
internal fun getCoinId(blockchain: Blockchain): DomainToken.ID {
internal fun getCoinId(blockchain: Blockchain): CryptoCurrency.ID {
return getTokenOrCoinId(blockchain, token = null)
}
internal fun getTokenId(blockchain: Blockchain, token: SdkToken): DomainToken.ID {
internal fun getTokenId(blockchain: Blockchain, token: SdkToken): CryptoCurrency.ID {
return getTokenOrCoinId(blockchain, token)
}
internal fun getResponseTokenId(token: DomainToken): String? {
return token.id.value.substringAfter(TOKEN_ID_DELIMITER)
.takeUnless { token.isCustom }
internal fun getResponseTokenId(currency: CryptoCurrency): String? {
return currency.id.value.substringAfter(TOKEN_ID_DELIMITER)
.takeUnless { currency is CryptoCurrency.Token && currency.isCustom }
}
internal fun getTokenIconUrl(blockchain: Blockchain, token: SdkToken): String? {
@ -74,7 +74,7 @@ internal fun getCoinIconUrl(blockchain: Blockchain): String? {
return coinId?.let(::getTokenIconUrlFromDefaultHost)
}
private fun getTokenOrCoinId(blockchain: Blockchain, token: SdkToken?): DomainToken.ID {
private fun getTokenOrCoinId(blockchain: Blockchain, token: SdkToken?): CryptoCurrency.ID {
val sdkTokenId = token?.id
val (prefix, suffix) = when {
token == null -> COIN_ID_PREFIX to blockchain.toCoinId()
@ -89,7 +89,7 @@ private fun getTokenOrCoinId(blockchain: Blockchain, token: SdkToken?): DomainTo
append(suffix.lowercase())
}
return DomainToken.ID(value)
return CryptoCurrency.ID(value)
}
private fun getTokenIconUrlFromDefaultHost(tokenId: String): String {

View file

@ -2,17 +2,17 @@ package com.tangem.data.tokens.utils
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.model.CryptoCurrency
internal class UserTokensResponseFactory {
fun createUserTokensResponse(
tokens: Set<Token>,
currencies: Set<CryptoCurrency>,
isGroupedByNetwork: Boolean,
isSortedByBalance: Boolean,
): UserTokensResponse {
return UserTokensResponse(
tokens = tokens.map(::createResponseToken),
tokens = currencies.map(::createResponseToken),
group = if (isGroupedByNetwork) {
UserTokensResponse.GroupType.NETWORK
} else {
@ -26,17 +26,17 @@ internal class UserTokensResponseFactory {
)
}
private fun createResponseToken(domainToken: Token): UserTokensResponse.Token {
val blockchain = getBlockchain(domainToken.networkId)
private fun createResponseToken(currency: CryptoCurrency): UserTokensResponse.Token {
val blockchain = getBlockchain(currency.networkId)
return UserTokensResponse.Token(
id = getResponseTokenId(domainToken),
id = getResponseTokenId(currency),
networkId = blockchain.toNetworkId(),
derivationPath = domainToken.derivationPath,
name = domainToken.name,
symbol = domainToken.symbol,
decimals = domainToken.decimals,
contractAddress = domainToken.contractAddress,
derivationPath = currency.derivationPath,
name = currency.name,
symbol = currency.symbol,
decimals = currency.decimals,
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
)
}
}

View file

@ -7,8 +7,8 @@ import arrow.core.raise.either
import arrow.core.raise.ensureNotNull
import arrow.core.toNonEmptySetOrNull
import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -22,7 +22,7 @@ class ApplyTokenListSortingUseCase(
suspend operator fun invoke(
userWalletId: UserWalletId,
sortedTokensIds: Set<Pair<Network.ID, Token.ID>>,
sortedTokensIds: Set<Pair<Network.ID, CryptoCurrency.ID>>,
isGroupedByNetwork: Boolean,
isSortedByBalance: Boolean,
): Either<TokenListSortingError, Unit> {
@ -30,7 +30,7 @@ class ApplyTokenListSortingUseCase(
either {
applySorting(
userWalletId = userWalletId,
tokens = sortTokens(sortedTokensIds, getTokens(userWalletId)),
tokens = sortTokens(sortedTokensIds, getCurrencies(userWalletId)),
isGrouped = isGroupedByNetwork,
isSortedByBalance = isSortedByBalance,
)
@ -39,14 +39,14 @@ class ApplyTokenListSortingUseCase(
}
private suspend fun Raise<TokenListSortingError>.sortTokens(
sortedTokensIds: Set<Pair<Network.ID, Token.ID>>,
unsortedTokens: Set<Token>,
): Set<Token> = withContext(dispatchers.default) {
sortedTokensIds: Set<Pair<Network.ID, CryptoCurrency.ID>>,
unsortedTokens: Set<CryptoCurrency>,
): Set<CryptoCurrency> = withContext(dispatchers.default) {
val nonEmptySortedTokensIds = ensureNotNull(sortedTokensIds.toNonEmptySetOrNull()) {
TokenListSortingError.TokenListIsEmpty
}
val sortedTokens = sortedMapOf<Int, Token>()
val sortedTokens = sortedMapOf<Int, CryptoCurrency>()
unsortedTokens.forEach { token ->
val index = nonEmptySortedTokensIds.indexOfFirst { (networkId, tokenId) ->
@ -65,9 +65,9 @@ class ApplyTokenListSortingUseCase(
}
}
private suspend fun Raise<TokenListSortingError>.getTokens(userWalletId: UserWalletId): Set<Token> {
private suspend fun Raise<TokenListSortingError>.getCurrencies(userWalletId: UserWalletId): Set<CryptoCurrency> {
val tokens = catch(
block = { tokensRepository.getMultiCurrencyWalletTokens(userWalletId, refresh = false).firstOrNull() },
block = { tokensRepository.getMultiCurrencyWalletCurrencies(userWalletId, refresh = false).firstOrNull() },
catch = { raise(TokenListSortingError.DataError(it)) },
)
@ -78,7 +78,7 @@ class ApplyTokenListSortingUseCase(
private suspend fun Raise<TokenListSortingError>.applySorting(
userWalletId: UserWalletId,
tokens: Set<Token>,
tokens: Set<CryptoCurrency>,
isGrouped: Boolean,
isSortedByBalance: Boolean,
) = withContext(dispatchers.io) {

View file

@ -7,8 +7,8 @@ import arrow.core.raise.recover
import arrow.core.right
import com.tangem.domain.tokens.error.TokenError
import com.tangem.domain.tokens.error.mapper.mapToTokenError
import com.tangem.domain.tokens.model.TokenStatus
import com.tangem.domain.tokens.operations.TokensStatusesOperations
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.repository.TokensRepository
@ -18,14 +18,17 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
class GetTokenUseCase(
class GetPrimaryCurrencyUseCase(
private val tokensRepository: TokensRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val dispatchers: CoroutineDispatcherProvider,
) {
operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = false): Flow<Either<TokenError, TokenStatus>> {
operator fun invoke(
userWalletId: UserWalletId,
refresh: Boolean = false,
): Flow<Either<TokenError, CryptoCurrencyStatus>> {
return channelFlow {
recover(
block = {
@ -40,8 +43,11 @@ class GetTokenUseCase(
}
}
private suspend fun Raise<TokenError>.getToken(userWalletId: UserWalletId, refresh: Boolean): Flow<TokenStatus> {
val operations = TokensStatusesOperations(
private suspend fun Raise<TokenError>.getToken(
userWalletId: UserWalletId,
refresh: Boolean,
): Flow<CryptoCurrencyStatus> {
val operations = CurrenciesStatusesOperations(
tokensRepository = tokensRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
@ -49,9 +55,9 @@ class GetTokenUseCase(
refresh = refresh,
dispatchers = dispatchers,
raise = this,
transformError = TokensStatusesOperations.Error::mapToTokenError,
transformError = CurrenciesStatusesOperations.Error::mapToTokenError,
)
return operations.getSingleCurrencyWalletTokenStatusFlow()
return operations.getPrimaryCurrencyStatusFlow()
}
}

View file

@ -7,10 +7,10 @@ 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
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.model.TokenStatus
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.operations.TokenListOperations
import com.tangem.domain.tokens.operations.TokensStatusesOperations
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.repository.TokensRepository
@ -51,21 +51,21 @@ class GetTokenListUseCase(
private fun Raise<TokenListError>.getTokensStatuses(
userWalletId: UserWalletId,
refresh: Boolean,
): Flow<Set<TokenStatus>> {
val operations = TokensStatusesOperations(
): Flow<Set<CryptoCurrencyStatus>> {
val operations = CurrenciesStatusesOperations(
userWalletId = userWalletId,
refresh = refresh,
useCase = this@GetTokenListUseCase,
raise = this,
transformError = TokensStatusesOperations.Error::mapToTokenListError,
transformError = CurrenciesStatusesOperations.Error::mapToTokenListError,
)
return operations.getMultiCurrencyWalletTokensStatusesFlow()
return operations.getMultiCurrencyWalletStatusesFlow()
}
private fun Raise<TokenListError>.createTokenList(
userWalletId: UserWalletId,
tokens: Set<TokenStatus>,
tokens: Set<CryptoCurrencyStatus>,
): Flow<TokenList> {
val operations = TokenListOperations(
userWalletId = userWalletId,

View file

@ -41,7 +41,7 @@ class ToggleTokenListGroupingUseCase(
val sortingOperations = getSortingOperations(tokenList)
val tokens = sortingOperations.getTokens()
val networks = getNetworks(tokens.map { it.networkId }.toSet())
val networks = getNetworks(tokens.map { it.currency.networkId }.toSet())
return TokenList.GroupedByNetwork(
groups = sortingOperations.getGroupedTokens(networks),
totalFiatBalance = tokenList.totalFiatBalance,
@ -55,7 +55,7 @@ class ToggleTokenListGroupingUseCase(
val sortingOperations = getSortingOperations(tokenList)
return TokenList.Ungrouped(
tokens = sortingOperations.getTokens(),
currencies = sortingOperations.getTokens(),
totalFiatBalance = tokenList.totalFiatBalance,
sortedBy = sortingOperations.getSortType(),
)

View file

@ -49,7 +49,7 @@ class ToggleTokenListSortingUseCase(
val operations = getSortingOperations(tokenList)
return tokenList.copy(
tokens = operations.getTokens(),
currencies = operations.getTokens(),
sortedBy = operations.getSortType(),
)
}

View file

@ -1,14 +1,14 @@
package com.tangem.domain.tokens.error.mapper
import com.tangem.domain.tokens.error.TokenError
import com.tangem.domain.tokens.operations.TokensStatusesOperations
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
internal fun TokensStatusesOperations.Error.mapToTokenError(): TokenError {
internal fun CurrenciesStatusesOperations.Error.mapToTokenError(): TokenError {
return when (this) {
is TokensStatusesOperations.Error.DataError -> TokenError.DataError(this.cause)
is TokensStatusesOperations.Error.EmptyNetworksStatuses,
is TokensStatusesOperations.Error.EmptyQuotes,
is TokensStatusesOperations.Error.EmptyTokens,
is CurrenciesStatusesOperations.Error.DataError -> TokenError.DataError(this.cause)
is CurrenciesStatusesOperations.Error.EmptyNetworksStatuses,
is CurrenciesStatusesOperations.Error.EmptyQuotes,
is CurrenciesStatusesOperations.Error.EmptyCurrencies,
-> TokenError.UnableToCreateToken
}
}

View file

@ -1,15 +1,15 @@
package com.tangem.domain.tokens.error.mapper
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.operations.TokenListOperations
import com.tangem.domain.tokens.operations.TokensStatusesOperations
internal fun TokensStatusesOperations.Error.mapToTokenListError(): TokenListError {
internal fun CurrenciesStatusesOperations.Error.mapToTokenListError(): TokenListError {
return when (this) {
is TokensStatusesOperations.Error.DataError -> TokenListError.DataError(this.cause)
is TokensStatusesOperations.Error.EmptyNetworksStatuses,
is TokensStatusesOperations.Error.EmptyQuotes,
is TokensStatusesOperations.Error.EmptyTokens,
is CurrenciesStatusesOperations.Error.DataError -> TokenListError.DataError(this.cause)
is CurrenciesStatusesOperations.Error.EmptyNetworksStatuses,
is CurrenciesStatusesOperations.Error.EmptyQuotes,
is CurrenciesStatusesOperations.Error.EmptyCurrencies,
-> TokenListError.EmptyTokens
}
}

View file

@ -0,0 +1,61 @@
package com.tangem.domain.tokens.model
sealed class CryptoCurrency {
abstract val id: ID
abstract val networkId: Network.ID
abstract val name: String
abstract val symbol: String
abstract val decimals: Int
abstract val iconUrl: String?
abstract val derivationPath: String?
data class Coin(
override val id: ID,
override val networkId: Network.ID,
override val name: String,
override val symbol: String,
override val decimals: Int,
override val iconUrl: String?,
override val derivationPath: String?,
) : CryptoCurrency() {
init {
checkProperties()
}
}
data class Token(
override val id: ID,
override val networkId: Network.ID,
override val name: String,
override val symbol: String,
override val decimals: Int,
override val iconUrl: String?,
override val derivationPath: String?,
val contractAddress: String,
val isCustom: Boolean,
) : CryptoCurrency() {
init {
checkProperties()
require(contractAddress.isNotBlank()) { "Token contract address must not be blank" }
}
}
@JvmInline
value class ID(val value: String) {
init {
require(value.isNotBlank()) { "Crypto currency ID must not be blank" }
}
}
protected fun checkProperties() {
require(name.isNotBlank()) { "Crypto currency name must not be blank" }
require(symbol.isNotBlank()) { "Crypto currency symbol must not be blank" }
require(iconUrl?.isNotBlank() ?: true) { "Crypto currency icon URL must not be blank" }
require(decimals > 0) { "Crypto currency decimal must not be less then zero, but it is: $decimals" }
require(derivationPath?.isNotBlank() ?: true) { "Crypto currency derivation path must not be blank" }
}
}

View file

@ -2,14 +2,8 @@ package com.tangem.domain.tokens.model
import java.math.BigDecimal
data class TokenStatus(
val id: Token.ID,
val networkId: Network.ID,
val name: String,
val symbol: String,
val decimals: Int,
val iconUrl: String?,
val isCoin: Boolean,
data class CryptoCurrencyStatus(
val currency: CryptoCurrency,
val value: Status,
) {

View file

@ -2,5 +2,5 @@ package com.tangem.domain.tokens.model
data class NetworkGroup(
val network: Network,
val tokens: Set<TokenStatus>,
val currencies: Set<CryptoCurrencyStatus>,
)

View file

@ -8,16 +8,16 @@ data class NetworkStatus(
) {
sealed class Status {
open val amounts: Map<Token.ID, BigDecimal>? = null
open val amounts: Map<CryptoCurrency.ID, BigDecimal>? = null
}
object Unreachable : Status()
object MissedDerivation : Status()
data class TransactionInProgress(override val amounts: Map<Token.ID, BigDecimal>) : Status()
data class TransactionInProgress(override val amounts: Map<CryptoCurrency.ID, BigDecimal>) : Status()
data class Verified(override val amounts: Map<Token.ID, BigDecimal>) : Status()
data class Verified(override val amounts: Map<CryptoCurrency.ID, BigDecimal>) : Status()
data class NoAccount(val amountToCreateAccount: BigDecimal) : Status()
}

View file

@ -3,7 +3,7 @@ package com.tangem.domain.tokens.model
import java.math.BigDecimal
data class Quote(
val tokenId: Token.ID,
val currencyId: CryptoCurrency.ID,
val fiatRate: BigDecimal,
val priceChange: BigDecimal,
)

View file

@ -1,31 +0,0 @@
package com.tangem.domain.tokens.model
data class Token(
val id: ID,
val networkId: Network.ID,
val name: String,
val symbol: String,
val iconUrl: String?,
val decimals: Int,
val isCustom: Boolean,
val contractAddress: String?,
val derivationPath: String?,
) {
init {
require(name.isNotBlank()) { "Token name must not be blank" }
require(symbol.isNotBlank()) { "Token symbol must not be blank" }
require(iconUrl?.isNotBlank() ?: true) { "Token icon URL must not be blank" }
require(decimals > 0) { "Token decimal must not be less then zero, but it is: $decimals" }
require(contractAddress?.isNotBlank() ?: true) { "Token contract address must not be blank" }
require(derivationPath?.isNotBlank() ?: true) { "Token derivation path must not be blank" }
}
@JvmInline
value class ID(val value: String) {
init {
require(value.isNotBlank()) { "Token ID value must not be blank" }
}
}
}

View file

@ -13,7 +13,7 @@ sealed class TokenList {
) : TokenList()
data class Ungrouped(
val tokens: Set<TokenStatus>,
val currencies: Set<CryptoCurrencyStatus>,
override val totalFiatBalance: FiatBalance,
override val sortedBy: SortType,
) : TokenList()

View file

@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withContext
@Suppress("LongParameterList")
internal class TokensStatusesOperations<E>(
internal class CurrenciesStatusesOperations<E>(
private val tokensRepository: TokensRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
@ -24,7 +24,7 @@ internal class TokensStatusesOperations<E>(
private val dispatchers: CoroutineDispatcherProvider,
raise: Raise<E>,
transformError: (Error) -> E,
) : DelegatedRaise<TokensStatusesOperations.Error, E>(raise, transformError) {
) : DelegatedRaise<CurrenciesStatusesOperations.Error, E>(raise, transformError) {
constructor(
userWalletId: UserWalletId,
@ -43,8 +43,8 @@ internal class TokensStatusesOperations<E>(
transformError = transformError,
)
fun getMultiCurrencyWalletTokensStatusesFlow(): Flow<Set<TokenStatus>> {
return getMultiCurrencyWalletTokens().flatMapConcat {
fun getMultiCurrencyWalletStatusesFlow(): Flow<Set<CryptoCurrencyStatus>> {
return getMultiCurrencyWalletCurrencies().flatMapConcat {
val tokens = it.toNonEmptySetOrNull()
if (tokens == null) {
@ -60,12 +60,12 @@ internal class TokensStatusesOperations<E>(
}
}
suspend fun getSingleCurrencyWalletTokenStatusFlow(): Flow<TokenStatus> {
val token = getSingleCurrencyWalletToken()
suspend fun getPrimaryCurrencyStatusFlow(): Flow<CryptoCurrencyStatus> {
val token = getPrimaryCurrency()
val quoteFlow = getQuotes(nonEmptySetOf(token.id))
.map { quotes ->
quotes.singleOrNull { it.tokenId == token.id }
quotes.singleOrNull { it.currencyId == token.id }
}
val statusFlow = getNetworksStatues(groupTokens(nonEmptySetOf(token)))
@ -79,61 +79,69 @@ internal class TokensStatusesOperations<E>(
}
private suspend fun createTokensStatuses(
tokens: Set<Token>,
tokens: Set<CryptoCurrency>,
quotes: Set<Quote>,
networkStatuses: Set<NetworkStatus>,
): Set<TokenStatus> = withContext(dispatchers.default) {
): Set<CryptoCurrencyStatus> = withContext(dispatchers.default) {
tokens.mapTo(hashSetOf()) { token ->
val quote = quotes.firstOrNull { it.tokenId == token.id }
val quote = quotes.firstOrNull { it.currencyId == token.id }
val networkStatus = networkStatuses.firstOrNull { it.networkId == token.networkId }
createStatus(token, quote, networkStatus)
}
}
private suspend fun createStatus(token: Token, quote: Quote?, networkStatus: NetworkStatus?): TokenStatus {
val tokenStatusOperations = TokenStatusOperations(
token = token,
private suspend fun createStatus(
token: CryptoCurrency,
quote: Quote?,
networkStatus: NetworkStatus?,
): CryptoCurrencyStatus {
val currencyStatusOperations = CurrencyStatusOperations(
currency = token,
quote = quote,
networkStatus = networkStatus,
dispatchers = dispatchers,
)
return tokenStatusOperations.createTokenStatus()
return currencyStatusOperations.createTokenStatus()
}
private fun getMultiCurrencyWalletTokens(): Flow<Set<Token>> {
return tokensRepository.getMultiCurrencyWalletTokens(userWalletId, refresh)
private fun getMultiCurrencyWalletCurrencies(): Flow<Set<CryptoCurrency>> {
return tokensRepository.getMultiCurrencyWalletCurrencies(userWalletId, refresh)
.catch { raise(Error.DataError(it)) }
.onEmpty { raise(Error.EmptyTokens) }
.onEmpty { raise(Error.EmptyCurrencies) }
.flowOn(dispatchers.io)
}
private suspend fun getSingleCurrencyWalletToken(): Token {
private suspend fun getPrimaryCurrency(): CryptoCurrency {
return withContext(dispatchers.io) {
catch(
block = { tokensRepository.getSingleCurrencyWalletToken(userWalletId) },
block = { tokensRepository.getPrimaryCurrency(userWalletId) },
catch = { raise(Error.DataError(it)) },
)
}
}
private fun getQuotes(tokensIds: NonEmptySet<Token.ID>): Flow<Set<Quote>> {
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Set<Quote>> {
return quotesRepository.getQuotes(tokensIds, refresh)
.catch { raise(Error.DataError(it)) }
.onEmpty { raise(Error.EmptyQuotes) }
.flowOn(dispatchers.io)
}
private fun getNetworksStatues(groupedTokens: Map<Network.ID, NonEmptySet<Token.ID>>): Flow<Set<NetworkStatus>> {
private fun getNetworksStatues(
groupedTokens: Map<Network.ID, NonEmptySet<CryptoCurrency.ID>>,
): Flow<Set<NetworkStatus>> {
return networksRepository.getNetworkStatuses(userWalletId, groupedTokens, refresh)
.catch { raise(Error.DataError(it)) }
.onEmpty { raise(Error.EmptyNetworksStatuses) }
.flowOn(dispatchers.io)
}
private suspend fun groupTokens(tokens: NonEmptySet<Token>): Map<Network.ID, NonEmptySet<Token.ID>> =
withContext(dispatchers.default) {
private suspend fun groupTokens(
tokens: NonEmptySet<CryptoCurrency>,
): Map<Network.ID, NonEmptySet<CryptoCurrency.ID>> {
return withContext(dispatchers.default) {
tokens
.groupBy { it.networkId }
.mapValues { (_, tokens) ->
@ -143,10 +151,11 @@ internal class TokensStatusesOperations<E>(
.toNonEmptySet()
}
}
}
sealed class Error {
object EmptyTokens : Error()
object EmptyCurrencies : Error()
object EmptyQuotes : Error()

View file

@ -1,39 +1,33 @@
package com.tangem.domain.tokens.operations
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.domain.tokens.model.Token
import com.tangem.domain.tokens.model.TokenStatus
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import java.math.BigDecimal
internal class TokenStatusOperations(
private val token: Token,
internal class CurrencyStatusOperations(
private val currency: CryptoCurrency,
private val quote: Quote?,
private val networkStatus: NetworkStatus?,
private val dispatchers: CoroutineDispatcherProvider,
) {
suspend fun createTokenStatus(): TokenStatus = withContext(dispatchers.default) {
TokenStatus(
id = token.id,
networkId = token.networkId,
name = token.name,
symbol = token.symbol,
isCoin = token.contractAddress == null,
decimals = token.decimals,
iconUrl = token.iconUrl,
suspend fun createTokenStatus(): CryptoCurrencyStatus = withContext(dispatchers.default) {
CryptoCurrencyStatus(
currency = currency,
value = createStatus(),
)
}
private fun createStatus(): TokenStatus.Status {
private fun createStatus(): CryptoCurrencyStatus.Status {
return when (val status = networkStatus?.value) {
null -> TokenStatus.Loading
is NetworkStatus.MissedDerivation -> TokenStatus.MissedDerivation
is NetworkStatus.Unreachable -> TokenStatus.Unreachable
is NetworkStatus.NoAccount -> TokenStatus.NoAccount
null -> CryptoCurrencyStatus.Loading
is NetworkStatus.MissedDerivation -> CryptoCurrencyStatus.MissedDerivation
is NetworkStatus.Unreachable -> CryptoCurrencyStatus.Unreachable
is NetworkStatus.NoAccount -> CryptoCurrencyStatus.NoAccount
is NetworkStatus.TransactionInProgress,
is NetworkStatus.Verified,
-> createStatus(
@ -43,17 +37,17 @@ internal class TokenStatusOperations(
}
}
private fun createStatus(amount: BigDecimal, hasTransactionsInProgress: Boolean): TokenStatus.Status {
private fun createStatus(amount: BigDecimal, hasTransactionsInProgress: Boolean): CryptoCurrencyStatus.Status {
return when {
token.isCustom -> TokenStatus.Custom(
currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom(
amount = amount,
fiatAmount = calculateFiatAmountOrNull(amount, quote?.fiatRate),
fiatRate = quote?.fiatRate,
priceChange = quote?.priceChange,
hasTransactionsInProgress = hasTransactionsInProgress,
)
quote == null -> TokenStatus.Loading
else -> TokenStatus.Loaded(
quote == null -> CryptoCurrencyStatus.Loading
else -> CryptoCurrencyStatus.Loaded(
amount = amount,
fiatAmount = calculateFiatAmount(amount, quote.fiatRate),
fiatRate = quote.fiatRate,
@ -64,7 +58,7 @@ internal class TokenStatusOperations(
}
private fun getTokenAmount(): BigDecimal {
val amount = networkStatus?.value?.amounts?.get(token.id)
val amount = networkStatus?.value?.amounts?.get(currency.id)
return amount ?: error("Incorrect network status: $networkStatus")
}

View file

@ -1,14 +1,14 @@
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.domain.tokens.model.TokenStatus
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import java.math.BigDecimal
internal class TokenListFiatBalanceOperations(
private val tokens: NonEmptySet<TokenStatus>,
private val currencies: NonEmptySet<CryptoCurrencyStatus>,
private val isAnyTokenLoading: Boolean,
private val dispatcher: CoroutineDispatcherProvider,
) {
@ -18,25 +18,25 @@ internal class TokenListFiatBalanceOperations(
var fiatBalance: TokenList.FiatBalance = TokenList.FiatBalance.Loading
if (isAnyTokenLoading) return@withContext fiatBalance
for (token in tokens) {
for (token in currencies) {
when (val status = token.value) {
is TokenStatus.Loading -> {
is CryptoCurrencyStatus.Loading -> {
fiatBalance = TokenList.FiatBalance.Loading
break
}
is TokenStatus.MissedDerivation,
is TokenStatus.Unreachable,
is CryptoCurrencyStatus.MissedDerivation,
is CryptoCurrencyStatus.Unreachable,
-> {
fiatBalance = TokenList.FiatBalance.Failed
break
}
is TokenStatus.NoAccount -> {
is CryptoCurrencyStatus.NoAccount -> {
fiatBalance = recalculateBalanceForNoAccountStatus(fiatBalance)
}
is TokenStatus.Loaded -> {
is CryptoCurrencyStatus.Loaded -> {
fiatBalance = recalculateBalance(status, fiatBalance)
}
is TokenStatus.Custom -> {
is CryptoCurrencyStatus.Custom -> {
fiatBalance = recalculateBalance(status, fiatBalance)
}
}
@ -57,7 +57,7 @@ internal class TokenListFiatBalanceOperations(
}
private fun recalculateBalance(
status: TokenStatus.Loaded,
status: CryptoCurrencyStatus.Loaded,
currentBalance: TokenList.FiatBalance,
): TokenList.FiatBalance {
return with(currentBalance) {
@ -71,7 +71,7 @@ internal class TokenListFiatBalanceOperations(
}
private fun recalculateBalance(
status: TokenStatus.Custom,
status: CryptoCurrencyStatus.Custom,
currentBalance: TokenList.FiatBalance,
): TokenList.FiatBalance {
return with(currentBalance) {

View file

@ -7,9 +7,9 @@ import arrow.core.raise.ensureNotNull
import arrow.core.toNonEmptySetOrNull
import com.tangem.domain.core.raise.DelegatedRaise
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.model.TokenStatus
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.domain.wallets.models.UserWalletId
@ -22,7 +22,7 @@ internal class TokenListOperations<E>(
private val tokensRepository: TokensRepository,
private val networksRepository: NetworksRepository,
private val userWalletId: UserWalletId,
private val tokens: Set<TokenStatus>,
private val tokens: Set<CryptoCurrencyStatus>,
private val dispatchers: CoroutineDispatcherProvider,
raise: Raise<E>,
transform: (Error) -> E,
@ -30,7 +30,7 @@ internal class TokenListOperations<E>(
constructor(
userWalletId: UserWalletId,
tokens: Set<TokenStatus>,
tokens: Set<CryptoCurrencyStatus>,
useCase: GetTokenListUseCase,
raise: Raise<E>,
transform: (Error) -> E,
@ -55,7 +55,7 @@ internal class TokenListOperations<E>(
val tokensNes = tokens.toNonEmptySetOrNull()
?: return@withContext TokenList.NotInitialized
val isAnyTokenLoading = tokensNes.any { it.value is TokenStatus.Loading }
val isAnyTokenLoading = tokensNes.any { it.value is CryptoCurrencyStatus.Loading }
val fiatBalanceOperations = TokenListFiatBalanceOperations(tokensNes, isAnyTokenLoading, dispatchers)
createTokenList(
@ -69,14 +69,14 @@ internal class TokenListOperations<E>(
}
private suspend fun createTokenList(
tokens: NonEmptySet<TokenStatus>,
tokens: NonEmptySet<CryptoCurrencyStatus>,
fiatBalance: TokenList.FiatBalance,
isAnyTokenLoading: Boolean,
isGrouped: Boolean,
isSortedByBalance: Boolean,
): TokenList {
val sortingOperations = TokenListSortingOperations(
tokens = tokens,
currencies = tokens,
isAnyTokenLoading = isAnyTokenLoading,
sortByBalance = isSortedByBalance,
dispatchers = dispatchers,
@ -90,7 +90,7 @@ internal class TokenListOperations<E>(
}
private suspend fun createTokenList(
tokens: NonEmptySet<TokenStatus>,
tokens: NonEmptySet<CryptoCurrencyStatus>,
sortingOperations: TokenListSortingOperations<*>,
fiatBalance: TokenList.FiatBalance,
isGrouped: Boolean,
@ -108,9 +108,9 @@ internal class TokenListOperations<E>(
}
}
private suspend fun getNetworks(tokensNes: NonEmptySet<TokenStatus>): Set<Network> {
private suspend fun getNetworks(tokensNes: NonEmptySet<CryptoCurrencyStatus>): Set<Network> {
return withContext(dispatchers.io) {
val networksIds = tokensNes.map { it.networkId }.toNonEmptySet()
val networksIds = tokensNes.map { it.currency.networkId }.toNonEmptySet()
catch(
block = { networksRepository.getNetworks(networksIds) },
catch = { raise(Error.DataError(it)) },
@ -124,7 +124,7 @@ internal class TokenListOperations<E>(
): TokenList.Ungrouped = TokenList.Ungrouped(
sortedBy = sortingOperations.getSortType(),
totalFiatBalance = fiatBalance,
tokens = sortingOperations.getTokens(),
currencies = sortingOperations.getTokens(),
)
private suspend fun createGroupedTokenList(
@ -138,13 +138,13 @@ internal class TokenListOperations<E>(
)
private fun createUnsortedUngroupedTokenList(
tokens: NonEmptySet<TokenStatus>,
tokens: NonEmptySet<CryptoCurrencyStatus>,
fiatBalance: TokenList.FiatBalance,
): TokenList.Ungrouped {
return TokenList.Ungrouped(
sortedBy = TokenList.SortType.NONE,
totalFiatBalance = fiatBalance,
tokens = tokens,
currencies = tokens,
)
}

View file

@ -6,16 +6,16 @@ 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.Network
import com.tangem.domain.tokens.model.NetworkGroup
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.model.TokenStatus
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import java.math.BigDecimal
internal class TokenListSortingOperations<E>(
private val tokens: Set<TokenStatus>,
private val currencies: Set<CryptoCurrencyStatus>,
private val isAnyTokenLoading: Boolean,
private val sortByBalance: Boolean,
private val dispatchers: CoroutineDispatcherProvider,
@ -31,9 +31,9 @@ internal class TokenListSortingOperations<E>(
sortByBalance: Boolean = tokenList.sortedBy == TokenList.SortType.BALANCE,
isAnyTokenLoading: Boolean = tokenList.totalFiatBalance is TokenList.FiatBalance.Loading,
) : this(
tokens = when (tokenList) {
is TokenList.GroupedByNetwork -> tokenList.groups.flatMap { it.tokens }.toSet()
is TokenList.Ungrouped -> tokenList.tokens
currencies = when (tokenList) {
is TokenList.GroupedByNetwork -> tokenList.groups.flatMap { it.currencies }.toSet()
is TokenList.Ungrouped -> tokenList.currencies
is TokenList.NotInitialized -> emptySet()
},
isAnyTokenLoading = isAnyTokenLoading,
@ -45,7 +45,7 @@ internal class TokenListSortingOperations<E>(
suspend fun getGroupedTokens(networks: Set<Network>): NonEmptySet<NetworkGroup> {
return withContext(dispatchers.default) {
ensure(tokens.isNotEmpty()) { Error.EmptyTokens }
ensure(currencies.isNotEmpty()) { Error.EmptyTokens }
val networksNes = ensureNotNull(networks.toNonEmptySetOrNull()) {
Error.EmptyNetworks
}
@ -58,9 +58,9 @@ internal class TokenListSortingOperations<E>(
}
}
suspend fun getTokens(): NonEmptySet<TokenStatus> {
suspend fun getTokens(): NonEmptySet<CryptoCurrencyStatus> {
return withContext(dispatchers.default) {
val tokensNes = ensureNotNull(tokens.toNonEmptySetOrNull()) {
val tokensNes = ensureNotNull(currencies.toNonEmptySetOrNull()) {
Error.EmptyTokens
}
@ -71,8 +71,8 @@ internal class TokenListSortingOperations<E>(
fun getSortType() = if (sortByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE
private fun groupTokens(networks: NonEmptySet<Network>): NonEmptySet<NetworkGroup> {
val groupedTokens = tokens
.groupBy { it.networkId }
val groupedTokens = currencies
.groupBy { it.currency.networkId }
.map { (networkId, tokens) ->
val network = ensureNotNull(networks.firstOrNull { it.id == networkId }) {
Error.NetworkNotFound(networkId)
@ -80,7 +80,7 @@ internal class TokenListSortingOperations<E>(
NetworkGroup(
network = network,
tokens = ensureNotNull(tokens.toNonEmptySetOrNull()) { Error.EmptyTokens },
currencies = ensureNotNull(tokens.toNonEmptySetOrNull()) { Error.EmptyTokens },
)
}
.toNonEmptySetOrNull()
@ -91,9 +91,9 @@ internal class TokenListSortingOperations<E>(
private fun groupAndSortTokensByBalance(networks: NonEmptySet<Network>): NonEmptySet<NetworkGroup> {
val groupsWithSortedTokens = groupTokens(networks)
.map { group ->
val tokens = group.tokens as? NonEmptySet<TokenStatus>
val tokens = group.currencies as? NonEmptySet<CryptoCurrencyStatus>
?: error("Tokens can not be empty here")
group.copy(tokens = sortTokensByBalance(tokens))
group.copy(currencies = sortTokensByBalance(tokens))
}
.toNonEmptySet()
@ -104,7 +104,7 @@ internal class TokenListSortingOperations<E>(
}
}
private fun sortTokensByBalance(tokens: NonEmptySet<TokenStatus>): NonEmptySet<TokenStatus> {
private fun sortTokensByBalance(tokens: NonEmptySet<CryptoCurrencyStatus>): NonEmptySet<CryptoCurrencyStatus> {
return if (isAnyTokenLoading) {
tokens
} else {
@ -117,7 +117,7 @@ internal class TokenListSortingOperations<E>(
private fun sortGroupsByBalance(groupsWithSortedTokens: NonEmptySet<NetworkGroup>): NonEmptySet<NetworkGroup> {
return groupsWithSortedTokens
.sortedByDescending { group ->
group.tokens.sumOf { it.value.fiatAmount ?: BigDecimal.ZERO }
group.currencies.sumOf { it.value.fiatAmount ?: BigDecimal.ZERO }
}
.toNonEmptySetOrNull()
?: error("Tokens can not be empty here")

View file

@ -1,8 +1,8 @@
package com.tangem.domain.tokens.repository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
@ -15,7 +15,7 @@ interface NetworksRepository {
fun getNetworkStatuses(
userWalletId: UserWalletId,
networks: Map<Network.ID, Set<Token.ID>>,
networks: Map<Network.ID, Set<CryptoCurrency.ID>>,
refresh: Boolean,
): Flow<Set<NetworkStatus>>
}

View file

@ -1,7 +1,7 @@
package com.tangem.domain.tokens.repository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.model.Token
import kotlinx.coroutines.flow.Flow
/**
@ -9,5 +9,5 @@ import kotlinx.coroutines.flow.Flow
* */
interface QuotesRepository {
fun getQuotes(tokensIds: Set<Token.ID>, refresh: Boolean): Flow<Set<Quote>>
fun getQuotes(tokensIds: Set<CryptoCurrency.ID>, refresh: Boolean): Flow<Set<Quote>>
}

View file

@ -1,6 +1,6 @@
package com.tangem.domain.tokens.repository
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
@ -11,14 +11,14 @@ interface TokensRepository {
suspend fun saveTokens(
userWalletId: UserWalletId,
tokens: Set<Token>,
currencies: Set<CryptoCurrency>,
isGroupedByNetwork: Boolean,
isSortedByBalance: Boolean,
)
suspend fun getSingleCurrencyWalletToken(userWalletId: UserWalletId): Token
suspend fun getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency
fun getMultiCurrencyWalletTokens(userWalletId: UserWalletId, refresh: Boolean): Flow<Set<Token>>
fun getMultiCurrencyWalletCurrencies(userWalletId: UserWalletId, refresh: Boolean): Flow<Set<CryptoCurrency>>
fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean>

View file

@ -6,7 +6,7 @@ import arrow.core.right
import com.tangem.domain.core.error.DataError
import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.domain.tokens.mock.MockTokens
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.repository.MockTokensRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
@ -191,7 +191,7 @@ internal class ApplyTokenListSortingUseCaseTest {
private fun getTokensRepository(
sortTokensResult: Either<DataError, Unit> = Unit.right(),
tokens: Flow<Either<DataError, Set<Token>>> = flowOf(MockTokens.tokens.right()),
tokens: Flow<Either<DataError, Set<CryptoCurrency>>> = flowOf(MockTokens.tokens.right()),
): MockTokensRepository {
return MockTokensRepository(sortTokensResult, tokens, emptyFlow(), emptyFlow())
}

View file

@ -9,10 +9,10 @@ import com.tangem.domain.tokens.mock.MockNetworks
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.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.repository.MockNetworksRepository
import com.tangem.domain.tokens.repository.MockQuotesRepository
import com.tangem.domain.tokens.repository.MockTokensRepository
@ -224,7 +224,7 @@ internal class GetTokenListUseCaseTest {
fun `when tokens is empty then not initialized token list should be received`() = runTest {
val expectedResult = MockTokenLists.notInitializedTokenList.right()
val useCase = getUseCase(tokens = flowOf(emptySet<Token>().right()))
val useCase = getUseCase(tokens = flowOf(emptySet<CryptoCurrency>().right()))
// When
val result = useCase(userWalletId).first()
@ -318,7 +318,7 @@ internal class GetTokenListUseCaseTest {
}
private fun getUseCase(
tokens: Flow<Either<DataError, Set<Token>>> = flowOf(MockTokens.tokens.right()),
tokens: Flow<Either<DataError, Set<CryptoCurrency>>> = flowOf(MockTokens.tokens.right()),
quotes: Flow<Either<DataError, Set<Quote>>> = flowOf(MockQuotes.quotes.right()),
networks: Either<DataError, Set<Network>> = MockNetworks.networks.right(),
statuses: Flow<Either<DataError, Set<NetworkStatus>>> = flowOf(MockNetworks.errorNetworksStatuses.right()),

View file

@ -9,22 +9,22 @@ internal object MockNetworksGroups {
val networkGroup1 = NetworkGroup(
network = MockNetworks.network1,
tokens = MockTokensStates.failedTokenStates
.filter { it.networkId == MockNetworks.network1.id }
currencies = MockTokensStates.failedTokenStates
.filter { it.currency.networkId == MockNetworks.network1.id }
.toNonEmptySetOrNull()!!,
)
val networkGroup2 = NetworkGroup(
network = MockNetworks.network2,
tokens = MockTokensStates.failedTokenStates
.filter { it.networkId == MockNetworks.network2.id }
currencies = MockTokensStates.failedTokenStates
.filter { it.currency.networkId == MockNetworks.network2.id }
.toNonEmptySetOrNull()!!,
)
val networkGroup3 = NetworkGroup(
network = MockNetworks.network3,
tokens = MockTokensStates.failedTokenStates
.filter { it.networkId == MockNetworks.network3.id }
currencies = MockTokensStates.failedTokenStates
.filter { it.currency.networkId == MockNetworks.network3.id }
.toNonEmptySetOrNull()!!,
)
@ -32,21 +32,21 @@ internal object MockNetworksGroups {
val loadedNetworksGroups = failedNetworksGroups.map { group ->
group.copy(
tokens = MockTokensStates.loadedTokensStates
.filter { it.networkId == group.network.id }
currencies = MockTokensStates.loadedTokensStates
.filter { it.currency.networkId == group.network.id }
.toNonEmptySetOrNull()!!,
)
}.toNonEmptySet()
val sortedNetworksGroups = loadedNetworksGroups.map { group ->
group.copy(
tokens = group.tokens
currencies = group.currencies
.sortedByDescending { it.value.fiatAmount }
.toNonEmptySetOrNull()!!,
)
}
.sortedByDescending { group ->
group.tokens.sumOf { it.value.fiatAmount!! }
group.currencies.sumOf { it.value.fiatAmount!! }
}
.toNonEmptySetOrNull()!!
}

View file

@ -8,61 +8,61 @@ import java.math.BigDecimal
internal object MockQuotes {
val quote1 = Quote(
tokenId = MockTokens.token1.id,
currencyId = MockTokens.token1.id,
fiatRate = BigDecimal("1.23"),
priceChange = BigDecimal("0.01"),
)
val quote2 = Quote(
tokenId = MockTokens.token2.id,
currencyId = MockTokens.token2.id,
fiatRate = BigDecimal("2.34"),
priceChange = BigDecimal("-0.02"),
)
val quote3 = Quote(
tokenId = MockTokens.token3.id,
currencyId = MockTokens.token3.id,
fiatRate = BigDecimal("3.45"),
priceChange = BigDecimal("0.03"),
)
val quote4 = Quote(
tokenId = MockTokens.token4.id,
currencyId = MockTokens.token4.id,
fiatRate = BigDecimal("4.56"),
priceChange = BigDecimal("-0.04"),
)
val quote5 = Quote(
tokenId = MockTokens.token5.id,
currencyId = MockTokens.token5.id,
fiatRate = BigDecimal("5.67"),
priceChange = BigDecimal("0.05"),
)
val quote6 = Quote(
tokenId = MockTokens.token6.id,
currencyId = MockTokens.token6.id,
fiatRate = BigDecimal("6.78"),
priceChange = BigDecimal("-0.06"),
)
val quote7 = Quote(
tokenId = MockTokens.token7.id,
currencyId = MockTokens.token7.id,
fiatRate = BigDecimal("7.89"),
priceChange = BigDecimal("0.07"),
)
val quote8 = Quote(
tokenId = MockTokens.token8.id,
currencyId = MockTokens.token8.id,
fiatRate = BigDecimal("8.90"),
priceChange = BigDecimal("-0.08"),
)
val quote9 = Quote(
tokenId = MockTokens.token9.id,
currencyId = MockTokens.token9.id,
fiatRate = BigDecimal("9.01"),
priceChange = BigDecimal("0.09"),
)
val quote10 = Quote(
tokenId = MockTokens.token10.id,
currencyId = MockTokens.token10.id,
fiatRate = BigDecimal("10.12"),
priceChange = BigDecimal("-0.10"),
)

View file

@ -5,8 +5,8 @@ import arrow.core.toNonEmptySetOrNull
import com.tangem.domain.tokens.mock.MockNetworksGroups.failedNetworksGroups
import com.tangem.domain.tokens.mock.MockNetworksGroups.loadedNetworksGroups
import com.tangem.domain.tokens.mock.MockNetworksGroups.sortedNetworksGroups
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.model.TokenStatus
import java.math.BigDecimal
@Suppress("MemberVisibilityCanBePrivate")
@ -24,7 +24,7 @@ internal object MockTokenLists {
)
val emptyUngroupedTokenList = TokenList.Ungrouped(
tokens = emptySet(),
currencies = emptySet(),
totalFiatBalance = TokenList.FiatBalance.Failed,
sortedBy = TokenList.SortType.NONE,
)
@ -36,14 +36,14 @@ internal object MockTokenLists {
)
val failedUngroupedTokenList = TokenList.Ungrouped(
tokens = MockTokensStates.failedTokenStates,
currencies = MockTokensStates.failedTokenStates,
totalFiatBalance = TokenList.FiatBalance.Failed,
sortedBy = TokenList.SortType.NONE,
)
val loadingUngroupedTokenList = with(failedUngroupedTokenList) {
copy(
tokens = tokens.map { it.copy(value = TokenStatus.Loading) }.toNonEmptySetOrNull()!!,
currencies = currencies.map { it.copy(value = CryptoCurrencyStatus.Loading) }.toNonEmptySetOrNull()!!,
totalFiatBalance = TokenList.FiatBalance.Loading,
)
}
@ -53,8 +53,8 @@ internal object MockTokenLists {
totalFiatBalance = TokenList.FiatBalance.Loading,
groups = groups.map { group ->
group.copy(
tokens = group.tokens
.map { it.copy(value = TokenStatus.Loading) }
currencies = group.currencies
.map { it.copy(value = CryptoCurrencyStatus.Loading) }
.toNonEmptySetOrNull()!!,
)
}.toNonEmptySetOrNull()!!,
@ -66,7 +66,7 @@ internal object MockTokenLists {
val tokens = MockTokensStates.loadedTokensStates
return failedUngroupedTokenList.copy(
tokens = tokens,
currencies = tokens,
sortedBy = TokenList.SortType.NONE,
totalFiatBalance = TokenList.FiatBalance.Loaded(
amount = tokens.sumOf { it.value.fiatAmount ?: BigDecimal.ZERO },
@ -84,7 +84,7 @@ internal object MockTokenLists {
sortedBy = TokenList.SortType.NONE,
totalFiatBalance = TokenList.FiatBalance.Loaded(
amount = groups
.flatMap { it.tokens as NonEmptySet<TokenStatus> }
.flatMap { it.currencies as NonEmptySet<CryptoCurrencyStatus> }
.sumOf { it.value.fiatAmount ?: BigDecimal.ZERO },
isAllAmountsSummarized = true,
),
@ -98,7 +98,7 @@ internal object MockTokenLists {
.toNonEmptySetOrNull()!!
return unsortedUngroupedTokenList.copy(
tokens = tokens,
currencies = tokens,
sortedBy = TokenList.SortType.BALANCE,
)
}

View file

@ -1,119 +1,123 @@
package com.tangem.domain.tokens.mock
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.model.CryptoCurrency
internal object MockTokens {
val token1 = Token(
id = Token.ID("token1"),
networkId = MockNetworks.network1.id,
name = "Token 1",
symbol = "T1",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = null,
derivationPath = null,
)
val token2 = Token(
id = Token.ID("token2"),
networkId = MockNetworks.network1.id,
name = "Token 2",
symbol = "T2",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token3 = Token(
id = Token.ID("token3"),
networkId = MockNetworks.network1.id,
name = "Token 3",
symbol = "T3",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token4 = Token(
id = Token.ID("token4"),
networkId = MockNetworks.network2.id,
name = "Token 4",
symbol = "T4",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = null,
derivationPath = null,
)
val token5 = Token(
id = Token.ID("token5"),
networkId = MockNetworks.network2.id,
name = "Token 5",
symbol = "T5",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token6 = Token(
id = Token.ID("token6"),
networkId = MockNetworks.network2.id,
name = "Token 6",
symbol = "T6",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token7 = Token(
id = Token.ID("token7"),
networkId = MockNetworks.network3.id,
name = "Token 7",
symbol = "T7",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = null,
derivationPath = null,
)
val token8 = Token(
id = Token.ID("token8"),
networkId = MockNetworks.network3.id,
name = "Token 8",
symbol = "T8",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token9 = Token(
id = Token.ID("token9"),
networkId = MockNetworks.network3.id,
name = "Token 9",
symbol = "T9",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token10 = Token(
id = Token.ID("token10"),
networkId = MockNetworks.network3.id,
name = "Token 10",
symbol = "T10",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token1
get() = CryptoCurrency.Coin(
id = CryptoCurrency.ID("token1"),
networkId = MockNetworks.network1.id,
name = "Token 1",
symbol = "T1",
decimals = 8,
iconUrl = null,
derivationPath = null,
)
val token2
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token2"),
networkId = MockNetworks.network1.id,
name = "Token 2",
symbol = "T2",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token3
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token3"),
networkId = MockNetworks.network1.id,
name = "Token 3",
symbol = "T3",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token4
get() = CryptoCurrency.Coin(
id = CryptoCurrency.ID("token4"),
networkId = MockNetworks.network2.id,
name = "Token 4",
symbol = "T4",
decimals = 8,
iconUrl = null,
derivationPath = null,
)
val token5
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token5"),
networkId = MockNetworks.network2.id,
name = "Token 5",
symbol = "T5",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token6
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token6"),
networkId = MockNetworks.network2.id,
name = "Token 6",
symbol = "T6",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token7
get() = CryptoCurrency.Coin(
id = CryptoCurrency.ID("token7"),
networkId = MockNetworks.network3.id,
name = "Token 7",
symbol = "T7",
decimals = 8,
iconUrl = null,
derivationPath = null,
)
val token8
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token8"),
networkId = MockNetworks.network3.id,
name = "Token 8",
symbol = "T8",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token9
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token9"),
networkId = MockNetworks.network3.id,
name = "Token 9",
symbol = "T9",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val token10
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token10"),
networkId = MockNetworks.network3.id,
name = "Token 10",
symbol = "T10",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = "address",
derivationPath = null,
)
val tokens = setOf(token1, token2, token3, token4, token5, token6, token7, token8, token9, token10)
}

View file

@ -1,120 +1,60 @@
package com.tangem.domain.tokens.mock
import arrow.core.nonEmptySetOf
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.TokenStatus
@Suppress("MemberVisibilityCanBePrivate")
internal object MockTokensStates {
val tokenState1 = TokenStatus(
id = MockTokens.token1.id,
networkId = MockTokens.token1.networkId,
name = MockTokens.token1.name,
symbol = MockTokens.token1.symbol,
decimals = MockTokens.token1.decimals,
iconUrl = MockTokens.token1.iconUrl,
isCoin = MockTokens.token1.contractAddress == null,
value = TokenStatus.Unreachable,
val tokenState1 = CryptoCurrencyStatus(
currency = MockTokens.token1,
value = CryptoCurrencyStatus.Unreachable,
)
val tokenState2 = TokenStatus(
id = MockTokens.token2.id,
networkId = MockTokens.token2.networkId,
name = MockTokens.token2.name,
symbol = MockTokens.token2.symbol,
decimals = MockTokens.token2.decimals,
iconUrl = MockTokens.token2.iconUrl,
isCoin = MockTokens.token2.contractAddress == null,
value = TokenStatus.Unreachable,
val tokenState2 = CryptoCurrencyStatus(
currency = MockTokens.token2,
value = CryptoCurrencyStatus.Unreachable,
)
val tokenState3 = TokenStatus(
id = MockTokens.token3.id,
networkId = MockTokens.token3.networkId,
name = MockTokens.token3.name,
symbol = MockTokens.token3.symbol,
decimals = MockTokens.token3.decimals,
iconUrl = MockTokens.token3.iconUrl,
isCoin = MockTokens.token3.contractAddress == null,
value = TokenStatus.Unreachable,
val tokenState3 = CryptoCurrencyStatus(
currency = MockTokens.token3,
value = CryptoCurrencyStatus.Unreachable,
)
val tokenState4 = TokenStatus(
id = MockTokens.token4.id,
networkId = MockTokens.token4.networkId,
name = MockTokens.token4.name,
symbol = MockTokens.token4.symbol,
decimals = MockTokens.token4.decimals,
iconUrl = MockTokens.token4.iconUrl,
isCoin = MockTokens.token4.contractAddress == null,
value = TokenStatus.MissedDerivation,
val tokenState4 = CryptoCurrencyStatus(
currency = MockTokens.token4,
value = CryptoCurrencyStatus.MissedDerivation,
)
val tokenState5 = TokenStatus(
id = MockTokens.token5.id,
networkId = MockTokens.token5.networkId,
name = MockTokens.token5.name,
symbol = MockTokens.token5.symbol,
decimals = MockTokens.token5.decimals,
iconUrl = MockTokens.token5.iconUrl,
isCoin = MockTokens.token5.contractAddress == null,
value = TokenStatus.MissedDerivation,
val tokenState5 = CryptoCurrencyStatus(
currency = MockTokens.token5,
value = CryptoCurrencyStatus.MissedDerivation,
)
val tokenState6 = TokenStatus(
id = MockTokens.token6.id,
networkId = MockTokens.token6.networkId,
name = MockTokens.token6.name,
symbol = MockTokens.token6.symbol,
decimals = MockTokens.token6.decimals,
iconUrl = MockTokens.token6.iconUrl,
isCoin = MockTokens.token6.contractAddress == null,
value = TokenStatus.MissedDerivation,
val tokenState6 = CryptoCurrencyStatus(
currency = MockTokens.token6,
value = CryptoCurrencyStatus.MissedDerivation,
)
val tokenState7 = TokenStatus(
id = MockTokens.token7.id,
networkId = MockTokens.token7.networkId,
name = MockTokens.token7.name,
symbol = MockTokens.token7.symbol,
decimals = MockTokens.token7.decimals,
iconUrl = MockTokens.token7.iconUrl,
isCoin = MockTokens.token7.contractAddress == null,
value = TokenStatus.NoAccount,
val tokenState7 = CryptoCurrencyStatus(
currency = MockTokens.token7,
value = CryptoCurrencyStatus.NoAccount,
)
val tokenState8 = TokenStatus(
id = MockTokens.token8.id,
networkId = MockTokens.token8.networkId,
name = MockTokens.token8.name,
symbol = MockTokens.token8.symbol,
decimals = MockTokens.token8.decimals,
iconUrl = MockTokens.token8.iconUrl,
isCoin = MockTokens.token8.contractAddress == null,
value = TokenStatus.NoAccount,
val tokenState8 = CryptoCurrencyStatus(
currency = MockTokens.token8,
value = CryptoCurrencyStatus.NoAccount,
)
val tokenState9 = TokenStatus(
id = MockTokens.token9.id,
networkId = MockTokens.token9.networkId,
name = MockTokens.token9.name,
symbol = MockTokens.token9.symbol,
decimals = MockTokens.token9.decimals,
iconUrl = MockTokens.token9.iconUrl,
isCoin = MockTokens.token9.contractAddress == null,
value = TokenStatus.NoAccount,
val tokenState9 = CryptoCurrencyStatus(
currency = MockTokens.token9,
value = CryptoCurrencyStatus.NoAccount,
)
val tokenState10 = TokenStatus(
id = MockTokens.token10.id,
networkId = MockTokens.token10.networkId,
name = MockTokens.token10.name,
symbol = MockTokens.token10.symbol,
decimals = MockTokens.token10.decimals,
iconUrl = MockTokens.token10.iconUrl,
isCoin = MockTokens.token10.contractAddress == null,
value = TokenStatus.NoAccount,
val tokenState10 = CryptoCurrencyStatus(
currency = MockTokens.token10,
value = CryptoCurrencyStatus.NoAccount,
)
val failedTokenStates = nonEmptySetOf(
@ -130,14 +70,15 @@ internal object MockTokensStates {
tokenState10,
)
val loadedTokensStates = failedTokenStates.map { state ->
val networkStatus = MockNetworks.verifiedNetworksStatuses.first { it.networkId == state.networkId }
val amount = (networkStatus.value as NetworkStatus.Verified).amounts[state.id]!!
val quote = MockQuotes.quotes.first { it.tokenId == state.id }
val loadedTokensStates = failedTokenStates.map { status ->
val networkStatus = MockNetworks.verifiedNetworksStatuses
.first { it.networkId == status.currency.networkId }
val amount = (networkStatus.value as NetworkStatus.Verified).amounts[status.currency.id]!!
val quote = MockQuotes.quotes.first { it.currencyId == status.currency.id }
val fiatAmount = amount * quote.fiatRate
state.copy(
value = TokenStatus.Loaded(
status.copy(
value = CryptoCurrencyStatus.Loaded(
amount = amount,
fiatAmount = fiatAmount,
fiatRate = quote.fiatRate,

View file

@ -3,9 +3,9 @@ package com.tangem.domain.tokens.repository
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.domain.core.error.DataError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@ -21,7 +21,7 @@ internal class MockNetworksRepository(
override fun getNetworkStatuses(
userWalletId: UserWalletId,
networks: Map<Network.ID, Set<Token.ID>>,
networks: Map<Network.ID, Set<CryptoCurrency.ID>>,
refresh: Boolean,
): Flow<Set<NetworkStatus>> {
return statuses.map { it.getOrElse { e -> throw e } }

View file

@ -3,8 +3,8 @@ package com.tangem.domain.tokens.repository
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.domain.core.error.DataError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.model.Token
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@ -12,7 +12,7 @@ internal class MockQuotesRepository(
private val quotes: Flow<Either<DataError, Set<Quote>>>,
) : QuotesRepository {
override fun getQuotes(tokensIds: Set<Token.ID>, refresh: Boolean): Flow<Set<Quote>> {
override fun getQuotes(tokensIds: Set<CryptoCurrency.ID>, refresh: Boolean): Flow<Set<Quote>> {
return quotes.map { it.getOrElse { e -> throw e } }
}
}

View file

@ -4,19 +4,19 @@ import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.domain.core.error.DataError
import com.tangem.domain.tokens.mock.MockTokens
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
internal class MockTokensRepository(
private val sortTokensResult: Either<DataError, Unit>,
private val tokens: Flow<Either<DataError, Set<Token>>>,
private val tokens: Flow<Either<DataError, Set<CryptoCurrency>>>,
private val isGrouped: Flow<Either<DataError, Boolean>>,
private val isSortedByBalance: Flow<Either<DataError, Boolean>>,
) : TokensRepository {
var tokensIdsAfterSortingApply: Set<Token>? = null
var tokensIdsAfterSortingApply: Set<CryptoCurrency>? = null
private set
var isTokensGroupedAfterSortingApply: Boolean? = null
@ -27,22 +27,25 @@ internal class MockTokensRepository(
override suspend fun saveTokens(
userWalletId: UserWalletId,
tokens: Set<Token>,
currencies: Set<CryptoCurrency>,
isGroupedByNetwork: Boolean,
isSortedByBalance: Boolean,
) {
sortTokensResult.onLeft { throw it }
tokensIdsAfterSortingApply = tokens
tokensIdsAfterSortingApply = currencies
isTokensGroupedAfterSortingApply = isGroupedByNetwork
isTokensSortedByBalanceAfterSortingApply = isSortedByBalance
}
override suspend fun getSingleCurrencyWalletToken(userWalletId: UserWalletId): Token {
override suspend fun getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency {
return MockTokens.token1
}
override fun getMultiCurrencyWalletTokens(userWalletId: UserWalletId, refresh: Boolean): Flow<Set<Token>> {
override fun getMultiCurrencyWalletCurrencies(
userWalletId: UserWalletId,
refresh: Boolean,
): Flow<Set<CryptoCurrency>> {
return tokens.map { it.getOrElse { e -> throw e } }
}

View file

@ -3,49 +3,50 @@ package com.tangem.feature.wallet.presentation.wallet.utils
import androidx.annotation.DrawableRes
import com.tangem.core.ui.components.marketprice.PriceChangeConfig
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.tokens.model.TokenStatus
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.utils.converter.Converter
import java.math.BigDecimal
internal class TokenStatusToTokenItemConverter(
internal class CryptoCurrencyStatusToTokenItemConverter(
private val isWalletContentHidden: Boolean,
private val fiatCurrencyCode: String,
private val fiatCurrencySymbol: String,
) : Converter<TokenStatus, TokenItemState> {
) : Converter<CryptoCurrencyStatus, TokenItemState> {
private val TokenStatus.networkIconResId: Int?
private val CryptoCurrencyStatus.networkIconResId: Int?
@DrawableRes get() {
// TODO: [REDACTED_JIRA]
return if (isCoin) null else R.drawable.img_eth_22
return if (currency is CryptoCurrency.Token) null else R.drawable.img_eth_22
}
private val TokenStatus.tokenIconResId: Int
private val CryptoCurrencyStatus.tokenIconResId: Int
@DrawableRes get() {
// TODO: [REDACTED_JIRA]
return R.drawable.img_eth_22
}
override fun convert(value: TokenStatus): TokenItemState {
override fun convert(value: CryptoCurrencyStatus): TokenItemState {
return when (value.value) {
is TokenStatus.Loading -> TokenItemState.Loading
is TokenStatus.Loaded,
is TokenStatus.Custom,
is CryptoCurrencyStatus.Loading -> TokenItemState.Loading
is CryptoCurrencyStatus.Loaded,
is CryptoCurrencyStatus.Custom,
-> value.mapToTokenItemState()
// TODO: Add other token item states, currently not designed
is TokenStatus.MissedDerivation,
is TokenStatus.NoAccount,
is TokenStatus.Unreachable,
is CryptoCurrencyStatus.MissedDerivation,
is CryptoCurrencyStatus.NoAccount,
is CryptoCurrencyStatus.Unreachable,
-> value.mapToUnreachableTokenItemState()
}
}
private fun TokenStatus.mapToTokenItemState(): TokenItemState.Content {
private fun CryptoCurrencyStatus.mapToTokenItemState(): TokenItemState.Content {
return TokenItemState.Content(
id = this.id.value,
name = this.name,
tokenIconUrl = this.iconUrl,
id = currency.id.value,
name = currency.name,
tokenIconUrl = currency.iconUrl,
tokenIconResId = this.tokenIconResId,
networkIconResId = this.networkIconResId,
amount = getFormattedAmount(),
@ -61,27 +62,27 @@ internal class TokenStatusToTokenItemConverter(
)
}
private fun TokenStatus.getFormattedAmount(): String {
private fun CryptoCurrencyStatus.getFormattedAmount(): String {
val amount = value.amount ?: return UNKNOWN_AMOUNT_SIGN
return BigDecimalFormatter.formatCryptoAmount(amount, symbol, decimals)
return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals)
}
private fun TokenStatus.getFormattedFiatAmount(): String {
private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String {
val fiatAmount = value.fiatAmount ?: return UNKNOWN_AMOUNT_SIGN
return BigDecimalFormatter.formatFiatAmount(fiatAmount, fiatCurrencyCode, fiatCurrencySymbol)
}
private fun TokenStatus.mapToUnreachableTokenItemState() = TokenItemState.Unreachable(
id = this.id.value,
name = this.name,
tokenIconUrl = this.iconUrl,
private fun CryptoCurrencyStatus.mapToUnreachableTokenItemState() = TokenItemState.Unreachable(
id = currency.id.value,
name = currency.name,
tokenIconUrl = currency.iconUrl,
tokenIconResId = this.tokenIconResId,
networkIconResId = this.networkIconResId,
)
private fun TokenStatus.getPriceChangeConfig(): PriceChangeConfig {
private fun CryptoCurrencyStatus.getPriceChangeConfig(): PriceChangeConfig {
val priceChange = value.priceChange
?: return PriceChangeConfig(UNKNOWN_AMOUNT_SIGN, PriceChangeConfig.Type.DOWN)

View file

@ -1,8 +1,8 @@
package com.tangem.feature.wallet.presentation.wallet.utils
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.model.TokenStatus
import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTokensListState.TokensListItemState
import com.tangem.feature.wallet.presentation.wallet.utils.LoadingItemsProvider.getLoadingMultiCurrencyTokens
@ -19,7 +19,7 @@ internal class TokenListToContentItemsConverter(
private val clickCallbacks: WalletClickCallbacks,
) : Converter<TokenList, WalletTokensListState> {
private val tokenStatusConverter = TokenStatusToTokenItemConverter(
private val tokenStatusConverter = CryptoCurrencyStatusToTokenItemConverter(
isWalletContentHidden = isWalletContentHidden,
fiatCurrencyCode = fiatCurrencyCode,
fiatCurrencySymbol = fiatCurrencySymbol,
@ -47,7 +47,7 @@ internal class TokenListToContentItemsConverter(
}
private fun TokenList.Ungrouped.mapToMultiCurrencyItems(): PersistentList<TokensListItemState> {
return tokens.fold(initial = persistentListOf()) { acc, token ->
return currencies.fold(initial = persistentListOf()) { acc, token ->
acc.mutate { it.addToken(token) }
}
}
@ -55,14 +55,14 @@ internal class TokenListToContentItemsConverter(
private fun MutableList<TokensListItemState>.addGroup(group: NetworkGroup): List<TokensListItemState> {
this.add(TokensListItemState.NetworkGroupTitle(group.network.name))
group.tokens.forEach { token ->
group.currencies.forEach { token ->
this.addToken(token)
}
return this
}
private fun MutableList<TokensListItemState>.addToken(token: TokenStatus): List<TokensListItemState> {
private fun MutableList<TokensListItemState>.addToken(token: CryptoCurrencyStatus): List<TokensListItemState> {
val tokenItemState = tokenStatusConverter.convert(token)
this.add(TokensListItemState.Token(tokenItemState))

View file

@ -2,9 +2,9 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels
import com.tangem.common.Provider
import com.tangem.domain.common.CardTypesResolver
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.model.TokenStatus
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.wallet.state.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
@ -123,12 +123,12 @@ internal class WalletNotificationsListFactory(
private fun TokenList.hasMissedDerivations(): Boolean {
val statuses = when (this) {
is TokenList.GroupedByNetwork -> groups.flatMap(NetworkGroup::tokens).map(TokenStatus::value)
is TokenList.Ungrouped -> tokens.map(TokenStatus::value)
is TokenList.GroupedByNetwork -> groups.flatMap(NetworkGroup::currencies).map(CryptoCurrencyStatus::value)
is TokenList.Ungrouped -> currencies.map(CryptoCurrencyStatus::value)
TokenList.NotInitialized -> emptyList()
}
return statuses.any { it is TokenStatus.MissedDerivation }
return statuses.any { it is CryptoCurrencyStatus.MissedDerivation }
}
private companion object {