Updated on 2026-08-14
This commit is contained in:
parent
52bf5cbb58
commit
9bd16deeb4
23 changed files with 245 additions and 151 deletions
|
|
@ -11,6 +11,7 @@ android {
|
|||
dependencies {
|
||||
/** Project - Core */
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Compose */
|
||||
implementation(tangemDeps.vico.core)
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>SuspendFunSwallowedCancellation:MarketChartDataProducer.kt$MarketChartDataProducer$runCatching</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -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)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>CastNullableToNonNullableType:DefaultDeviceFlipDetector.kt$DefaultDeviceFlipDetector$as</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<Boolean>(PreferencesKeys.ACCESS_CODE_SKIPPED_STATES_KEY) .plus(userWalletId.stringValue to skipped), ) }</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,23 +38,25 @@ internal class DefaultPromoRepository(
|
|||
return appPreferencesStore.get(
|
||||
key = PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name),
|
||||
default = true,
|
||||
).map { shouldShow ->
|
||||
when (promoId) {
|
||||
PromoId.Referral -> runCatching {
|
||||
!referralRepository.isReferralParticipant(userWalletId) && shouldShow
|
||||
}.getOrDefault(false)
|
||||
PromoId.Sepa -> {
|
||||
val isActive = getSepaPromoBanner()?.isActive ?: false
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
.map { shouldShow ->
|
||||
when (promoId) {
|
||||
PromoId.Referral -> runCatching {
|
||||
!referralRepository.isReferralParticipant(userWalletId) && shouldShow
|
||||
}.getOrDefault(false)
|
||||
PromoId.Sepa -> {
|
||||
val isActive = getSepaPromoBanner()?.isActive ?: false
|
||||
|
||||
isActive && shouldShow
|
||||
}
|
||||
PromoId.VisaPresale -> {
|
||||
val isActive = getVisaPromoBanner()?.isActive ?: false
|
||||
isActive && shouldShow
|
||||
}
|
||||
PromoId.VisaPresale -> {
|
||||
val isActive = getVisaPromoBanner()?.isActive ?: false
|
||||
|
||||
isActive && shouldShow
|
||||
isActive && shouldShow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isReadyToShowTokenPromo(promoId: PromoId): Flow<Boolean> {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>SuspendFunSwallowedCancellation:DefaultSettingsRepository.kt$DefaultSettingsRepository$runCatching</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
────────────────────────────────────────────────────────────────
|
||||
|
|
@ -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 && currency.network.derivationPath.value == derivationPath.value && isContractAddressMatch }</ID>
|
||||
|
|
|
|||
|
|
@ -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,31 +57,11 @@ 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 accountListFlow.flatMapLatest { accountList ->
|
||||
val accountStatusFlows = accountList.accounts.mapNotNull { account ->
|
||||
if (account !is Account.CryptoPortfolio) return@mapNotNull null
|
||||
|
||||
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()
|
||||
}
|
||||
return singleAccountListSupplier(userWalletId = params.userWalletId).flatMapLatest { accountList ->
|
||||
val accountStatusFlows = createAccountStatusFlows(accountList)
|
||||
|
||||
combine(accountStatusFlows) { accountStatuses ->
|
||||
val balances = accountStatuses.map { it.tokenList.totalFiatBalance }
|
||||
val balances = accountStatuses.flattenTotalFiatBalance()
|
||||
|
||||
AccountStatusList(
|
||||
userWalletId = accountList.userWalletId,
|
||||
|
|
@ -85,11 +72,34 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
|||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEmptyAccountStatusFlow(account: Account.CryptoPortfolio): Flow<AccountStatus.CryptoPortfolio> {
|
||||
return flowOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
|
|
@ -109,25 +119,85 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
|||
groupType: TokensGroupType,
|
||||
sortType: TokensSortType,
|
||||
): Flow<AccountStatus.CryptoPortfolio> {
|
||||
val statusesFlows = getCryptoCurrencyStatusesFlow(userWallet, account)
|
||||
|
||||
return statusesFlows
|
||||
.map { statusList ->
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = account,
|
||||
tokenList = TokenListFactory.create(
|
||||
statuses = statusList,
|
||||
groupType = groupType,
|
||||
sortType = sortType,
|
||||
),
|
||||
priceChangeLce = PriceChangeCalculator.calculate(statuses = statusList),
|
||||
)
|
||||
}
|
||||
.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) { statuses ->
|
||||
val statusList = statuses.toList()
|
||||
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = account,
|
||||
tokenList = TokenListFactory.create(
|
||||
statuses = statusList,
|
||||
groupType = groupType,
|
||||
sortType = sortType,
|
||||
),
|
||||
priceChangeLce = PriceChangeCalculator.calculate(statuses = statusList),
|
||||
)
|
||||
}
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 -> 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>
|
||||
|
|
|
|||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue