Updated on 2026-08-14
This commit is contained in:
parent
7a197ec6c9
commit
c662136303
26 changed files with 906 additions and 346 deletions
|
|
@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.account
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusSupplier
|
||||
import com.tangem.domain.account.status.utils.ExpandedAccountsHolder
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
|||
|
|
@ -1,112 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.account
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.account.models.AccountExpandedState
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.repository.AccountsExpandedRepository
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class ExpandedAccountsHolder @Inject constructor(
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
private val accountsExpandedRepository: AccountsExpandedRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
private val actionChannel = MutableSharedFlow<Pair<AccountId, Boolean>>(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
)
|
||||
|
||||
fun expandedAccounts(userWallet: UserWallet): Flow<Set<AccountId>> = channelFlow {
|
||||
val walletId = userWallet.walletId
|
||||
|
||||
val storedState = accountsExpandedRepository.expandedAccounts
|
||||
.map { it[walletId].orEmpty() }
|
||||
.stateIn(this)
|
||||
|
||||
val isAccountsMode = isAccountsModeEnabledUseCase.invoke()
|
||||
.stateIn(this)
|
||||
|
||||
val initExpandedState = storedState.value
|
||||
.mapNotNull { it.takeIf { state -> state.isExpanded }?.accountId }
|
||||
.toSet()
|
||||
// main state holder
|
||||
val expandedAccounts = MutableStateFlow(initExpandedState)
|
||||
var debounceJob: Job? = null
|
||||
|
||||
actionChannel
|
||||
.filter { (accountId, _) -> accountId.userWalletId == walletId }
|
||||
.filter { debounceJob?.isActive != true }
|
||||
.onEach { (accountId, isExpand) ->
|
||||
debounceJob = launch { delay(DEBOUNCE_MILLIS) }
|
||||
val newState = AccountExpandedState(accountId, isExpand)
|
||||
launch { accountsExpandedRepository.update(newState) }
|
||||
if (isExpand) {
|
||||
expandedAccounts.update { it.plus(accountId) }
|
||||
} else {
|
||||
expandedAccounts.update { it.minus(accountId) }
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
|
||||
walletAccounts(walletId).onEach { accountList ->
|
||||
if (!isAccountsModeEnabledUseCase.invokeSync()) {
|
||||
accountsExpandedRepository.clearStore()
|
||||
expandedAccounts.update { setOf() }
|
||||
return@onEach
|
||||
}
|
||||
val idsSet = accountList.accounts.mapTo(mutableSetOf()) { it.accountId }
|
||||
accountsExpandedRepository.syncStore(walletId, idsSet)
|
||||
|
||||
val isSingleAccount = accountList.accounts.size == 1
|
||||
val storedMainAccountState = storedState.value
|
||||
.find { it.accountId == accountList.mainAccount.accountId }
|
||||
|
||||
if (isSingleAccount && storedMainAccountState == null) {
|
||||
// force expand for single and not stored account
|
||||
expandedAccounts.update { setOf(accountList.mainAccount.accountId) }
|
||||
}
|
||||
}.launchIn(this)
|
||||
|
||||
combine(
|
||||
flow = expandedAccounts,
|
||||
flow2 = isAccountsMode,
|
||||
transform = { expanded, isAccountMode ->
|
||||
if (isAccountMode) {
|
||||
channel.send(expanded)
|
||||
} else {
|
||||
channel.send(setOf())
|
||||
}
|
||||
},
|
||||
).collect()
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
.distinctUntilChanged()
|
||||
|
||||
fun expandAccount(accountId: AccountId) {
|
||||
actionChannel.tryEmit(accountId to true)
|
||||
}
|
||||
|
||||
fun collapseAccount(accountId: AccountId) {
|
||||
actionChannel.tryEmit(accountId to false)
|
||||
}
|
||||
|
||||
private fun walletAccounts(walletId: UserWalletId): Flow<AccountList> = singleAccountListSupplier(walletId)
|
||||
|
||||
companion object {
|
||||
private const val DEBOUNCE_MILLIS = 200L
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.common.ui.tokens.TokenConverterParams
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
|
||||
sealed interface TokenConverterParams {
|
||||
/** Wallet mode; list of tokens for main account */
|
||||
data class Wallet(
|
||||
val accountId: AccountId,
|
||||
val tokenList: TokenList,
|
||||
) : TokenConverterParams
|
||||
|
||||
/** Account mode; list of accounts */
|
||||
data class Account(
|
||||
val accountList: AccountStatusList,
|
||||
val expandedAccounts: Set<AccountId>,
|
||||
) : TokenConverterParams
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import com.tangem.core.ui.components.tokenlist.state.PortfolioItemContentUM
|
|||
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
|
|
@ -25,11 +24,10 @@ import com.tangem.domain.staking.model.StakingAvailability
|
|||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
|
||||
import com.tangem.common.ui.tokens.TokenConverterParams
|
||||
import com.tangem.common.ui.tokens.TokenItemGrouping.toGroupedItems
|
||||
import com.tangem.common.ui.tokens.TokenItemGrouping.toUngroupedItems
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig
|
||||
|
|
@ -92,7 +90,7 @@ internal class TokenListStateConverter(
|
|||
return when (params) {
|
||||
is TokenConverterParams.Account -> convertAccountList(params)
|
||||
is TokenConverterParams.Wallet -> convertTokenList(
|
||||
tokenConverter = tokenStatusConverter(params.accountId),
|
||||
tokenConverter = tokenStatusConverter(params.mainAccount.accountId),
|
||||
tokenList = params.tokenList,
|
||||
)
|
||||
}
|
||||
|
|
@ -102,11 +100,11 @@ internal class TokenListStateConverter(
|
|||
when (tokenList) {
|
||||
is TokenList.Empty -> WalletTokensListState.Empty
|
||||
is TokenList.GroupedByNetwork -> WalletTokensListState.ContentState.Content(
|
||||
items = tokenList.toGroupedItems(tokenConverter),
|
||||
items = tokenList.toGroupedItems(tokenConverter).toPersistentList(),
|
||||
organizeTokensButtonConfig = getOrganizeTokensButtonState(tokenList = tokenList),
|
||||
)
|
||||
is TokenList.Ungrouped -> WalletTokensListState.ContentState.Content(
|
||||
items = tokenList.toUngroupedItems(tokenConverter),
|
||||
items = tokenList.toUngroupedItems(tokenConverter).toPersistentList(),
|
||||
organizeTokensButtonConfig = getOrganizeTokensButtonState(tokenList = tokenList),
|
||||
)
|
||||
}
|
||||
|
|
@ -167,51 +165,6 @@ internal class TokenListStateConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun TokenList.GroupedByNetwork.toGroupedItems(
|
||||
tokenConverter: TokenItemStateConverter,
|
||||
): PersistentList<TokensListItemUM> {
|
||||
return groups.fold(initial = persistentListOf()) { acc, group ->
|
||||
acc.mutate { it.addGroup(tokenConverter, group) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun TokenList.Ungrouped.toUngroupedItems(
|
||||
tokenConverter: TokenItemStateConverter,
|
||||
): PersistentList<TokensListItemUM> {
|
||||
return currencies.fold(initial = persistentListOf()) { acc, token ->
|
||||
acc.mutate { it.addToken(tokenConverter, token) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<TokensListItemUM>.addGroup(
|
||||
tokenConverter: TokenItemStateConverter,
|
||||
group: NetworkGroup,
|
||||
): List<TokensListItemUM> {
|
||||
val groupTitle = TokensListItemUM.GroupTitle(
|
||||
id = group.network.hashCode(),
|
||||
text = resourceReference(
|
||||
id = R.string.wallet_network_group_title,
|
||||
formatArgs = wrappedList(group.network.name),
|
||||
),
|
||||
)
|
||||
|
||||
add(groupTitle)
|
||||
group.currencies.forEach { token -> addToken(tokenConverter, token) }
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun MutableList<TokensListItemUM>.addToken(
|
||||
tokenConverter: TokenItemStateConverter,
|
||||
token: CryptoCurrencyStatus,
|
||||
): List<TokensListItemUM> {
|
||||
val tokenItemState = tokenConverter.convert(token)
|
||||
|
||||
add(TokensListItemUM.Token(tokenItemState))
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getOrganizeTokensButtonState(tokenList: TokenList): WalletOrganizeTokensButtonConfig? {
|
||||
val currenciesSize = when (tokenList) {
|
||||
TokenList.Empty -> return null
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.tangem.domain.account.models.AccountStatusList
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
|
||||
import com.tangem.common.ui.tokens.TokenConverterParams
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListErrorTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
|
||||
import com.tangem.common.ui.tokens.TokenConverterParams
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
|
@ -65,7 +66,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
singleAccountTransform(
|
||||
maybeTokenList = maybeTokenList,
|
||||
appCurrency = appCurrency,
|
||||
accountId = mainAccount.accountId,
|
||||
mainAccount = mainAccount,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingAvailabilityMap = stakingAvailabilityMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
|
|
@ -111,7 +112,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
private fun singleAccountTransform(
|
||||
maybeTokenList: Lce<TokenListError, TokenList>,
|
||||
appCurrency: AppCurrency,
|
||||
accountId: AccountId,
|
||||
mainAccount: AccountStatus.CryptoPortfolio,
|
||||
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
|
||||
shouldShowMainPromo: Boolean,
|
||||
|
|
@ -141,7 +142,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
)
|
||||
|
||||
updateContent(
|
||||
params = TokenConverterParams.Wallet(accountId, tokenList),
|
||||
params = TokenConverterParams.Wallet(mainAccount, tokenList),
|
||||
appCurrency = appCurrency,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingAvailabilityMap = stakingAvailabilityMap,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.common.ui.tokens.TokenConverterParams
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.Account.CryptoPortfolio.Companion.createMainAccount
|
||||
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
|
||||
|
|
@ -11,19 +13,25 @@ import com.tangem.domain.models.network.NetworkAddress
|
|||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class YieldSupplyPromoBannerConverterTest {
|
||||
|
||||
private val account
|
||||
get() = AccountStatus.CryptoPortfolio(
|
||||
tokenList = TokenList.Empty,
|
||||
priceChangeLce = Unit.lceError(),
|
||||
account = createMainAccount(UserWalletId("00"))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN promo disabled WHEN convert THEN return null`() {
|
||||
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xABCDEF")
|
||||
val status = createLoadedStatus(token = token, amount = BigDecimal.ONE, isYieldActive = false)
|
||||
val tokenList = ungroupedTokenList(status)
|
||||
val params = TokenConverterParams.Wallet(
|
||||
accountId = AccountId.forMainCryptoPortfolio(UserWalletId("00")),
|
||||
mainAccount = account,
|
||||
tokenList = tokenList,
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerConverter(
|
||||
|
|
@ -41,7 +49,7 @@ class YieldSupplyPromoBannerConverterTest {
|
|||
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xA1")
|
||||
val status = createLoadedStatus(token = token, amount = BigDecimal("2.0"), isYieldActive = false)
|
||||
val params = TokenConverterParams.Wallet(
|
||||
accountId = AccountId.forMainCryptoPortfolio(UserWalletId("00")),
|
||||
mainAccount = account,
|
||||
tokenList = ungroupedTokenList(status),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerConverter(
|
||||
|
|
@ -59,7 +67,7 @@ class YieldSupplyPromoBannerConverterTest {
|
|||
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xAA")
|
||||
val statusActive = createLoadedStatus(token = token, amount = BigDecimal("5"), isYieldActive = true)
|
||||
val params = TokenConverterParams.Wallet(
|
||||
accountId = AccountId.forMainCryptoPortfolio(UserWalletId("00")),
|
||||
mainAccount = account,
|
||||
tokenList = ungroupedTokenList(statusActive),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerConverter(
|
||||
|
|
@ -87,7 +95,7 @@ class YieldSupplyPromoBannerConverterTest {
|
|||
)
|
||||
|
||||
val params = TokenConverterParams.Wallet(
|
||||
accountId = AccountId.forMainCryptoPortfolio(UserWalletId("00")),
|
||||
mainAccount = account,
|
||||
tokenList = ungroupedTokenList(statusSmall, statusBig),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerConverter(
|
||||
|
|
@ -110,7 +118,7 @@ class YieldSupplyPromoBannerConverterTest {
|
|||
val apyMap = mapOf(mismatchedKey to BigDecimal("0.07"))
|
||||
|
||||
val params = TokenConverterParams.Wallet(
|
||||
accountId = AccountId.forMainCryptoPortfolio(UserWalletId("00")),
|
||||
mainAccount = account,
|
||||
tokenList = ungroupedTokenList(status),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerConverter(
|
||||
|
|
@ -128,7 +136,7 @@ class YieldSupplyPromoBannerConverterTest {
|
|||
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xCUSTOM")
|
||||
val status = createCustomStatus(token = token, amount = BigDecimal("5.0"), isYieldActive = false)
|
||||
val params = TokenConverterParams.Wallet(
|
||||
accountId = AccountId.forMainCryptoPortfolio(UserWalletId("00")),
|
||||
mainAccount = account,
|
||||
tokenList = ungroupedTokenList(status),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerConverter(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue