Updated on 2026-08-14
This commit is contained in:
commit
c50840894f
739 changed files with 27664 additions and 4701 deletions
|
|
@ -12,4 +12,13 @@ interface AccountsExpandedRepository {
|
|||
suspend fun syncStore(walletId: UserWalletId, existAccounts: Set<AccountId>)
|
||||
suspend fun clearStore()
|
||||
suspend fun update(accountState: AccountExpandedState)
|
||||
|
||||
interface Factory {
|
||||
fun create(storeFileName: String): AccountsExpandedRepository
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAIN_STORE_FILE_NAME = "account_expanded_store"
|
||||
const val CHOOSE_TOKEN_FILE_NAME = "choose_token_account_expanded_store"
|
||||
}
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ class ArchiveCryptoPortfolioUseCase(
|
|||
val hasNotReferralToken = statuses.none { status ->
|
||||
val currency = status.currency
|
||||
|
||||
currency.network.backendId == referralToken.networkId &&
|
||||
currency.network.rawId == referralToken.networkId &&
|
||||
(currency as? CryptoCurrency.Token)?.contractAddress == referralToken.contractAddress &&
|
||||
status.value.networkAddress?.availableAddresses?.any { it.value == address } == true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ class ManageCryptoCurrenciesUseCase(
|
|||
val foundToken = accountStatus.tokenList.flattenCurrencies()
|
||||
.mapNotNull { it.currency as? CryptoCurrency.Token }
|
||||
.firstOrNull { token ->
|
||||
token.network.backendId == networkId &&
|
||||
token.network.rawId == networkId &&
|
||||
!token.isCustom &&
|
||||
token.contractAddress.equals(contractAddress, true)
|
||||
}
|
||||
|
|
@ -361,7 +361,7 @@ class ManageCryptoCurrenciesUseCase(
|
|||
launch {
|
||||
val assetIds = currencies.mapTo(hashSetOf()) { currency ->
|
||||
ExpressAsset.ID(
|
||||
networkId = currency.network.backendId,
|
||||
networkId = currency.network.rawId,
|
||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
}
|
||||
|
|
@ -388,19 +388,19 @@ class ManageCryptoCurrenciesUseCase(
|
|||
) {
|
||||
|
||||
constructor(network: Network) : this(
|
||||
networkId = network.backendId,
|
||||
networkId = network.rawId,
|
||||
derivationPath = network.derivationPath,
|
||||
contractAddress = null,
|
||||
)
|
||||
|
||||
constructor(currency: CryptoCurrency) : this(
|
||||
networkId = currency.network.backendId,
|
||||
networkId = currency.network.rawId,
|
||||
derivationPath = currency.network.derivationPath,
|
||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
|
||||
constructor(status: CryptoCurrencyStatus) : this(
|
||||
networkId = status.currency.network.backendId,
|
||||
networkId = status.currency.network.rawId,
|
||||
derivationPath = status.currency.network.derivationPath,
|
||||
contractAddress = (status.currency as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.domain.account.status.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.model.AccountCryptoCurrency
|
||||
|
|
@ -180,7 +180,7 @@ internal object AccountCryptoCurrencyStatusFinder {
|
|||
contractAddress: String?,
|
||||
): AccountCryptoCurrency? {
|
||||
return accountList.getExpectedAccounts(
|
||||
rawNetworkId = networkId.rawId.value,
|
||||
rawNetworkId = networkId.rawId,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
.asSequence()
|
||||
|
|
@ -220,7 +220,7 @@ internal object AccountCryptoCurrencyStatusFinder {
|
|||
|
||||
internal fun AccountStatusList.getExpectedAccountStatuses(networkId: Network.ID): List<AccountStatus> {
|
||||
val possibleAccountIndex = getAccountIndexOrNull(
|
||||
rawNetworkId = networkId.rawId.value,
|
||||
rawNetworkId = networkId.rawId,
|
||||
derivationPath = networkId.derivationPath,
|
||||
)
|
||||
|
||||
|
|
@ -239,7 +239,7 @@ internal object AccountCryptoCurrencyStatusFinder {
|
|||
}
|
||||
|
||||
internal fun AccountStatusList.getExpectedAccountStatuses(networks: List<Network>): List<AccountStatus> {
|
||||
val possibleAccountIndexes = networks.mapNotNull { getAccountIndexOrNull(it.rawId, it.derivationPath) }
|
||||
val possibleAccountIndexes = networks.mapNotNull { getAccountIndexOrNull(it.id.rawId, it.derivationPath) }
|
||||
|
||||
if (possibleAccountIndexes.isEmpty()) return accountStatuses
|
||||
|
||||
|
|
@ -256,16 +256,14 @@ internal object AccountCryptoCurrencyStatusFinder {
|
|||
// region AccountList helpers
|
||||
|
||||
internal fun AccountList.getExpectedAccounts(network: Network?): List<Account> {
|
||||
return getExpectedAccounts(rawNetworkId = network?.rawId, derivationPath = network?.derivationPath)
|
||||
return getExpectedAccounts(rawNetworkId = network?.id?.rawId, derivationPath = network?.derivationPath)
|
||||
}
|
||||
|
||||
private fun AccountList.getExpectedAccounts(
|
||||
rawNetworkId: String?,
|
||||
rawNetworkId: Network.RawID?,
|
||||
derivationPath: Network.DerivationPath?,
|
||||
): List<Account> {
|
||||
val possibleAccountIndex = getAccountIndexOrNull(rawNetworkId, derivationPath)
|
||||
|
||||
return when (possibleAccountIndex) {
|
||||
return when (val possibleAccountIndex = getAccountIndexOrNull(rawNetworkId, derivationPath)) {
|
||||
null -> accounts
|
||||
DerivationIndex.Main.value -> listOf(mainAccount)
|
||||
// currency only in the account with specific derivation index or in the main account
|
||||
|
|
@ -283,10 +281,10 @@ internal object AccountCryptoCurrencyStatusFinder {
|
|||
|
||||
// region Common helpers
|
||||
|
||||
private fun getAccountIndexOrNull(rawNetworkId: String?, derivationPath: Network.DerivationPath?): Int? {
|
||||
private fun getAccountIndexOrNull(rawNetworkId: Network.RawID?, derivationPath: Network.DerivationPath?): Int? {
|
||||
if (rawNetworkId == null || derivationPath == null) return null
|
||||
|
||||
val blockchain = Blockchain.fromId(id = rawNetworkId)
|
||||
val blockchain = rawNetworkId.toBlockchain()
|
||||
val recognizer = AccountNodeRecognizer(blockchain)
|
||||
|
||||
return recognizer.recognize(derivationPath)?.toInt()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.domain.account.status.utils
|
||||
|
||||
import com.tangem.domain.account.repository.AccountsExpandedRepository
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ChooseTokenExpandedAccountsHolder @Inject constructor(
|
||||
private val mainHolder: MainExpandedAccountsHolder,
|
||||
holderFactory: DefaultExpandedAccountsHolder.Factory,
|
||||
repositoryFactory: AccountsExpandedRepository.Factory,
|
||||
) : ExpandedAccountsHolder {
|
||||
|
||||
private val repository: AccountsExpandedRepository =
|
||||
repositoryFactory.create(AccountsExpandedRepository.CHOOSE_TOKEN_FILE_NAME)
|
||||
private val defaultHolder: DefaultExpandedAccountsHolder = holderFactory.create(repository)
|
||||
|
||||
override fun expandedAccounts(walletId: UserWalletId): Flow<Set<AccountId>> = flow {
|
||||
val isStored = repository.expandedAccounts.first()[walletId] != null
|
||||
|
||||
if (isStored) {
|
||||
emitAll(defaultHolder.expandedAccounts(walletId))
|
||||
} else {
|
||||
val initExpanded = mainHolder.expandedAccounts(walletId).first()
|
||||
emitAll(defaultHolder.expandedAccounts(walletId, initExpanded))
|
||||
}
|
||||
}
|
||||
|
||||
override fun expandAccount(accountId: AccountId) {
|
||||
defaultHolder.expandAccount(accountId)
|
||||
}
|
||||
|
||||
override fun collapseAccount(accountId: AccountId) {
|
||||
defaultHolder.collapseAccount(accountId)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,20 +9,26 @@ 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 dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
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
|
||||
import javax.inject.Singleton
|
||||
|
||||
// todo swap separate for main and swap
|
||||
@Singleton
|
||||
class ExpandedAccountsHolder @Inject constructor(
|
||||
interface ExpandedAccountsHolder {
|
||||
fun expandedAccounts(userWallet: UserWallet): Flow<Set<AccountId>> = expandedAccounts(userWallet.walletId)
|
||||
fun expandedAccounts(walletId: UserWalletId): Flow<Set<AccountId>>
|
||||
fun expandAccount(accountId: AccountId)
|
||||
fun collapseAccount(accountId: AccountId)
|
||||
}
|
||||
|
||||
class DefaultExpandedAccountsHolder @AssistedInject constructor(
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
private val accountsExpandedRepository: AccountsExpandedRepository,
|
||||
@Assisted private val accountsExpandedRepository: AccountsExpandedRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
|
|
@ -31,71 +37,70 @@ class ExpandedAccountsHolder @Inject constructor(
|
|||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
)
|
||||
|
||||
fun expandedAccounts(userWallet: UserWallet): Flow<Set<AccountId>> = expandedAccounts(userWallet.walletId)
|
||||
fun expandedAccounts(walletId: UserWalletId, initExpanded: Set<AccountId> = emptySet()): Flow<Set<AccountId>> =
|
||||
channelFlow {
|
||||
val storedState = accountsExpandedRepository.expandedAccounts
|
||||
.map { it[walletId] ?: initExpanded.map { id -> AccountExpandedState(id, true) } }
|
||||
.stateIn(this)
|
||||
|
||||
fun expandedAccounts(walletId: UserWalletId): Flow<Set<AccountId>> = channelFlow {
|
||||
val storedState = accountsExpandedRepository.expandedAccounts
|
||||
.map { it[walletId].orEmpty() }
|
||||
.stateIn(this)
|
||||
val isAccountsMode = isAccountsModeEnabledUseCase.invoke()
|
||||
.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
|
||||
|
||||
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) }
|
||||
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)
|
||||
.launchIn(this)
|
||||
|
||||
walletAccounts(walletId).onEach { accountList ->
|
||||
if (!isAccountsModeEnabledUseCase.invokeSync()) {
|
||||
accountsExpandedRepository.clearStore()
|
||||
expandedAccounts.update { emptySet() }
|
||||
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(emptySet())
|
||||
walletAccounts(walletId).onEach { accountList ->
|
||||
if (!isAccountsModeEnabledUseCase.invokeSync()) {
|
||||
accountsExpandedRepository.clearStore()
|
||||
expandedAccounts.update { emptySet() }
|
||||
return@onEach
|
||||
}
|
||||
},
|
||||
).collect()
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
.distinctUntilChanged()
|
||||
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(emptySet())
|
||||
}
|
||||
},
|
||||
).collect()
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
.distinctUntilChanged()
|
||||
|
||||
fun expandAccount(accountId: AccountId) {
|
||||
actionChannel.tryEmit(accountId to true)
|
||||
|
|
@ -107,6 +112,11 @@ class ExpandedAccountsHolder @Inject constructor(
|
|||
|
||||
private fun walletAccounts(walletId: UserWalletId): Flow<AccountList> = singleAccountListSupplier(walletId)
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(accountsExpandedRepository: AccountsExpandedRepository): DefaultExpandedAccountsHolder
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEBOUNCE_MILLIS = 200L
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.domain.account.status.utils
|
||||
|
||||
import com.tangem.domain.account.repository.AccountsExpandedRepository
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class MainExpandedAccountsHolder @Inject constructor(
|
||||
holderFactory: DefaultExpandedAccountsHolder.Factory,
|
||||
repositoryFactory: AccountsExpandedRepository.Factory,
|
||||
) : ExpandedAccountsHolder {
|
||||
|
||||
private val repository: AccountsExpandedRepository = repositoryFactory
|
||||
.create(AccountsExpandedRepository.MAIN_STORE_FILE_NAME)
|
||||
private val default: DefaultExpandedAccountsHolder = holderFactory.create(repository)
|
||||
|
||||
override fun expandedAccounts(walletId: UserWalletId): Flow<Set<AccountId>> {
|
||||
return default.expandedAccounts(walletId)
|
||||
}
|
||||
|
||||
override fun expandAccount(accountId: AccountId) {
|
||||
default.expandAccount(accountId)
|
||||
}
|
||||
|
||||
override fun collapseAccount(accountId: AccountId) {
|
||||
default.collapseAccount(accountId)
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
|
|||
val defaultAddress = "0xABC"
|
||||
|
||||
val cryptoCurrency = mockk<CryptoCurrency.Token> {
|
||||
every { this@mockk.network.backendId } returns token.networkId
|
||||
every { this@mockk.network.rawId } returns token.networkId
|
||||
every { this@mockk.contractAddress } returns token.contractAddress!!
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue