Updated on 2026-08-14
This commit is contained in:
parent
ce57171f3b
commit
ec07825276
22 changed files with 273 additions and 362 deletions
|
|
@ -1,8 +1,14 @@
|
|||
package com.tangem.utils.converter
|
||||
|
||||
interface Converter<I, O> {
|
||||
interface Converter<I : Any, O : Any?> {
|
||||
|
||||
fun convert(value: I): O
|
||||
fun convertList(input: List<I>): List<O> {
|
||||
return input.map { convert(it) }
|
||||
|
||||
fun convertList(input: Collection<I>): List<O> {
|
||||
return input.map(::convert)
|
||||
}
|
||||
|
||||
fun convertSet(input: Collection<I>): Set<O> {
|
||||
return input.mapTo(hashSetOf(), ::convert)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.utils.converter
|
||||
|
||||
interface TwoWayConverter<I, O> : Converter<I, O> {
|
||||
interface TwoWayConverter<I : Any, O> : Converter<I, O> {
|
||||
|
||||
fun convertBack(value: O): I
|
||||
fun convertListBack(input: List<O>): List<I> {
|
||||
|
||||
fun convertListBack(input: Collection<O>): List<I> {
|
||||
return input.map { convertBack(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ dependencies {
|
|||
|
||||
/** Project - Utils */
|
||||
implementation(projects.core.utils)
|
||||
// FIXME: For blockchain extensions, remove after refactoring
|
||||
implementation(projects.domain.legacy)
|
||||
|
||||
/** Tangem SDKs */
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.data.tokens.di
|
||||
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.tokens.repository.DefaultNetworksRepository
|
||||
import com.tangem.data.tokens.repository.DefaultTokensRepository
|
||||
import com.tangem.data.tokens.repository.MockNetworksRepository
|
||||
import com.tangem.data.tokens.repository.MockQuotesRepository
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.datasource.local.userwallet.UserWalletsStore
|
|||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.tokens.repository.TokensRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -39,5 +40,19 @@ internal object TokensDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNetworksRepository(): NetworksRepository = MockNetworksRepository()
|
||||
fun provideNetworksRepository(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
userTokensStore: UserTokensStore,
|
||||
cacheRegistry: CacheRegistry,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): NetworksRepository {
|
||||
return DefaultNetworksRepository(
|
||||
walletManagersFacade,
|
||||
userWalletsStore,
|
||||
userTokensStore,
|
||||
cacheRegistry,
|
||||
dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
package com.tangem.data.tokens.mock
|
||||
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
internal object MockNetworks {
|
||||
|
||||
val network1 = Network(
|
||||
id = Network.ID("network1"),
|
||||
name = "Network One",
|
||||
)
|
||||
|
||||
val network2 = Network(
|
||||
id = Network.ID("network2"),
|
||||
name = "Network Two",
|
||||
)
|
||||
|
||||
val network3 = Network(
|
||||
id = Network.ID("network3"),
|
||||
name = "Network Three",
|
||||
)
|
||||
|
||||
val networks = setOf(network1, network2, network3)
|
||||
|
||||
val networkStatus1 = NetworkStatus(
|
||||
networkId = network1.id,
|
||||
value = NetworkStatus.Verified(
|
||||
amounts = mapOf(
|
||||
MockTokens.token1.id to BigDecimal("123.1234556789"),
|
||||
MockTokens.token2.id to BigDecimal("42.2"),
|
||||
MockTokens.token3.id to BigDecimal("1000000000.5"),
|
||||
),
|
||||
hasTransactionsInProgress = false,
|
||||
),
|
||||
)
|
||||
|
||||
val networkStatus2 = NetworkStatus(
|
||||
networkId = network2.id,
|
||||
value = NetworkStatus.MissedDerivation,
|
||||
)
|
||||
|
||||
val networkStatus3 = NetworkStatus(
|
||||
networkId = network3.id,
|
||||
value = NetworkStatus.Verified(
|
||||
amounts = mapOf(
|
||||
MockTokens.token7.id to BigDecimal.ZERO,
|
||||
MockTokens.token8.id to BigDecimal.TEN,
|
||||
MockTokens.token9.id to BigDecimal.TEN,
|
||||
MockTokens.token10.id to BigDecimal.TEN,
|
||||
),
|
||||
hasTransactionsInProgress = false,
|
||||
),
|
||||
)
|
||||
|
||||
val networksStatuses = setOf(networkStatus1, networkStatus2, networkStatus3)
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
package com.tangem.data.tokens.mock
|
||||
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
internal object MockQuotes {
|
||||
|
||||
val quote1 = Quote(
|
||||
currencyId = MockTokens.token1.id,
|
||||
fiatRate = BigDecimal("1.23"),
|
||||
priceChange = BigDecimal("0.01"),
|
||||
)
|
||||
|
||||
val quote2 = Quote(
|
||||
currencyId = MockTokens.token2.id,
|
||||
fiatRate = BigDecimal("2.34"),
|
||||
priceChange = BigDecimal("-0.02"),
|
||||
)
|
||||
|
||||
val quote3 = Quote(
|
||||
currencyId = MockTokens.token3.id,
|
||||
fiatRate = BigDecimal("3.45"),
|
||||
priceChange = BigDecimal("0.03"),
|
||||
)
|
||||
|
||||
val quote4 = Quote(
|
||||
currencyId = MockTokens.token4.id,
|
||||
fiatRate = BigDecimal("4.56"),
|
||||
priceChange = BigDecimal("-0.04"),
|
||||
)
|
||||
|
||||
val quote5 = Quote(
|
||||
currencyId = MockTokens.token5.id,
|
||||
fiatRate = BigDecimal("5.67"),
|
||||
priceChange = BigDecimal("0.05"),
|
||||
)
|
||||
|
||||
val quote6 = Quote(
|
||||
currencyId = MockTokens.token6.id,
|
||||
fiatRate = BigDecimal("6.78"),
|
||||
priceChange = BigDecimal("-0.06"),
|
||||
)
|
||||
|
||||
val quote7 = Quote(
|
||||
currencyId = MockTokens.token7.id,
|
||||
fiatRate = BigDecimal("7.89"),
|
||||
priceChange = BigDecimal("0.07"),
|
||||
)
|
||||
|
||||
val quote8 = Quote(
|
||||
currencyId = MockTokens.token8.id,
|
||||
fiatRate = BigDecimal("8.90"),
|
||||
priceChange = BigDecimal("-0.08"),
|
||||
)
|
||||
|
||||
val quote9 = Quote(
|
||||
currencyId = MockTokens.token9.id,
|
||||
fiatRate = BigDecimal("9.01"),
|
||||
priceChange = BigDecimal("0.09"),
|
||||
)
|
||||
|
||||
val quote10 = Quote(
|
||||
currencyId = MockTokens.token10.id,
|
||||
fiatRate = BigDecimal("10.12"),
|
||||
priceChange = BigDecimal("-0.10"),
|
||||
)
|
||||
|
||||
val quotes = setOf(quote1, quote2, quote3, quote4, quote5, quote6, quote7, quote8, quote9, quote10)
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
package com.tangem.data.tokens.mock
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
internal object MockTokens {
|
||||
|
||||
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
|
||||
get() = mapOf(
|
||||
UserWalletId(stringValue = "123") to setOf(
|
||||
token1, token2, token3, token4, token5,
|
||||
token6, token7, token8, token9, token10,
|
||||
),
|
||||
UserWalletId(stringValue = "321") to setOf(token1, token2, token3),
|
||||
UserWalletId(stringValue = "42") to setOf(token7, token8, token9, token10),
|
||||
UserWalletId(stringValue = "24") to setOf(token4, token5, token6),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.tangem.data.tokens.repository
|
||||
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.tokens.utils.NetworkConverter
|
||||
import com.tangem.data.tokens.utils.NetworkStatusFactory
|
||||
import com.tangem.data.tokens.utils.ResponseCurrenciesFactory
|
||||
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.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class DefaultNetworksRepository(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val userTokensStore: UserTokensStore,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : NetworksRepository {
|
||||
|
||||
private val networkConverter by lazy { NetworkConverter() }
|
||||
private val responseCurrenciesFactory by lazy { ResponseCurrenciesFactory(DemoConfig()) }
|
||||
private val networkStatusFactory by lazy { NetworkStatusFactory() }
|
||||
|
||||
private val networksStatuses: MutableStateFlow<HashSet<NetworkStatus>> = MutableStateFlow(hashSetOf())
|
||||
|
||||
override fun getNetworks(networksIds: Set<Network.ID>): Set<Network> {
|
||||
return networkConverter.convertSet(networksIds)
|
||||
}
|
||||
|
||||
override fun getNetworkStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network.ID>,
|
||||
refresh: Boolean,
|
||||
): Flow<Set<NetworkStatus>> = channelFlow {
|
||||
networksStatuses.collectLatest(::send)
|
||||
|
||||
launch(dispatchers.io) {
|
||||
fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchNetworksStatusesIfCacheExpired(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network.ID>,
|
||||
refresh: Boolean,
|
||||
) {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getNetworksStatusesCacheKey(userWalletId),
|
||||
skipCache = refresh,
|
||||
block = { fetchNetworksStatuses(userWalletId, networks) },
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun fetchNetworksStatuses(userWalletId: UserWalletId, networks: Set<Network.ID>) {
|
||||
coroutineScope {
|
||||
networks
|
||||
.map { networkId ->
|
||||
async {
|
||||
fetchNetworkStatus(userWalletId, networkId)
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, networkId: Network.ID) {
|
||||
val currencies = getCurrencies(userWalletId)
|
||||
val result = walletManagersFacade.update(
|
||||
userWalletId = userWalletId,
|
||||
networkId = networkId,
|
||||
extraTokens = currencies.filterIsInstanceTo(hashSetOf()),
|
||||
)
|
||||
val networkStatus = networkStatusFactory.createNetworkStatus(networkId, result, currencies)
|
||||
|
||||
networksStatuses.update { statuses ->
|
||||
statuses.apply {
|
||||
addOrReplace(networkStatus) { it.networkId == networkStatus.networkId }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getCurrencies(userWalletId: UserWalletId): Set<CryptoCurrency> {
|
||||
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
val response = requireNotNull(userTokensStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
|
||||
return responseCurrenciesFactory.createTokens(response, userWallet.scanResponse.card)
|
||||
}
|
||||
|
||||
private fun getNetworksStatusesCacheKey(userWalletId: UserWalletId): String = "network_status_$userWalletId"
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
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.NetworkStatus
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
internal class MockNetworksRepository : NetworksRepository {
|
||||
|
||||
override fun getNetworks(networksIds: Set<Network.ID>): Set<Network> {
|
||||
return MockNetworks.networks
|
||||
.filter { it.id in networksIds }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
override fun getNetworkStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Map<Network.ID, Set<CryptoCurrency.ID>>,
|
||||
refresh: Boolean,
|
||||
): Flow<Set<NetworkStatus>> {
|
||||
return flowOf(
|
||||
MockNetworks.networksStatuses
|
||||
.filter { it.networkId in networks.keys }
|
||||
.toSet(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,23 @@
|
|||
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.repository.QuotesRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class MockQuotesRepository : QuotesRepository {
|
||||
|
||||
override fun getQuotes(tokensIds: Set<CryptoCurrency.ID>, refresh: Boolean): Flow<Set<Quote>> {
|
||||
override fun getQuotes(currenciesIds: Set<CryptoCurrency.ID>, refresh: Boolean): Flow<Set<Quote>> {
|
||||
return flowOf(
|
||||
MockQuotes.quotes
|
||||
.filter { it.currencyId in tokensIds }
|
||||
.toSet(),
|
||||
currenciesIds.map {
|
||||
Quote(
|
||||
currencyId = it,
|
||||
fiatRate = BigDecimal.ZERO,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
)
|
||||
}.toSet(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.data.tokens.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import com.tangem.utils.converter.Converter
|
||||
import timber.log.Timber
|
||||
|
||||
internal class NetworkConverter : Converter<Network.ID, Network?> {
|
||||
|
||||
override fun convert(value: Network.ID): Network? {
|
||||
val blockchain = Blockchain.fromId(value.value)
|
||||
|
||||
if (blockchain == Blockchain.Unknown) {
|
||||
Timber.e("Unable to convert Unknown blockchain to the domain network model")
|
||||
return null
|
||||
}
|
||||
|
||||
return Network(
|
||||
id = value,
|
||||
name = blockchain.fullName,
|
||||
)
|
||||
}
|
||||
|
||||
override fun convertList(input: Collection<Network.ID>): List<Network> {
|
||||
return input.mapNotNull(::convert)
|
||||
}
|
||||
|
||||
override fun convertSet(input: Collection<Network.ID>): Set<Network> {
|
||||
return input.mapNotNullTo(hashSetOf(), ::convert)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.tangem.data.tokens.utils
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
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 {
|
||||
|
||||
fun createNetworkStatus(
|
||||
networkId: Network.ID,
|
||||
result: UpdateWalletManagerResult,
|
||||
currencies: Set<CryptoCurrency>,
|
||||
): NetworkStatus {
|
||||
return NetworkStatus(
|
||||
networkId = networkId,
|
||||
value = when (result) {
|
||||
is UpdateWalletManagerResult.MissedDerivation -> NetworkStatus.MissedDerivation
|
||||
is UpdateWalletManagerResult.Unreachable -> NetworkStatus.Unreachable
|
||||
is UpdateWalletManagerResult.NoAccount -> NetworkStatus.NoAccount(result.amountToCreateAccount)
|
||||
is UpdateWalletManagerResult.Verified -> NetworkStatus.Verified(
|
||||
amounts = formatAmounts(result.tokensAmounts, currencies),
|
||||
hasTransactionsInProgress = result.hasTransactionsInProgress,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatAmounts(
|
||||
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) &&
|
||||
it.tokenContractAddress == currency.contractAddress
|
||||
}
|
||||
}?.value
|
||||
|
||||
if (amount == null) {
|
||||
Timber.e("Unable to find a token amount for: ${currency.name}")
|
||||
} else {
|
||||
formattedAmounts[currency.id] = amount
|
||||
}
|
||||
}
|
||||
|
||||
return formattedAmounts
|
||||
}
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ internal fun getTokenId(blockchain: Blockchain, token: SdkToken): CryptoCurrency
|
|||
return getTokenOrCoinId(blockchain, token)
|
||||
}
|
||||
|
||||
internal fun getResponseTokenId(currency: CryptoCurrency): String? {
|
||||
internal fun getTokenIdString(currency: CryptoCurrency): String? {
|
||||
return currency.id.value.substringAfter(TOKEN_ID_DELIMITER)
|
||||
.takeUnless { currency is CryptoCurrency.Token && currency.isCustom }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ internal class UserTokensResponseFactory {
|
|||
val blockchain = getBlockchain(currency.networkId)
|
||||
|
||||
return UserTokensResponse.Token(
|
||||
id = getResponseTokenId(currency),
|
||||
id = getTokenIdString(currency),
|
||||
networkId = blockchain.toNetworkId(),
|
||||
derivationPath = currency.derivationPath,
|
||||
name = currency.name,
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class DefaultWalletManagersFacade(
|
|||
|
||||
return try {
|
||||
if (demoConfig.isDemoCardId(userWallet.scanResponse.card.cardId)) {
|
||||
updateDemoWalletManager(walletManager, extraTokens)
|
||||
updateDemoWalletManager(walletManager)
|
||||
} else {
|
||||
updateWalletManager(walletManager)
|
||||
}
|
||||
|
|
@ -95,14 +95,11 @@ class DefaultWalletManagersFacade(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateDemoWalletManager(
|
||||
walletManager: WalletManager,
|
||||
tokens: Set<CryptoCurrency.Token>,
|
||||
): UpdateWalletManagerResult {
|
||||
private fun updateDemoWalletManager(walletManager: WalletManager): UpdateWalletManagerResult {
|
||||
val amount = demoConfig.getBalance(walletManager.wallet.blockchain)
|
||||
walletManager.wallet.setAmount(amount)
|
||||
|
||||
return resultFactory.getDemoResult(amount, tokens)
|
||||
return resultFactory.getDemoResult(walletManager, amount)
|
||||
}
|
||||
|
||||
private suspend fun updateWalletManager(walletManager: WalletManager): UpdateWalletManagerResult {
|
||||
|
|
@ -149,7 +146,7 @@ class DefaultWalletManagersFacade(
|
|||
if (tokens.isEmpty()) return
|
||||
|
||||
val tokensToAdd = sdkTokenConverter
|
||||
.convertList(tokens.toList())
|
||||
.convertList(tokens)
|
||||
.filter { it !in walletManager.cardTokens }
|
||||
|
||||
walletManager.addTokens(tokensToAdd)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ sealed class CryptoCurrencyAmount {
|
|||
data class Coin(override val value: BigDecimal) : CryptoCurrencyAmount()
|
||||
|
||||
data class Token(
|
||||
val id: String?,
|
||||
val tokenContractAddress: String,
|
||||
override val value: BigDecimal,
|
||||
) : CryptoCurrencyAmount()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
package com.tangem.domain.walletmanager.utils
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.TransactionStatus
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.domain.common.extensions.amountToCreateAccount
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount
|
||||
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
|
||||
import timber.log.Timber
|
||||
|
|
@ -26,9 +22,9 @@ internal class UpdateWalletManagerResultFactory {
|
|||
)
|
||||
}
|
||||
|
||||
fun getDemoResult(demoAmount: Amount, tokens: Set<CryptoCurrency.Token>): UpdateWalletManagerResult.Verified {
|
||||
fun getDemoResult(walletManager: WalletManager, demoAmount: Amount): UpdateWalletManagerResult.Verified {
|
||||
return UpdateWalletManagerResult.Verified(
|
||||
tokensAmounts = getDemoTokensAmounts(demoAmount, tokens),
|
||||
tokensAmounts = getDemoTokensAmounts(demoAmount, walletManager.cardTokens),
|
||||
hasTransactionsInProgress = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -53,18 +49,19 @@ internal class UpdateWalletManagerResultFactory {
|
|||
return amounts.mapNotNullTo(mutableAmounts, ::getTokenAmount)
|
||||
}
|
||||
|
||||
private fun getDemoTokensAmounts(demoAmount: Amount, tokens: Set<CryptoCurrency.Token>): Set<CryptoCurrencyAmount> {
|
||||
private fun getDemoTokensAmounts(demoAmount: Amount, tokens: Set<Token>): Set<CryptoCurrencyAmount> {
|
||||
val amountValue = demoAmount.value ?: BigDecimal.ZERO
|
||||
val demoAmounts = hashSetOf<CryptoCurrencyAmount>(CryptoCurrencyAmount.Coin(amountValue))
|
||||
|
||||
return tokens.mapTo(demoAmounts) { token ->
|
||||
CryptoCurrencyAmount.Token(token.contractAddress, amountValue)
|
||||
CryptoCurrencyAmount.Token(token.id, token.contractAddress, amountValue)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTokenAmount(amount: Amount): CryptoCurrencyAmount? {
|
||||
return when (val type = amount.type) {
|
||||
is AmountType.Token -> CryptoCurrencyAmount.Token(
|
||||
id = type.token.id,
|
||||
tokenContractAddress = type.token.contractAddress,
|
||||
value = getAmountValue(amount) ?: return null,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -46,36 +46,43 @@ internal class CurrenciesStatusesOperations<E>(
|
|||
|
||||
fun getMultiCurrencyWalletStatusesFlow(): Flow<Set<CryptoCurrencyStatus>> {
|
||||
return getMultiCurrencyWalletCurrencies().flatMapConcat {
|
||||
val tokens = it.toNonEmptySetOrNull()
|
||||
val currencies = it.toNonEmptySetOrNull()
|
||||
|
||||
if (tokens == null) {
|
||||
if (currencies == null) {
|
||||
flowOf(emptySet())
|
||||
} else {
|
||||
val tokensIds = tokens.map { token -> token.id }.toNonEmptySet()
|
||||
val groupedTokens = groupTokens(tokens)
|
||||
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"
|
||||
}
|
||||
|
||||
combine(getQuotes(tokensIds), getNetworksStatues(groupedTokens)) { quotes, networksStatuses ->
|
||||
createTokensStatuses(tokens, quotes, networksStatuses)
|
||||
combine(getQuotes(currenciesIds), getNetworksStatues(networksIds)) { quotes, networksStatuses ->
|
||||
createTokensStatuses(currencies, quotes, networksStatuses)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getPrimaryCurrencyStatusFlow(): Flow<CryptoCurrencyStatus> {
|
||||
val token = getPrimaryCurrency()
|
||||
val currency = getPrimaryCurrency()
|
||||
|
||||
val quoteFlow = getQuotes(nonEmptySetOf(token.id))
|
||||
val quoteFlow = getQuotes(nonEmptySetOf(currency.id))
|
||||
.map { quotes ->
|
||||
quotes.singleOrNull { it.currencyId == token.id }
|
||||
quotes.singleOrNull { it.currencyId == currency.id }
|
||||
}
|
||||
|
||||
val statusFlow = getNetworksStatues(groupTokens(nonEmptySetOf(token)))
|
||||
val statusFlow = getNetworksStatues(nonEmptySetOf(currency.networkId))
|
||||
.map { statuses ->
|
||||
statuses.singleOrNull { it.networkId == token.networkId }
|
||||
statuses.singleOrNull { it.networkId == currency.networkId }
|
||||
}
|
||||
|
||||
return combine(quoteFlow, statusFlow) { quote, networkStatus ->
|
||||
createStatus(token, quote, networkStatus)
|
||||
createStatus(currency, quote, networkStatus)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,30 +139,13 @@ internal class CurrenciesStatusesOperations<E>(
|
|||
.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
private fun getNetworksStatues(
|
||||
groupedTokens: Map<Network.ID, NonEmptySet<CryptoCurrency.ID>>,
|
||||
): Flow<Set<NetworkStatus>> {
|
||||
return networksRepository.getNetworkStatuses(userWalletId, groupedTokens, refresh)
|
||||
private fun getNetworksStatues(networks: NonEmptySet<Network.ID>): Flow<Set<NetworkStatus>> {
|
||||
return networksRepository.getNetworkStatuses(userWalletId, networks, refresh)
|
||||
.catch { raise(Error.DataError(it)) }
|
||||
.onEmpty { raise(Error.EmptyNetworksStatuses) }
|
||||
.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
private suspend fun groupTokens(
|
||||
tokens: NonEmptySet<CryptoCurrency>,
|
||||
): Map<Network.ID, NonEmptySet<CryptoCurrency.ID>> {
|
||||
return withContext(dispatchers.default) {
|
||||
tokens
|
||||
.groupBy { it.networkId }
|
||||
.mapValues { (_, tokens) ->
|
||||
// Can not be empty
|
||||
tokens.toNonEmptySetOrNull()!!
|
||||
.map { it.id }
|
||||
.toNonEmptySet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Error {
|
||||
|
||||
object EmptyCurrencies : Error()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.domain.tokens.repository
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -23,13 +22,13 @@ interface NetworksRepository {
|
|||
* Retrieves the statuses of specified blockchain networks for a specific user wallet.
|
||||
*
|
||||
* @param userWalletId The unique identifier of the user wallet.
|
||||
* @param networks A map of network IDs to sets of cryptocurrency IDs, representing the networks for which statuses are to be retrieved.
|
||||
* @param networks A set of network IDs which statuses are to be retrieved.
|
||||
* @param refresh A boolean flag indicating whether the data should be refreshed.
|
||||
* @return A [Flow] emitting a set of [NetworkStatus] objects corresponding to the specified networks.
|
||||
*/
|
||||
fun getNetworkStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Map<Network.ID, Set<CryptoCurrency.ID>>,
|
||||
networks: Set<Network.ID>,
|
||||
refresh: Boolean,
|
||||
): Flow<Set<NetworkStatus>>
|
||||
}
|
||||
|
|
@ -12,9 +12,9 @@ interface QuotesRepository {
|
|||
/**
|
||||
* Retrieves the quotes for a set of specified cryptocurrencies, identified by their unique IDs.
|
||||
*
|
||||
* @param tokensIds The unique identifiers of the cryptocurrencies for which quotes are to be retrieved.
|
||||
* @param currenciesIds The unique identifiers of the cryptocurrencies for which quotes are to be retrieved.
|
||||
* @param refresh A boolean flag indicating whether the data should be refreshed.
|
||||
* @return A [Flow] emitting a set of quotes corresponding to the specified cryptocurrencies.
|
||||
*/
|
||||
fun getQuotes(tokensIds: Set<CryptoCurrency.ID>, refresh: Boolean): Flow<Set<Quote>>
|
||||
fun getQuotes(currenciesIds: Set<CryptoCurrency.ID>, refresh: Boolean): Flow<Set<Quote>>
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ 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.NetworkStatus
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -21,7 +20,7 @@ internal class MockNetworksRepository(
|
|||
|
||||
override fun getNetworkStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Map<Network.ID, Set<CryptoCurrency.ID>>,
|
||||
networks: Set<Network.ID>,
|
||||
refresh: Boolean,
|
||||
): Flow<Set<NetworkStatus>> {
|
||||
return statuses.map { it.getOrElse { e -> throw e } }
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ internal class MockQuotesRepository(
|
|||
private val quotes: Flow<Either<DataError, Set<Quote>>>,
|
||||
) : QuotesRepository {
|
||||
|
||||
override fun getQuotes(tokensIds: Set<CryptoCurrency.ID>, refresh: Boolean): Flow<Set<Quote>> {
|
||||
override fun getQuotes(currenciesIds: Set<CryptoCurrency.ID>, refresh: Boolean): Flow<Set<Quote>> {
|
||||
return quotes.map { it.getOrElse { e -> throw e } }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue