Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-25 17:17:12 +04:00
parent 52bf5cbb58
commit 9bd16deeb4
23 changed files with 245 additions and 151 deletions

View file

@ -11,6 +11,7 @@ android {
dependencies {
/** Project - Core */
implementation(projects.core.ui)
implementation(projects.core.utils)
/** Compose */
implementation(tangemDeps.vico.core)

View file

@ -1,7 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>SuspendFunSwallowedCancellation:MarketChartDataProducer.kt$MarketChartDataProducer$runCatching</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -6,6 +6,7 @@ import com.patrykandpatrick.vico.core.cartesian.data.LineCartesianLayerModel
import com.tangem.common.ui.charts.state.converter.PointValuesConverter
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
import com.tangem.common.ui.charts.state.formatter.FormatterWrapWithCache
import com.tangem.utils.coroutines.runSuspendCatching
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.sync.Mutex
@ -126,7 +127,7 @@ class MarketChartDataProducer private constructor(
currentCoroutineContext().ensureActive()
runCatching {
runSuspendCatching {
modelProducer.runTransaction {
add(LineCartesianLayerModel.Partial(series = listOf(entriesLocal)))
}

View file

@ -1,7 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>CastNullableToNonNullableType:DefaultDeviceFlipDetector.kt$DefaultDeviceFlipDetector$as</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -16,11 +16,19 @@ import javax.inject.Singleton
@Singleton
class DefaultDeviceFlipDetector @Inject constructor(
@ApplicationContext context: Context,
@ApplicationContext private val context: Context,
) : DeviceFlipDetector, DefaultLifecycleObserver {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
private val sensorManager: SensorManager by lazy {
val manager = context.getSystemService(Context.SENSOR_SERVICE) as? SensorManager
requireNotNull(manager)
}
private val gravitySensor by lazy {
sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
}
private val isResumedState = AtomicBoolean(false)
override fun onPause(owner: LifecycleOwner) {

View file

@ -1,7 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>MultilineLambdaItParameter:DefaultHotWalletRepository.kt$DefaultHotWalletRepository${ it.setObjectMap( key = PreferencesKeys.ACCESS_CODE_SKIPPED_STATES_KEY, value = it.getObjectMap&lt;Boolean&gt;(PreferencesKeys.ACCESS_CODE_SKIPPED_STATES_KEY) .plus(userWalletId.stringValue to skipped), ) }</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -17,10 +17,10 @@ internal class DefaultHotWalletRepository(
.map { it[userWalletId.stringValue] == true }
override suspend fun setAccessCodeSkipped(userWalletId: UserWalletId, skipped: Boolean) {
appPreferencesStore.editData {
it.setObjectMap(
appPreferencesStore.editData { mutablePreferences ->
mutablePreferences.setObjectMap(
key = PreferencesKeys.ACCESS_CODE_SKIPPED_STATES_KEY,
value = it.getObjectMap<Boolean>(PreferencesKeys.ACCESS_CODE_SKIPPED_STATES_KEY)
value = mutablePreferences.getObjectMap<Boolean>(PreferencesKeys.ACCESS_CODE_SKIPPED_STATES_KEY)
.plus(userWalletId.stringValue to skipped),
)
}

View file

@ -79,6 +79,10 @@ internal class DefaultNetworksRepository(
}
}
override suspend fun hasCachedStatuses(userWalletId: UserWalletId): Boolean {
return networksStatusesStore.contains(userWalletId)
}
private suspend fun fetchPendingTransactions(
userWalletId: UserWalletId,
network: Network,

View file

@ -124,6 +124,10 @@ internal class DefaultNetworksStatusesStore(
}
}
override suspend fun contains(userWalletId: UserWalletId): Boolean {
return runtimeStore.getSyncOrDefault(emptyMap()).containsKey(userWalletId.stringValue)
}
private suspend fun updateInRuntime(
userWalletId: UserWalletId,
networks: Set<Network>,

View file

@ -44,4 +44,7 @@ internal interface NetworksStatusesStore {
/** Clear statuses of [networks] by [userWalletId] */
suspend fun clear(userWalletId: UserWalletId, networks: Set<Network>)
/** Check if there are statuses for given [userWalletId] */
suspend fun contains(userWalletId: UserWalletId): Boolean
}

View file

@ -19,10 +19,7 @@ import com.tangem.domain.promo.models.StoryContent
import com.tangem.feature.referral.domain.ReferralRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
@ -41,7 +38,9 @@ internal class DefaultPromoRepository(
return appPreferencesStore.get(
key = PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name),
default = true,
).map { shouldShow ->
)
.distinctUntilChanged()
.map { shouldShow ->
when (promoId) {
PromoId.Referral -> runCatching {
!referralRepository.isReferralParticipant(userWalletId) && shouldShow

View file

@ -1,7 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>MultilineLambdaItParameter:DefaultMultiQuoteStatusFetcher.kt$DefaultMultiQuoteStatusFetcher${ Timber.e(it) quotesStatusesStore.setSourceAsOnlyCache(currenciesIds = params.currenciesIds) }</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -66,8 +66,8 @@ internal class DefaultMultiQuoteStatusFetcher @Inject constructor(
quotesStatusesStore.store(values = updatedResponse.quotes)
}
.onLeft {
Timber.e(it)
.onLeft { throwable ->
Timber.e(throwable)
quotesStatusesStore.setSourceAsOnlyCache(currenciesIds = params.currenciesIds)
}

View file

@ -1,7 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>SuspendFunSwallowedCancellation:DefaultSettingsRepository.kt$DefaultSettingsRepository$runCatching</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -13,6 +13,7 @@ import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.models.GB_COUNTRY
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -153,7 +154,7 @@ internal class DefaultSettingsRepository(
}
withContext(dispatchers.io) {
val country = runCatching { tangemTechApi.getUserCountryCode() }
val country = runSuspendCatching { tangemTechApi.getUserCountryCode() }
.fold(
onSuccess = GeoResponse::code,
onFailure = { Locale.getDefault().country },

View file

@ -1,9 +1,13 @@
==========================================
Detekt Baseline Updater & Issue Counter
==========================================
Date: 2025-11-21 15:22:23
Date: 2025-11-24 18:35:00
Updating detekt baseline for debug variant...
Step 1: Running detekt to check for new issues...
✓ Detekt passed - no new issues found
Step 2: Updating detekt baseline for debug variant...
Baseline updated successfully!
@ -13,13 +17,13 @@ Counting issues in baseline files...
==========================================
Summary:
Total Issues: 1720
Modules with Issues: 90
Average Issues per Module: 19
Total Issues: 1700
Modules with Issues: 83
Average Issues per Module: 20
Progress:
Fixed: 82 out of 1802 (4%)
Remaining: 1720
Fixed: 102 out of 1802 (5%)
Remaining: 1700
==========================================
All Modules with Issues (sorted by count)
@ -27,7 +31,7 @@ All Modules with Issues (sorted by count)
Module Issues
────────────────────────────────────────────────────────────────
features/wallet/impl 170
features/wallet/impl 169
features/markets/impl 155
features/onboarding-v2/impl 131
features/onramp/impl 89
@ -43,10 +47,10 @@ features/manage-tokens/impl 45
domain/wallets 39
features/nft/impl 36
features/tester/impl 31
features/yield-supply/impl 28
domain/tokens 28
features/swap/domain 27
core/ui 27
features/yield-supply/impl 26
common/ui 26
data/visa 23
features/tangempay/details/impl 22
@ -54,9 +58,9 @@ data/nft 20
data/wallets 18
features/swap/data 15
data/swap 13
domain/account/status 12
features/token-recieve/impl 11
features/qr-scanning/impl 11
domain/account/status 11
data/onramp 11
data/manage-tokens 11
core/datasource 11
@ -104,16 +108,10 @@ domain/nft 2
data/express 2
core/ab-tests 2
common/test 2
features/referral/data 1
features/manage-tokens/api 1
features/kyc/impl 1
features/hot-wallet/api 1
features/disclaimer/impl 1
domain/wallet-connect 1
domain/visa 1
data/settings 1
data/quotes 1
data/hot-wallet 1
data/balance-hiding 1
common/ui-charts 1
────────────────────────────────────────────────────────────────

View file

@ -2,7 +2,6 @@
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>ExplicitCollectionElementAccessMethod:ManageCryptoCurrenciesUseCase.kt$ManageCryptoCurrenciesUseCase$mutableMap.put(id, currency)</ID>
<ID>MultilineLambdaItParameter:AccountCryptoCurrencyStatusFinder.kt$AccountCryptoCurrencyStatusFinder${ val cryptoPortfolio = it.account as? Account.CryptoPortfolio ?: return@filter false cryptoPortfolio.derivationIndex.value in possibleAccountIndexes }</ID>
<ID>MultilineLambdaItParameter:AccountCryptoCurrencyStatusFinder.kt$AccountCryptoCurrencyStatusFinder${ val cryptoPortfolio = it.account as? Account.CryptoPortfolio ?: return@firstOrNull false cryptoPortfolio.derivationIndex.value == possibleAccountIndex }</ID>
<ID>MultilineLambdaItParameter:AccountCryptoCurrencyStatusFinder.kt$AccountCryptoCurrencyStatusFinder${ val currency = it.currency val isContractAddressMatch = contractAddress == null || currency.id.contractAddress.equals(contractAddress, ignoreCase = true) currency.network.rawId == networkId.rawId.value &amp;&amp; currency.network.derivationPath.value == derivationPath.value &amp;&amp; isContractAddressMatch }</ID>

View file

@ -2,20 +2,24 @@ package com.tangem.domain.account.status.producer
import arrow.core.Option
import arrow.core.none
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.producer.SingleAccountListProducer
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.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.CryptoCurrencyStatus
import com.tangem.domain.models.quote.PriceChange
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.networks.repository.NetworksRepository
import com.tangem.domain.tokens.operations.PriceChangeCalculator
import com.tangem.domain.tokens.operations.TokenListFactory
import com.tangem.domain.tokens.operations.TotalFiatBalanceCalculator
@ -24,8 +28,10 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.*
import java.math.BigDecimal
import kotlin.time.Duration.Companion.milliseconds
/**
* Produces a flow of [AccountStatusList] for a single user wallet.
@ -43,6 +49,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
@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,
) : SingleAccountStatusListProducer {
@ -50,14 +57,31 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
override val fallback: Option<AccountStatusList> = none()
override fun produce(): Flow<AccountStatusList> {
val accountListFlow = singleAccountListSupplier(
params = SingleAccountListProducer.Params(params.userWalletId),
return singleAccountListSupplier(userWalletId = params.userWalletId).flatMapLatest { accountList ->
val accountStatusFlows = createAccountStatusFlows(accountList)
combine(accountStatusFlows) { accountStatuses ->
val balances = accountStatuses.flattenTotalFiatBalance()
AccountStatusList(
userWalletId = accountList.userWalletId,
accountStatuses = accountStatuses.toList(),
totalAccounts = accountList.totalAccounts,
totalFiatBalance = TotalFiatBalanceCalculator.calculate(balances),
sortType = accountList.sortType,
groupType = accountList.groupType,
)
}
.onStartCheckCachedNetworks(accountList)
}
.distinctUntilChanged()
.flowOn(dispatchers.default)
}
return accountListFlow.flatMapLatest { accountList ->
val accountStatusFlows = accountList.accounts.mapNotNull { account ->
if (account !is Account.CryptoPortfolio) return@mapNotNull null
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 {
@ -72,22 +96,8 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
}
.distinctUntilChanged()
}
combine(accountStatusFlows) { accountStatuses ->
val balances = accountStatuses.map { it.tokenList.totalFiatBalance }
AccountStatusList(
userWalletId = accountList.userWalletId,
accountStatuses = accountStatuses.toList(),
totalAccounts = accountList.totalAccounts,
totalFiatBalance = TotalFiatBalanceCalculator.calculate(balances),
sortType = accountList.sortType,
groupType = accountList.groupType,
)
}
}
.distinctUntilChanged()
.flowOn(dispatchers.default)
}
private fun createEmptyAccountStatusFlow(account: Account.CryptoPortfolio): Flow<AccountStatus.CryptoPortfolio> {
@ -109,14 +119,10 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
groupType: TokensGroupType,
sortType: TokensSortType,
): Flow<AccountStatus.CryptoPortfolio> {
val statusesFlows = account.cryptoCurrencies.map { currency ->
cryptoCurrencyStatusesFlowFactory.create(userWallet = userWallet, currency = currency)
.distinctUntilChanged()
}
return combine(statusesFlows) { statuses ->
val statusList = statuses.toList()
val statusesFlows = getCryptoCurrencyStatusesFlow(userWallet, account)
return statusesFlows
.map { statusList ->
AccountStatus.CryptoPortfolio(
account = account,
tokenList = TokenListFactory.create(
@ -130,6 +136,70 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
.distinctUntilChanged()
}
@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)) }
.distinctUntilChanged()
}
return combine(statusesFlows) { it.toList() }
.distinctUntilChanged()
.debounce(50.milliseconds)
}
private fun Array<AccountStatus>.flattenTotalFiatBalance(): List<TotalFiatBalance> {
return map { accountStatus ->
when (accountStatus) {
is AccountStatus.CryptoPortfolio -> accountStatus.tokenList.totalFiatBalance
}
}
}
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,
accountStatuses = accountList.accounts.map { account ->
when (account) {
is Account.CryptoPortfolio -> {
val currencyStatuses = account.cryptoCurrencies.map {
CryptoCurrencyStatus(currency = it, value = CryptoCurrencyStatus.Loading)
}
AccountStatus.CryptoPortfolio(
account = account,
tokenList = TokenListFactory.create(
statuses = currencyStatuses,
groupType = accountList.groupType,
sortType = accountList.sortType,
),
priceChangeLce = lceLoading(),
)
}
}
},
totalAccounts = accountList.totalAccounts,
totalFiatBalance = TotalFiatBalance.Loading,
sortType = accountList.sortType,
groupType = accountList.groupType,
)
}
@AssistedFactory
interface Factory : SingleAccountStatusListProducer.Factory {
override fun create(params: SingleAccountStatusListProducer.Params): DefaultSingleAccountStatusListProducer

View file

@ -5,7 +5,6 @@ import com.google.common.truth.Truth
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.producer.SingleAccountListProducer
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusesFlowFactory
import com.tangem.domain.account.supplier.SingleAccountListSupplier
@ -20,9 +19,11 @@ import com.tangem.domain.models.quote.PriceChange
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.networks.repository.NetworksRepository
import com.tangem.test.core.getEmittedValues
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
@ -40,6 +41,7 @@ class DefaultSingleAccountStatusListProducerTest {
private val accountsCRUDRepository: AccountsCRUDRepository = mockk()
private val singleAccountListSupplier: SingleAccountListSupplier = mockk()
private val networksRepository: NetworksRepository = mockk()
private val cryptoCurrencyStatusesFlowFactory: CryptoCurrencyStatusesFlowFactory = mockk()
private val userWalletId = UserWalletId("011")
@ -51,13 +53,19 @@ class DefaultSingleAccountStatusListProducerTest {
params = SingleAccountStatusListProducer.Params(userWalletId),
accountsCRUDRepository = accountsCRUDRepository,
singleAccountListSupplier = singleAccountListSupplier,
networksRepository = networksRepository,
cryptoCurrencyStatusesFlowFactory = cryptoCurrencyStatusesFlowFactory,
dispatchers = TestingCoroutineDispatcherProvider(),
)
@AfterEach
fun tearDown() {
clearMocks(accountsCRUDRepository, singleAccountListSupplier, cryptoCurrencyStatusesFlowFactory)
clearMocks(
accountsCRUDRepository,
singleAccountListSupplier,
networksRepository,
cryptoCurrencyStatusesFlowFactory,
)
}
@Test
@ -65,9 +73,8 @@ class DefaultSingleAccountStatusListProducerTest {
// Arrange
val accountList = AccountList.empty(userWalletId = userWalletId)
every {
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
} returns flowOf(accountList)
every { singleAccountListSupplier(userWalletId) } returns flowOf(accountList)
coEvery { networksRepository.hasCachedStatuses(userWalletId) } returns true
// Act
val actual = producer.produce().let(::getEmittedValues)
@ -89,8 +96,14 @@ class DefaultSingleAccountStatusListProducerTest {
)
Truth.assertThat(actual).containsExactly(expected)
coVerify(ordering = Ordering.SEQUENCE) {
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
coVerifySequence {
singleAccountListSupplier(userWalletId)
networksRepository.hasCachedStatuses(userWalletId)
}
coVerify(inverse = true) {
accountsCRUDRepository.getUserWallet(userWalletId = any())
cryptoCurrencyStatusesFlowFactory.create(userWallet = any(), currency = any())
}
}
@ -102,9 +115,8 @@ class DefaultSingleAccountStatusListProducerTest {
val accountListFlow = MutableStateFlow(value = accountList)
every {
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
} returns accountListFlow
every { singleAccountListSupplier(userWalletId) } returns accountListFlow
coEvery { networksRepository.hasCachedStatuses(userWalletId) } returns true
// Act (first emission)
val actual1 = producer.produce().let(::getEmittedValues)
@ -147,9 +159,17 @@ class DefaultSingleAccountStatusListProducerTest {
)
Truth.assertThat(actual2).containsExactly(expected2)
coVerify(ordering = Ordering.SEQUENCE) {
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
coVerifySequence {
singleAccountListSupplier(userWalletId)
networksRepository.hasCachedStatuses(userWalletId)
networksRepository.hasCachedStatuses(userWalletId)
singleAccountListSupplier(userWalletId)
networksRepository.hasCachedStatuses(userWalletId)
}
coVerify(inverse = true) {
accountsCRUDRepository.getUserWallet(userWalletId = any())
cryptoCurrencyStatusesFlowFactory.create(userWallet = any(), currency = any())
}
}
@ -159,9 +179,8 @@ class DefaultSingleAccountStatusListProducerTest {
val accountList = AccountList.empty(userWalletId)
val accountListFlow = MutableStateFlow(value = accountList)
every {
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
} returns accountListFlow
every { singleAccountListSupplier(userWalletId) } returns accountListFlow
coEvery { networksRepository.hasCachedStatuses(userWalletId) } returns true
val expected = AccountStatusList(
userWalletId = userWalletId,
@ -191,9 +210,16 @@ class DefaultSingleAccountStatusListProducerTest {
// Assert (second emission)
Truth.assertThat(actual2).containsExactly(expected)
coVerify(ordering = Ordering.SEQUENCE) {
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
coVerifySequence {
singleAccountListSupplier(userWalletId)
networksRepository.hasCachedStatuses(userWalletId)
singleAccountListSupplier(userWalletId)
networksRepository.hasCachedStatuses(userWalletId)
}
coVerify(inverse = true) {
accountsCRUDRepository.getUserWallet(userWalletId = any())
cryptoCurrencyStatusesFlowFactory.create(userWallet = any(), currency = any())
}
}
@ -208,9 +234,8 @@ class DefaultSingleAccountStatusListProducerTest {
coEvery { accountsCRUDRepository.getUserWallet(userWalletId = userWalletId) } returns userWallet
every {
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
} returns flowOf(accountList)
every { singleAccountListSupplier(userWalletId) } returns flowOf(accountList)
coEvery { networksRepository.hasCachedStatuses(userWalletId) } returns true
val ethereumStatus = CryptoCurrencyStatus(
currency = cryptoCurrencyFactory.ethereum,
@ -229,7 +254,9 @@ class DefaultSingleAccountStatusListProducerTest {
} returns flowOf(stellarStatus)
// Act
val actual = producer.produce().let(::getEmittedValues)
val flow = producer.produce()
delay(1000)
val actual = flow.let(::getEmittedValues)
// Assert
val expected = AccountStatusList(
@ -252,8 +279,12 @@ class DefaultSingleAccountStatusListProducerTest {
)
Truth.assertThat(actual).containsExactly(expected)
coVerify(ordering = Ordering.SEQUENCE) {
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
coVerifySequence {
singleAccountListSupplier(userWalletId)
accountsCRUDRepository.getUserWallet(userWalletId = userWalletId)
cryptoCurrencyStatusesFlowFactory.create(userWallet = userWallet, currency = cryptoCurrencyFactory.ethereum)
cryptoCurrencyStatusesFlowFactory.create(userWallet = userWallet, currency = cryptoCurrencyFactory.stellar)
networksRepository.hasCachedStatuses(userWalletId)
}
}
}

View file

@ -22,5 +22,14 @@ interface NetworksRepository {
*/
suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List<CryptoCurrencyAddress>
/**
* Returns addresses and crypto currency
*
* @param userWalletId the unique identifier of the user wallet
* @param network network id
*/
suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network.RawID): List<CryptoCurrencyAddress>
/** Checks if there are cached statuses for given [userWalletId] */
suspend fun hasCachedStatuses(userWalletId: UserWalletId): Boolean
}

View file

@ -72,7 +72,6 @@
<ID>MultilineLambdaItParameter:WalletCurrencyActionsClickIntents.kt$WalletCurrencyActionsClickIntentsImplementor${ analyticsEventHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(currency.symbol)) clipboardManager.setText(text = it, isSensitive = true) }</ID>
<ID>MultilineLambdaItParameter:WalletCurrencyActionsClickIntents.kt$WalletCurrencyActionsClickIntentsImplementor${ analyticsEventHandler.send(TokenReceiveAnalyticsEvent.ButtonShareAddress(currency.symbol)) shareManager.shareText(text = it) }</ID>
<ID>MultilineLambdaItParameter:WalletCurrencyActionsClickIntents.kt$WalletCurrencyActionsClickIntentsImplementor${ onAddressTypeSelected( userWalletId = userWalletId, currency = currency, addressModel = it, ) }</ID>
<ID>MultilineLambdaItParameter:WalletDropDownItemsSubscriber.kt$WalletDropDownItemsSubscriber${ stateHolder.update( SetWalletCardDropDownItemsTransformer( dropdownEnabled = it, clickIntents = clickIntents, ), ) }</ID>
<ID>MultilineLambdaItParameter:WalletLoaderStorage.kt$WalletLoaderStorage${ it.forEach(Job::cancel) loaders.remove(id) }</ID>
<ID>MultilineLambdaItParameter:WalletModel.kt$WalletModel${ it .conflate() .distinctUntilChanged() .onEach { selectedWallet -&gt; if (selectedWallet.isMultiCurrency) { selectedWalletAnalyticsSender.send(selectedWallet) } subscribeOnExpressTransactionsUpdates(selectedWallet) observeAndClearNFTCacheIfNeedUseCase(selectedWallet) } .flowOn(dispatchers.main) .launchIn(modelScope) }</ID>
<ID>MultilineLambdaItParameter:WalletModel.kt$WalletModel${ walletScreenContentLoader.load( userWallet = it, clickIntents = clickIntents, coroutineScope = modelScope, isRefresh = true, ) }</ID>

View file

@ -45,6 +45,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import timber.log.Timber
import javax.inject.Inject
@ -95,13 +96,16 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
return combine(
// todo account just use it, after delete accountsFeatureToggles
// accountStatusListFlow,
isReadyToShowRateAppUseCase(),
isNeedToBackupUseCase(userWallet.walletId),
seedPhraseNotificationUseCase(userWalletId = userWallet.walletId),
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.VisaPresale),
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa),
notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key),
getAccessCodeSkippedUseCase(userWallet.walletId),
isReadyToShowRateAppUseCase().distinctUntilChanged(),
isNeedToBackupUseCase(userWallet.walletId).distinctUntilChanged(),
seedPhraseNotificationUseCase(userWalletId = userWallet.walletId).distinctUntilChanged(),
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.VisaPresale)
.distinctUntilChanged(),
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa)
.distinctUntilChanged(),
notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key)
.distinctUntilChanged(),
getAccessCodeSkippedUseCase(userWallet.walletId).distinctUntilChanged(),
) { array -> array }
.combine(tokenListFlow()) { array, any: Any -> arrayOf(any).plus(elements = array) }
.map { array ->

View file

@ -5,12 +5,10 @@
<ID>BooleanPropertyNaming:YieldSupplyApyComponent.kt$YieldSupplyApyComponent$val state by loadingState.collectAsState()</ID>
<ID>BooleanPropertyNaming:YieldSupplyModel.kt$YieldSupplyModel$private val handleNavigation = params.handleNavigation</ID>
<ID>BooleanPropertyNaming:YieldSupplyModel.kt$YieldSupplyModel$val processing = uiState.value is YieldSupplyUM.Processing</ID>
<ID>BooleanPropertyNaming:YieldSupplyModel.kt$YieldSupplyModel$val showInfoIcon = cryptoCurrencyStatus.yieldSupplyNotAllAmountSupplied()</ID>
<ID>BooleanPropertyNaming:YieldSupplyModel.kt$YieldSupplyModel$val showWarningIcon = !yieldSupplyStatus.isAllowedToSpend</ID>
<ID>BooleanPropertyNaming:YieldSupplyUM.kt$YieldSupplyUM.Content$val showInfoIcon: Boolean</ID>
<ID>BooleanPropertyNaming:YieldSupplyUM.kt$YieldSupplyUM.Content$val showWarningIcon: Boolean</ID>
<ID>MultilineLambdaItParameter:YieldSupplyApproveModel.kt$YieldSupplyApproveModel${ Timber.e(it) return }</ID>
<ID>MultilineLambdaItParameter:YieldSupplyModel.kt$YieldSupplyModel${ Timber.e(it) uiState.update { YieldSupplyUM.Content( title = resourceReference( R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title, ), subtitle = resourceReference( R.string.yield_module_token_details_earn_notification_earning_on_your_balance_subtitle, ), rewardsApy = TextReference.EMPTY, onClick = ::onActiveClick, showWarningIcon = showWarningIcon, showInfoIcon = showInfoIcon, apy = "", ) } }</ID>
<ID>MultilineLambdaItParameter:YieldSupplyModel.kt$YieldSupplyModel${ Timber.e(it) uiState.update { YieldSupplyUM.Initial } }</ID>
<ID>MultilineLambdaItParameter:YieldSupplyModel.kt$YieldSupplyModel${ Timber.w(it.toString()) return@launch }</ID>
<ID>MultilineLambdaItParameter:YieldSupplyStartEarningModel.kt$YieldSupplyStartEarningModel${ Timber.w(it.toString()) showAlertError() }</ID>