Updated on 2026-08-14
This commit is contained in:
parent
bd699c5bd9
commit
ef5d0fec78
40 changed files with 600 additions and 642 deletions
|
|
@ -6,12 +6,15 @@ import arrow.core.raise.ensure
|
|||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
typealias AccountCurrencyId = Pair<AccountId, CryptoCurrency.ID>
|
||||
|
||||
/**
|
||||
* Represents a list of accounts associated with a user wallet ID.
|
||||
*
|
||||
|
|
@ -104,6 +107,19 @@ data class AccountList private constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun flattenMapCurrencies(): Map<AccountCurrencyId, CryptoCurrency> = buildMap {
|
||||
accounts.forEach { acc ->
|
||||
val account = when (acc) {
|
||||
is Account.CryptoPortfolio -> acc
|
||||
is Account.Payment -> TODO("[REDACTED_JIRA]")
|
||||
}
|
||||
account.cryptoCurrencies.forEach { currency ->
|
||||
val key = account.accountId to currency.id
|
||||
put(key, currency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents possible errors that can occur when creating an `AccountList`
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
package com.tangem.domain.account.status.di
|
||||
|
||||
import com.tangem.domain.account.status.producer.DefaultMultiAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.producer.DefaultSingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.producer.MultiAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.producer.*
|
||||
import com.tangem.domain.core.flow.FlowProducerScope
|
||||
import com.tangem.domain.core.flow.FlowProducerTools
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -25,4 +24,12 @@ internal interface AccountStatusListProducerFactoryModule {
|
|||
fun bindMultiAccountStatusListProducerFactory(
|
||||
factory: DefaultMultiAccountStatusListProducer.Factory,
|
||||
): MultiAccountStatusListProducer.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindDefaultFlowProducerTools(impl: DefaultFlowProducerTools): FlowProducerTools
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindFlowProducerScope(impl: DefaultFlowProducerAppScope): FlowProducerScope
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package com.tangem.domain.account.status.producer
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.domain.core.flow.FlowProducer
|
||||
import com.tangem.domain.core.flow.FlowProducerScope
|
||||
import com.tangem.domain.core.flow.FlowProducerTools
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class DefaultFlowProducerAppScope @Inject constructor(
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
) : FlowProducerScope {
|
||||
|
||||
private val tag = "FlowProducerScope"
|
||||
|
||||
override val coroutineContext: CoroutineContext = SupervisorJob() +
|
||||
dispatchers.default +
|
||||
CoroutineName(tag) +
|
||||
CoroutineExceptionHandler { context, throwable ->
|
||||
@Suppress("NullableToStringCall")
|
||||
val coroutineName = context[CoroutineName]?.name.toString()
|
||||
logError(throwable, coroutineName)
|
||||
}
|
||||
|
||||
private fun logError(throwable: Throwable, coroutineName: String) {
|
||||
Timber.tag("FlowProducerExceptionHandler").e(
|
||||
throwable,
|
||||
"CoroutineName $coroutineName",
|
||||
)
|
||||
val event = ExceptionAnalyticsEvent(
|
||||
exception = throwable,
|
||||
params = mapOf(
|
||||
"source" to tag,
|
||||
"coroutineName" to coroutineName,
|
||||
),
|
||||
)
|
||||
analyticsExceptionHandler.sendException(event)
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultFlowProducerTools @Inject constructor(
|
||||
private val scope: FlowProducerScope,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
) : FlowProducerTools {
|
||||
|
||||
override fun <T> shareInProducer(
|
||||
flow: Flow<T>,
|
||||
flowProducer: FlowProducer<T>,
|
||||
withRetryWhen: Boolean,
|
||||
): SharedFlow<T> {
|
||||
var upstream = flow
|
||||
|
||||
if (withRetryWhen) {
|
||||
val flowProducerName = flowProducer.javaClass.simpleName
|
||||
upstream = upstream
|
||||
.retryWhen { cause: Throwable, attempt: Long ->
|
||||
logError(cause, flowProducerName, attempt)
|
||||
flowProducer.fallback.onSome { this.emit(value = it) }
|
||||
delay(timeMillis = 2000)
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
return upstream
|
||||
.distinctUntilChanged()
|
||||
.shareIn(
|
||||
scope = scope,
|
||||
replay = 1,
|
||||
// params control flow cleanup
|
||||
started = SharingStarted.WhileSubscribed(
|
||||
stopTimeoutMillis = 5_000,
|
||||
replayExpirationMillis = 30_000,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun logError(cause: Throwable, flowProducerName: String, attempt: Long) {
|
||||
val tag = "FlowProducerRetryWhen"
|
||||
Timber.tag(tag)
|
||||
.e(cause, "flowProducerName $flowProducerName attempt $attempt")
|
||||
|
||||
val event = ExceptionAnalyticsEvent(
|
||||
exception = cause,
|
||||
params = mapOf(
|
||||
"source" to tag,
|
||||
"flowProducerName" to flowProducerName,
|
||||
),
|
||||
)
|
||||
analyticsExceptionHandler.sendException(event)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import arrow.core.some
|
|||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.core.flow.FlowProducerTools
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -27,6 +28,7 @@ import kotlinx.coroutines.flow.flowOn
|
|||
*/
|
||||
internal class DefaultMultiAccountStatusListProducer @AssistedInject constructor(
|
||||
@Assisted val params: Unit,
|
||||
override val flowProducerTools: FlowProducerTools,
|
||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
|
|||
|
|
@ -2,24 +2,42 @@ package com.tangem.domain.account.status.producer
|
|||
|
||||
import arrow.core.Option
|
||||
import arrow.core.none
|
||||
import arrow.core.toOption
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.domain.account.models.AccountCurrencyId
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusesFlowFactory
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.core.flow.FlowProducerTools
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.network.getAddress
|
||||
import com.tangem.domain.models.quote.PriceChange
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusProducer
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
||||
import com.tangem.domain.networks.repository.NetworksRepository
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusSupplier
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
|
||||
import com.tangem.domain.staking.multi.MultiStakingBalanceSupplier
|
||||
import com.tangem.domain.staking.single.SingleStakingBalanceProducer.Companion.selectStakingBalance
|
||||
import com.tangem.domain.tokens.operations.CryptoCurrencyStatusFactory
|
||||
import com.tangem.domain.tokens.operations.PriceChangeCalculator
|
||||
import com.tangem.domain.tokens.operations.TokenListFactory
|
||||
import com.tangem.domain.tokens.operations.TotalFiatBalanceCalculator
|
||||
|
|
@ -28,10 +46,10 @@ import dagger.assisted.Assisted
|
|||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.channels.ProducerScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.math.BigDecimal
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* Produces a flow of [AccountStatusList] for a single user wallet.
|
||||
|
|
@ -44,117 +62,229 @@ import kotlin.time.Duration.Companion.milliseconds
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class DefaultSingleAccountStatusListProducer @AssistedInject constructor(
|
||||
@Assisted private val params: SingleAccountStatusListProducer.Params,
|
||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val cryptoCurrencyStatusesFlowFactory: CryptoCurrencyStatusesFlowFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
override val flowProducerTools: FlowProducerTools,
|
||||
private val networkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
private val quoteStatusSupplier: MultiQuoteStatusSupplier,
|
||||
private val stakingBalanceSupplier: MultiStakingBalanceSupplier,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
) : SingleAccountStatusListProducer {
|
||||
|
||||
override val fallback: Option<AccountStatusList> = none()
|
||||
|
||||
override fun produce(): Flow<AccountStatusList> {
|
||||
return singleAccountListSupplier(userWalletId = params.userWalletId).flatMapLatest { accountList ->
|
||||
val accountStatusFlows = createAccountStatusFlows(accountList)
|
||||
return flattenFlow()
|
||||
.flowOn(dispatchers.default)
|
||||
}
|
||||
|
||||
combine(accountStatusFlows) { accountStatuses ->
|
||||
@Suppress("LongMethod")
|
||||
private fun flattenFlow(): Flow<AccountStatusList> = channelFlow {
|
||||
val walletId = params.userWalletId
|
||||
val userWallet = accountsCRUDRepository.getUserWallet(userWalletId = params.userWalletId)
|
||||
|
||||
val flattenCurrency: MutableSharedFlow<Map<AccountCurrencyId, CryptoCurrency>> = MutableSharedFlow(
|
||||
replay = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
)
|
||||
|
||||
val accountListFlow: StateFlow<AccountList> = singleAccountListSupplier(walletId)
|
||||
.onEach { accountList -> flattenCurrency.tryEmit(accountList.flattenMapCurrencies()) }
|
||||
.stateIn(this)
|
||||
|
||||
val hasCachedNetworks = networksRepository.hasCachedStatuses(walletId)
|
||||
if (!hasCachedNetworks) {
|
||||
send(createLoadingAccountStatusList(accountListFlow.value))
|
||||
}
|
||||
|
||||
val cryptoCurrencyStatusFlow: Flow<Map<AccountCurrencyId, CryptoCurrencyStatus>> = flattenCurrencyStatusFlow(
|
||||
userWallet = userWallet,
|
||||
flattenCurrency = flattenCurrency,
|
||||
)
|
||||
|
||||
combine(
|
||||
flow = accountListFlow,
|
||||
flow2 = cryptoCurrencyStatusFlow,
|
||||
transform = { accountList, currencyStatusMap ->
|
||||
val accountStatuses: List<AccountStatus.CryptoPortfolio> = accountList.accounts.map { acc ->
|
||||
val account: Account.CryptoPortfolio = when (acc) {
|
||||
is Account.CryptoPortfolio -> acc
|
||||
is Account.Payment -> TODO("[REDACTED_JIRA]")
|
||||
}
|
||||
if (account.cryptoCurrencies.isEmpty()) {
|
||||
account.toEmptyAccountStatus()
|
||||
} else {
|
||||
val statuses: List<CryptoCurrencyStatus> = account.cryptoCurrencies.map { currency ->
|
||||
val acId = account.accountId to currency.id
|
||||
currencyStatusMap[acId] ?: currency.toLoadingCurrencyStatus()
|
||||
}
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = account,
|
||||
tokenList = TokenListFactory.create(
|
||||
statuses = statuses,
|
||||
groupType = accountList.groupType,
|
||||
sortType = accountList.sortType,
|
||||
),
|
||||
priceChangeLce = PriceChangeCalculator.calculate(statuses = statuses),
|
||||
)
|
||||
}
|
||||
}
|
||||
val balances = accountStatuses.flattenTotalFiatBalance()
|
||||
|
||||
AccountStatusList(
|
||||
userWalletId = accountList.userWalletId,
|
||||
accountStatuses = accountStatuses.toList(),
|
||||
accountStatuses = accountStatuses,
|
||||
totalAccounts = accountList.totalAccounts,
|
||||
totalFiatBalance = TotalFiatBalanceCalculator.calculate(balances),
|
||||
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||
sortType = accountList.sortType,
|
||||
groupType = accountList.groupType,
|
||||
)
|
||||
}
|
||||
.onStartCheckCachedNetworks(accountList)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.flowOn(dispatchers.default)
|
||||
}
|
||||
|
||||
private fun createAccountStatusFlows(accountList: AccountList): List<Flow<AccountStatus>> {
|
||||
return accountList.accounts.map { account ->
|
||||
when (account) {
|
||||
is Account.CryptoPortfolio -> {
|
||||
if (account.cryptoCurrencies.isEmpty()) {
|
||||
createEmptyAccountStatusFlow(account)
|
||||
} else {
|
||||
val userWallet = accountsCRUDRepository.getUserWallet(userWalletId = params.userWalletId)
|
||||
|
||||
getAccountStatusFlow(
|
||||
userWallet = userWallet,
|
||||
account = account,
|
||||
groupType = accountList.groupType,
|
||||
sortType = accountList.sortType,
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
is Account.Payment -> TODO("[REDACTED_JIRA]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEmptyAccountStatusFlow(account: Account.CryptoPortfolio): Flow<AccountStatus.CryptoPortfolio> {
|
||||
return flowOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = account,
|
||||
tokenList = TokenList.Empty,
|
||||
priceChangeLce = PriceChange(
|
||||
value = BigDecimal.ZERO.movePointLeft(2),
|
||||
source = StatusSource.ACTUAL,
|
||||
).lceContent(),
|
||||
),
|
||||
},
|
||||
)
|
||||
.collect { accountStatusList -> channel.send(accountStatusList) }
|
||||
}
|
||||
|
||||
private fun getAccountStatusFlow(
|
||||
private fun ProducerScope<AccountStatusList>.flattenCurrencyStatusFlow(
|
||||
userWallet: UserWallet,
|
||||
account: Account.CryptoPortfolio,
|
||||
groupType: TokensGroupType,
|
||||
sortType: TokensSortType,
|
||||
): Flow<AccountStatus.CryptoPortfolio> {
|
||||
val statusesFlows = getCryptoCurrencyStatusesFlow(userWallet, account)
|
||||
flattenCurrency: MutableSharedFlow<Map<AccountCurrencyId, CryptoCurrency>>,
|
||||
): Flow<Map<AccountCurrencyId, CryptoCurrencyStatus>> {
|
||||
val walletId = userWallet.walletId
|
||||
val networkStatusFlow: SharedFlow<Map<Network.ID, NetworkStatus>> = networkStatusFlow(walletId, flattenCurrency)
|
||||
.shareIn(this, started = SharingStarted.Eagerly, replay = 1)
|
||||
val stakingBalanceFlow: SharedFlow<Map<StakingID, Set<StakingBalance>>> = stakingFlow(userWallet)
|
||||
.shareIn(this, started = SharingStarted.Eagerly, replay = 1)
|
||||
val quoteStatusFlow: SharedFlow<Map<CryptoCurrency.RawID, QuoteStatus>> = quoteStatusFlow()
|
||||
.shareIn(this, started = SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
return statusesFlows
|
||||
.map { statusList ->
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = account,
|
||||
tokenList = TokenListFactory.create(
|
||||
statuses = statusList,
|
||||
groupType = groupType,
|
||||
sortType = sortType,
|
||||
),
|
||||
priceChangeLce = PriceChangeCalculator.calculate(statuses = statusList),
|
||||
return flattenCurrency
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { a ->
|
||||
combine(
|
||||
flow = networkStatusFlow,
|
||||
flow2 = stakingBalanceFlow,
|
||||
flow3 = quoteStatusFlow,
|
||||
transform = { b, c, d -> Box(
|
||||
flattenCurrencyMap = a,
|
||||
networkStatusMap = b,
|
||||
stakingBalanceMap = c,
|
||||
quoteStatusMap = d,
|
||||
) },
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.map { box ->
|
||||
val flattenCurrencyMap: Map<AccountCurrencyId, CryptoCurrency> = box.flattenCurrencyMap
|
||||
val networkStatusMap: Map<Network.ID, NetworkStatus> = box.networkStatusMap
|
||||
val stakingBalanceMap: Map<StakingID, Set<StakingBalance>> = box.stakingBalanceMap
|
||||
val quoteStatusMap: Map<CryptoCurrency.RawID, QuoteStatus> = box.quoteStatusMap
|
||||
|
||||
flattenCurrencyMap.mapValues { (acId: AccountCurrencyId, currency) ->
|
||||
val (_, id) = acId
|
||||
val networkStatus: NetworkStatus? = networkStatusMap[currency.network.id]
|
||||
val quoteStatus: QuoteStatus? = id.rawCurrencyId?.let { rawID -> quoteStatusMap[rawID] }
|
||||
val stakingBalance: StakingBalance? = findStakingBalance(
|
||||
networkStatus = networkStatus,
|
||||
id = id,
|
||||
wallet = userWallet,
|
||||
stakingBalanceMap = stakingBalanceMap,
|
||||
)
|
||||
CryptoCurrencyStatusFactory.create(
|
||||
currency = currency,
|
||||
maybeNetworkStatus = networkStatus.toOption(),
|
||||
maybeQuoteStatus = quoteStatus.toOption(),
|
||||
maybeStakingBalance = stakingBalance.toOption(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private fun getCryptoCurrencyStatusesFlow(
|
||||
userWallet: UserWallet,
|
||||
account: Account.CryptoPortfolio,
|
||||
): Flow<List<CryptoCurrencyStatus>> {
|
||||
val statusesFlows = account.cryptoCurrencies.map { currency ->
|
||||
cryptoCurrencyStatusesFlowFactory.create(userWallet = userWallet, currency = currency)
|
||||
.onStart { emit(CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading)) }
|
||||
private fun networkStatusFlow(
|
||||
walletId: UserWalletId,
|
||||
flattenCurrency: MutableSharedFlow<Map<AccountCurrencyId, CryptoCurrency>>,
|
||||
): Flow<Map<Network.ID, NetworkStatus>> = channelFlow {
|
||||
val currencyCount = flattenCurrency
|
||||
.map { map -> map.size }
|
||||
.stateIn(this, SharingStarted.Eagerly, 0)
|
||||
|
||||
networkStatusSupplier(MultiNetworkStatusProducer.Params(walletId))
|
||||
// todo accounts high frequency, investigate better debounce
|
||||
.debounce {
|
||||
val count = currencyCount.value
|
||||
@Suppress("MagicNumber") when {
|
||||
count in 10..25 -> 50L
|
||||
count > 25 -> 100L
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
.mapLatest { statuses -> statuses.associateBy { status -> status.network.id } }
|
||||
.distinctUntilChanged().collect { result -> channel.send(result) }
|
||||
}
|
||||
|
||||
private fun stakingFlow(wallet: UserWallet): Flow<Map<StakingID, Set<StakingBalance>>> =
|
||||
if (!wallet.isMultiCurrency) {
|
||||
flowOf(emptyMap())
|
||||
} else {
|
||||
stakingBalanceSupplier(MultiStakingBalanceProducer.Params(wallet.walletId))
|
||||
.map { balances ->
|
||||
val result = mutableMapOf<StakingID, MutableSet<StakingBalance>>()
|
||||
balances.forEach { balance ->
|
||||
val set = result[balance.stakingId] ?: mutableSetOf()
|
||||
set.add(balance)
|
||||
result[balance.stakingId] = set
|
||||
}
|
||||
result
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return combine(statusesFlows) { it.toList() }
|
||||
.distinctUntilChanged()
|
||||
.debounce(50.milliseconds)
|
||||
private fun quoteStatusFlow(): Flow<Map<CryptoCurrency.RawID, QuoteStatus>> = quoteStatusSupplier(Unit)
|
||||
.distinctUntilChanged()
|
||||
|
||||
private fun findStakingBalance(
|
||||
networkStatus: NetworkStatus?,
|
||||
id: CryptoCurrency.ID,
|
||||
wallet: UserWallet,
|
||||
stakingBalanceMap: Map<StakingID, Set<StakingBalance>>,
|
||||
): StakingBalance? {
|
||||
if (!wallet.isMultiCurrency) {
|
||||
return null
|
||||
}
|
||||
val stakingId = stakingIdFactory.create(
|
||||
currencyId = id,
|
||||
defaultAddress = networkStatus.getAddress(),
|
||||
).getOrNull() ?: return null
|
||||
val stakingBalance = stakingBalanceMap[stakingId] ?: return null
|
||||
|
||||
return selectStakingBalance(
|
||||
currentStakingId = stakingId,
|
||||
currentBalances = stakingBalance.toList(),
|
||||
analyticsExceptionHandler = analyticsExceptionHandler,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Array<AccountStatus>.flattenTotalFiatBalance(): List<TotalFiatBalance> {
|
||||
private fun Account.CryptoPortfolio.toEmptyAccountStatus() = AccountStatus.CryptoPortfolio(
|
||||
account = this,
|
||||
tokenList = TokenList.Empty,
|
||||
priceChangeLce = PriceChange(
|
||||
value = BigDecimal.ZERO.movePointLeft(2),
|
||||
source = StatusSource.ACTUAL,
|
||||
).lceContent(),
|
||||
)
|
||||
|
||||
private fun CryptoCurrency.toLoadingCurrencyStatus() = CryptoCurrencyStatus(
|
||||
currency = this,
|
||||
value = CryptoCurrencyStatus.Loading,
|
||||
)
|
||||
|
||||
private fun List<AccountStatus>.flattenTotalFiatBalance(): List<TotalFiatBalance> {
|
||||
return map { accountStatus ->
|
||||
when (accountStatus) {
|
||||
is AccountStatus.CryptoPortfolio -> accountStatus.tokenList.totalFiatBalance
|
||||
|
|
@ -162,17 +292,6 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
|||
}
|
||||
}
|
||||
|
||||
private fun Flow<AccountStatusList>.onStartCheckCachedNetworks(accountList: AccountList): Flow<AccountStatusList> {
|
||||
return onStart {
|
||||
val hasCachedNetworks = networksRepository.hasCachedStatuses(userWalletId = accountList.userWalletId)
|
||||
|
||||
if (hasCachedNetworks) return@onStart
|
||||
|
||||
val loading = createLoadingAccountStatusList(accountList)
|
||||
emit(loading)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLoadingAccountStatusList(accountList: AccountList): AccountStatusList {
|
||||
return AccountStatusList(
|
||||
userWalletId = accountList.userWalletId,
|
||||
|
|
@ -204,6 +323,13 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
|||
)
|
||||
}
|
||||
|
||||
private data class Box(
|
||||
val flattenCurrencyMap: Map<AccountCurrencyId, CryptoCurrency>,
|
||||
val networkStatusMap: Map<Network.ID, NetworkStatus>,
|
||||
val stakingBalanceMap: Map<StakingID, Set<StakingBalance>>,
|
||||
val quoteStatusMap: Map<CryptoCurrency.RawID, QuoteStatus>,
|
||||
)
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : SingleAccountStatusListProducer.Factory {
|
||||
override fun create(params: SingleAccountStatusListProducer.Params): DefaultSingleAccountStatusListProducer
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.domain.account.status.producer
|
|||
import arrow.core.Option
|
||||
import arrow.core.none
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.core.flow.FlowProducerTools
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -21,6 +22,7 @@ import kotlinx.coroutines.flow.mapNotNull
|
|||
*/
|
||||
internal class DefaultSingleAccountStatusProducer @AssistedInject constructor(
|
||||
@Assisted val params: SingleAccountStatusProducer.Params,
|
||||
override val flowProducerTools: FlowProducerTools,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SingleAccountStatusProducer {
|
||||
|
|
|
|||
|
|
@ -1,165 +0,0 @@
|
|||
package com.tangem.domain.account.status.utils
|
||||
|
||||
import arrow.core.toOption
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.network.getAddress
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusProducer
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusProducer
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.single.SingleStakingBalanceProducer
|
||||
import com.tangem.domain.staking.single.SingleStakingBalanceSupplier
|
||||
import com.tangem.domain.tokens.operations.CryptoCurrencyStatusFactory
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Factory that creates a flow of [CryptoCurrencyStatus] for a given [CryptoCurrency] in a [UserWallet].
|
||||
*
|
||||
* @property singleNetworkStatusSupplier Supplier for obtaining network status.
|
||||
* @property singleQuoteStatusSupplier Supplier for obtaining quote status.
|
||||
* @property singleStakingBalanceSupplier Supplier for obtaining staking balance.
|
||||
* @property stakingIdFactory Factory for creating staking IDs.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class CryptoCurrencyStatusesFlowFactory @Inject constructor(
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||
private val singleStakingBalanceSupplier: SingleStakingBalanceSupplier,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Creates a flow of [CryptoCurrencyStatus] for the specified [userWallet] and [currency].
|
||||
*
|
||||
* @param userWallet The user wallet containing the currency.
|
||||
|
||||
*/
|
||||
fun create(userWallet: UserWallet, currency: CryptoCurrency): Flow<CryptoCurrencyStatus> {
|
||||
return getCryptoCurrencyStatusSourcesFlow(userWallet = userWallet, currency = currency)
|
||||
.map { statusSources ->
|
||||
CryptoCurrencyStatusFactory.create(
|
||||
currency = currency,
|
||||
maybeNetworkStatus = statusSources.networkStatus.toOption(),
|
||||
maybeQuoteStatus = statusSources.quoteStatus.toOption(),
|
||||
maybeStakingBalance = statusSources.stakingBalance.toOption(),
|
||||
)
|
||||
}
|
||||
.onEmpty {
|
||||
emit(
|
||||
CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading),
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun getCryptoCurrencyStatusSourcesFlow(
|
||||
userWallet: UserWallet,
|
||||
currency: CryptoCurrency,
|
||||
): Flow<CryptoCurrencyStatusSources> {
|
||||
val networkStatusFlow = getNetworkStatusFlow(userWalletId = userWallet.walletId, network = currency.network)
|
||||
|
||||
val stakingBalanceFlow = if (userWallet.isMultiCurrency) {
|
||||
networkStatusFlow.flatMapLatest { networkStatus ->
|
||||
if (networkStatus != null) {
|
||||
getStakingBalanceFlow(
|
||||
userWalletId = userWallet.walletId,
|
||||
currencyId = currency.id,
|
||||
networkStatus = networkStatus,
|
||||
)
|
||||
} else {
|
||||
flowOf(null)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val quoteStatusFlow = currency.id.rawCurrencyId?.let(::getQuoteStatusFlow)
|
||||
|
||||
return combine(networkStatusFlow, stakingBalanceFlow, quoteStatusFlow)
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun combine(
|
||||
networkStatusFlow: Flow<NetworkStatus?>,
|
||||
stakingBalanceFlow: Flow<StakingBalance?>?,
|
||||
quoteStatusFlow: Flow<QuoteStatus>?,
|
||||
): Flow<CryptoCurrencyStatusSources> {
|
||||
return when {
|
||||
stakingBalanceFlow != null && quoteStatusFlow != null -> {
|
||||
combine(
|
||||
flow = networkStatusFlow,
|
||||
flow2 = stakingBalanceFlow,
|
||||
flow3 = quoteStatusFlow,
|
||||
transform = ::CryptoCurrencyStatusSources,
|
||||
)
|
||||
}
|
||||
stakingBalanceFlow != null -> {
|
||||
combine(flow = networkStatusFlow, flow2 = stakingBalanceFlow, transform = ::CryptoCurrencyStatusSources)
|
||||
}
|
||||
quoteStatusFlow != null -> {
|
||||
combine(flow = networkStatusFlow, flow2 = quoteStatusFlow) { networkStatus, quoteStatus ->
|
||||
CryptoCurrencyStatusSources(networkStatus = networkStatus, quoteStatus = quoteStatus)
|
||||
}
|
||||
}
|
||||
else -> networkStatusFlow.map(::CryptoCurrencyStatusSources)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNetworkStatusFlow(userWalletId: UserWalletId, network: Network): Flow<NetworkStatus?> {
|
||||
return singleNetworkStatusSupplier(
|
||||
params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network),
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
.onEmpty<NetworkStatus?> {
|
||||
emit(value = null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getQuoteStatusFlow(rawCurrencyId: CryptoCurrency.RawID): Flow<QuoteStatus> {
|
||||
return singleQuoteStatusSupplier(
|
||||
params = SingleQuoteStatusProducer.Params(rawCurrencyId = rawCurrencyId),
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun getStakingBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
currencyId: CryptoCurrency.ID,
|
||||
networkStatus: NetworkStatus,
|
||||
): Flow<StakingBalance?> {
|
||||
val stakingId = stakingIdFactory.create(
|
||||
currencyId = currencyId,
|
||||
defaultAddress = networkStatus.getAddress(),
|
||||
)
|
||||
.getOrNull()
|
||||
|
||||
return if (stakingId != null) {
|
||||
singleStakingBalanceSupplier(
|
||||
params = SingleStakingBalanceProducer.Params(userWalletId = userWalletId, stakingId = stakingId),
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
} else {
|
||||
flowOf(null)
|
||||
}
|
||||
}
|
||||
|
||||
private data class CryptoCurrencyStatusSources(
|
||||
val networkStatus: NetworkStatus? = null,
|
||||
val stakingBalance: StakingBalance? = null,
|
||||
val quoteStatus: QuoteStatus? = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.google.common.truth.Truth
|
|||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.core.flow.FlowProducerTools
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.test.core.getEmittedValues
|
||||
|
|
@ -23,6 +24,7 @@ class DefaultMultiAccountStatusListProducerTest {
|
|||
private val accountsCRUDRepository: AccountsCRUDRepository = mockk()
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier = mockk()
|
||||
private val dispatchers = TestingCoroutineDispatcherProvider()
|
||||
private val flowProducerTools: FlowProducerTools = mockk()
|
||||
|
||||
private val userWalletId1 = UserWalletId("001")
|
||||
private val userWallet1 = mockk<UserWallet> {
|
||||
|
|
@ -39,6 +41,7 @@ class DefaultMultiAccountStatusListProducerTest {
|
|||
accountsCRUDRepository = accountsCRUDRepository,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
dispatchers = dispatchers,
|
||||
flowProducerTools = flowProducerTools,
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
|||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusesFlowFactory
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.core.flow.FlowProducerTools
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.StatusSource
|
||||
|
|
@ -42,20 +42,22 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
private val accountsCRUDRepository: AccountsCRUDRepository = mockk()
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier = mockk()
|
||||
private val networksRepository: NetworksRepository = mockk()
|
||||
private val cryptoCurrencyStatusesFlowFactory: CryptoCurrencyStatusesFlowFactory = mockk()
|
||||
private val flowProducerTools: FlowProducerTools = mockk()
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
}
|
||||
|
||||
private val producer = DefaultSingleAccountStatusListProducer(
|
||||
// todo accounts status producer tests
|
||||
/*private val producer = DefaultSingleAccountStatusListProducer(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId),
|
||||
accountsCRUDRepository = accountsCRUDRepository,
|
||||
singleAccountListSupplier = singleAccountListSupplier,
|
||||
networksRepository = networksRepository,
|
||||
cryptoCurrencyStatusesFlowFactory = cryptoCurrencyStatusesFlowFactory,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
flowProducerTools = flowProducerTools,
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
|
|
@ -291,5 +293,5 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
cryptoCurrencyStatusesFlowFactory.create(userWallet = userWallet, currency = cryptoCurrencyFactory.stellar)
|
||||
networksRepository.hasCachedStatuses(userWalletId)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
|
@ -1,308 +0,0 @@
|
|||
package com.tangem.domain.account.status.utils
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusProducer
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusProducer
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.single.SingleStakingBalanceProducer
|
||||
import com.tangem.domain.staking.single.SingleStakingBalanceSupplier
|
||||
import com.tangem.test.core.getEmittedValues
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("UnusedFlow")
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class CryptoCurrencyStatusesFlowFactoryTest {
|
||||
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier = mockk()
|
||||
private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier = mockk()
|
||||
private val singleStakingBalanceSupplier: SingleStakingBalanceSupplier = mockk()
|
||||
private val stakingIdFactory: StakingIdFactory = mockk()
|
||||
|
||||
private val factory = CryptoCurrencyStatusesFlowFactory(
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
|
||||
singleStakingBalanceSupplier = singleStakingBalanceSupplier,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
|
||||
private val networkAddress = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(value = "0x1", type = NetworkAddress.Address.Type.Primary),
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(
|
||||
singleNetworkStatusSupplier,
|
||||
singleQuoteStatusSupplier,
|
||||
singleStakingBalanceSupplier,
|
||||
stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if rawCurrencyId is null, there will be no subscription to the quote status`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
every { this@mockk.isMultiCurrency } returns true
|
||||
}
|
||||
|
||||
val currency = cryptoCurrencyFactory.ethereum.copy(
|
||||
id = cryptoCurrencyFactory.ethereum.id.copy(
|
||||
suffix = CryptoCurrency.ID.Suffix.ContractAddress(contractAddress = "0x12345"),
|
||||
),
|
||||
)
|
||||
|
||||
val networkStatus = NetworkStatus(
|
||||
network = currency.network,
|
||||
value = NetworkStatus.Unreachable(address = networkAddress),
|
||||
)
|
||||
val networkStatusFlow = flowOf(networkStatus)
|
||||
every {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
} returns networkStatusFlow
|
||||
|
||||
val stakingId = StakingID(integrationId = "id", address = networkAddress.defaultAddress.value)
|
||||
coEvery {
|
||||
stakingIdFactory.create(currencyId = currency.id, defaultAddress = networkAddress.defaultAddress.value)
|
||||
} returns stakingId.right()
|
||||
|
||||
val stakingBalance = StakingBalance.Empty(stakingId = stakingId, source = StatusSource.ACTUAL)
|
||||
val stakingBalanceFlow = flowOf(stakingBalance)
|
||||
every {
|
||||
singleStakingBalanceSupplier(
|
||||
params = SingleStakingBalanceProducer.Params(userWalletId = userWalletId, stakingId = stakingId),
|
||||
)
|
||||
} returns stakingBalanceFlow
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = CryptoCurrencyStatus(
|
||||
currency = currency,
|
||||
value = CryptoCurrencyStatus.Unreachable(
|
||||
priceChange = null,
|
||||
fiatRate = null,
|
||||
networkAddress = networkAddress,
|
||||
),
|
||||
)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
stakingIdFactory.create(currencyId = currency.id, defaultAddress = networkAddress.defaultAddress.value)
|
||||
singleStakingBalanceSupplier(params = SingleStakingBalanceProducer.Params(userWalletId, stakingId))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if userWallet is not multi-currency, there will be no subscription to the yield balance`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
every { this@mockk.isMultiCurrency } returns false
|
||||
}
|
||||
|
||||
val currency = cryptoCurrencyFactory.ethereum
|
||||
|
||||
val networkStatus = NetworkStatus(
|
||||
network = currency.network,
|
||||
value = NetworkStatus.Unreachable(address = networkAddress),
|
||||
)
|
||||
val networkStatusFlow = flowOf(networkStatus)
|
||||
every {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
} returns networkStatusFlow
|
||||
|
||||
val quoteStatus = QuoteStatus(
|
||||
rawCurrencyId = currency.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
priceChange = BigDecimal.ONE,
|
||||
),
|
||||
)
|
||||
val quoteStatusFlow = flowOf(quoteStatus)
|
||||
every {
|
||||
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!))
|
||||
} returns quoteStatusFlow
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = CryptoCurrencyStatus(
|
||||
currency = currency,
|
||||
value = CryptoCurrencyStatus.Unreachable(
|
||||
priceChange = BigDecimal.ONE,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
networkAddress = networkAddress,
|
||||
),
|
||||
)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no subscription to the quote status and yield balance`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
every { this@mockk.isMultiCurrency } returns false
|
||||
}
|
||||
|
||||
val currency = cryptoCurrencyFactory.ethereum.copy(
|
||||
id = cryptoCurrencyFactory.ethereum.id.copy(
|
||||
suffix = CryptoCurrency.ID.Suffix.ContractAddress(contractAddress = "0x12345"),
|
||||
),
|
||||
)
|
||||
|
||||
val networkStatus = NetworkStatus(
|
||||
network = currency.network,
|
||||
value = NetworkStatus.Unreachable(address = networkAddress),
|
||||
)
|
||||
val networkStatusFlow = flowOf(networkStatus)
|
||||
every {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
} returns networkStatusFlow
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = CryptoCurrencyStatus(
|
||||
currency = currency,
|
||||
value = CryptoCurrencyStatus.Unreachable(
|
||||
priceChange = null,
|
||||
fiatRate = null,
|
||||
networkAddress = networkAddress,
|
||||
),
|
||||
)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if stakingId is not supported, yield balance will be null`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
every { this@mockk.isMultiCurrency } returns true
|
||||
}
|
||||
|
||||
val currency = cryptoCurrencyFactory.ethereum
|
||||
|
||||
val networkStatus = NetworkStatus(
|
||||
network = currency.network,
|
||||
value = NetworkStatus.Unreachable(address = networkAddress),
|
||||
)
|
||||
val networkStatusFlow = flowOf(networkStatus)
|
||||
every {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
} returns networkStatusFlow
|
||||
|
||||
val quoteStatus = QuoteStatus(
|
||||
rawCurrencyId = currency.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
priceChange = BigDecimal.ONE,
|
||||
),
|
||||
)
|
||||
val quoteStatusFlow = flowOf(quoteStatus)
|
||||
every {
|
||||
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!))
|
||||
} returns quoteStatusFlow
|
||||
|
||||
coEvery {
|
||||
stakingIdFactory.create(currencyId = currency.id, defaultAddress = networkAddress.defaultAddress.value)
|
||||
} returns StakingIdFactory.Error.UnsupportedCurrency.left()
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = CryptoCurrencyStatus(
|
||||
currency = currency,
|
||||
value = CryptoCurrencyStatus.Unreachable(
|
||||
priceChange = BigDecimal.ONE,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
networkAddress = networkAddress,
|
||||
),
|
||||
)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `all sources are empty`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
every { this@mockk.isMultiCurrency } returns true
|
||||
}
|
||||
|
||||
val currency = cryptoCurrencyFactory.ethereum
|
||||
|
||||
every {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
} returns emptyFlow()
|
||||
|
||||
every {
|
||||
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!))
|
||||
} returns emptyFlow()
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue