diff --git a/data/account/src/main/kotlin/com/tangem/data/account/di/AccountDataModule.kt b/data/account/src/main/kotlin/com/tangem/data/account/di/AccountDataModule.kt index b2ba244666..668808ef2a 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/di/AccountDataModule.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/di/AccountDataModule.kt @@ -1,14 +1,9 @@ package com.tangem.data.account.di import android.content.Context -import androidx.datastore.core.DataStoreFactory -import androidx.datastore.dataStoreFile -import com.squareup.moshi.Moshi import com.tangem.data.account.converter.AccountConverterFactoryContainer import com.tangem.data.account.fetcher.DefaultWalletAccountsFetcher -import com.tangem.data.account.repository.AccountsExpandedDTO import com.tangem.data.account.repository.DefaultAccountsCRUDRepository -import com.tangem.data.account.repository.DefaultAccountsExpandedRepository import com.tangem.data.account.store.AccountsResponseStoreFactory import com.tangem.data.account.store.ArchivedAccountsStoreFactory import com.tangem.data.account.tokens.DefaultMainAccountTokensMigration @@ -17,14 +12,9 @@ import com.tangem.data.common.account.WalletAccountsSaver import com.tangem.data.common.cache.etag.ETagsStore import com.tangem.data.common.currency.UserTokensSaver import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.accounts.AccountTokenMigrationStore import com.tangem.datasource.local.datastore.RuntimeStateStore -import com.tangem.datasource.utils.MoshiDataStoreSerializer -import com.tangem.datasource.utils.mapWithStringKeyTypes -import com.tangem.datasource.utils.setTypes import com.tangem.domain.account.repository.AccountsCRUDRepository -import com.tangem.domain.account.repository.AccountsExpandedRepository import com.tangem.domain.account.tokens.MainAccountTokensMigration import com.tangem.utils.coroutines.AppCoroutineScope import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -63,28 +53,6 @@ internal object AccountDataModule { ) } - @Provides - @Singleton - fun provideAccountsExpandedRepository( - @NetworkMoshi moshi: Moshi, - @ApplicationContext context: Context, - appScope: AppCoroutineScope, - ): AccountsExpandedRepository { - val store = DataStoreFactory.create>>( - serializer = MoshiDataStoreSerializer( - moshi = moshi, - types = mapWithStringKeyTypes(valueTypes = setTypes()), - defaultValue = emptyMap(), - ), - produceFile = { context.dataStoreFile(fileName = "account_expanded_store") }, - scope = appScope, - ) - - return DefaultAccountsExpandedRepository( - store = store, - ) - } - @Provides @Singleton fun provideWalletAccountsFetcher(impl: DefaultWalletAccountsFetcher): WalletAccountsFetcher = impl diff --git a/data/account/src/main/kotlin/com/tangem/data/account/di/AccountUtilsModule.kt b/data/account/src/main/kotlin/com/tangem/data/account/di/AccountUtilsModule.kt new file mode 100644 index 0000000000..5a1e61c2bf --- /dev/null +++ b/data/account/src/main/kotlin/com/tangem/data/account/di/AccountUtilsModule.kt @@ -0,0 +1,18 @@ +package com.tangem.data.account.di + +import com.tangem.data.account.repository.DefaultAccountsExpandedRepository +import com.tangem.domain.account.repository.AccountsExpandedRepository +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +internal interface AccountUtilsModule { + + @Binds + fun provideAccountsExpandedRepositoryFactory( + factory: DefaultAccountsExpandedRepository.Factory, + ): AccountsExpandedRepository.Factory +} \ No newline at end of file diff --git a/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsExpandedRepository.kt b/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsExpandedRepository.kt index e5232c0dc9..2bbaad3697 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsExpandedRepository.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsExpandedRepository.kt @@ -1,17 +1,28 @@ package com.tangem.data.account.repository +import android.content.Context import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.dataStoreFile import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import com.squareup.moshi.Moshi import com.tangem.data.account.converter.toAccountId +import com.tangem.datasource.di.NetworkMoshi +import com.tangem.datasource.utils.MoshiDataStoreSerializer +import com.tangem.datasource.utils.mapWithStringKeyTypes +import com.tangem.datasource.utils.setTypes import com.tangem.domain.account.models.AccountExpandedState import com.tangem.domain.account.repository.AccountsExpandedRepository import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.utils.coroutines.AppCoroutineScope +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import javax.inject.Inject -internal class DefaultAccountsExpandedRepository( +internal class DefaultAccountsExpandedRepository constructor( private val store: DataStore>>, ) : AccountsExpandedRepository { @@ -41,7 +52,7 @@ internal class DefaultAccountsExpandedRepository( } override suspend fun clearStore() { - store.updateData { emptyMap() } + store.updateData { map -> map.mapValues { emptySet() } } } override suspend fun update(accountState: AccountExpandedState) { @@ -59,6 +70,28 @@ internal class DefaultAccountsExpandedRepository( map.plus(walletId.stringValue to updatedSet) } } + + internal class Factory @Inject constructor( + @NetworkMoshi private val moshi: Moshi, + @ApplicationContext private val context: Context, + private val appScope: AppCoroutineScope, + ) : AccountsExpandedRepository.Factory { + override fun create(storeFileName: String): DefaultAccountsExpandedRepository { + val store = DataStoreFactory.create>>( + serializer = MoshiDataStoreSerializer( + moshi = moshi, + types = mapWithStringKeyTypes(valueTypes = setTypes()), + defaultValue = emptyMap(), + ), + produceFile = { context.dataStoreFile(fileName = storeFileName) }, + scope = appScope, + ) + + return DefaultAccountsExpandedRepository( + store = store, + ) + } + } } @JsonClass(generateAdapter = true) diff --git a/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsExpandedRepository.kt b/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsExpandedRepository.kt index d493fc48c5..85c362e718 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsExpandedRepository.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsExpandedRepository.kt @@ -12,4 +12,13 @@ interface AccountsExpandedRepository { suspend fun syncStore(walletId: UserWalletId, existAccounts: Set) 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" + } } \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/ChooseTokenExpandedAccountsHolder.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/ChooseTokenExpandedAccountsHolder.kt new file mode 100644 index 0000000000..c571c27ac0 --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/ChooseTokenExpandedAccountsHolder.kt @@ -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> = 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) + } +} \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/ExpandedAccountsHolder.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/ExpandedAccountsHolder.kt index 4a33fdf3b8..f0bf98cccd 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/ExpandedAccountsHolder.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/ExpandedAccountsHolder.kt @@ -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> = expandedAccounts(userWallet.walletId) + fun expandedAccounts(walletId: UserWalletId): Flow> + 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> = expandedAccounts(userWallet.walletId) + fun expandedAccounts(walletId: UserWalletId, initExpanded: Set = emptySet()): Flow> = + channelFlow { + val storedState = accountsExpandedRepository.expandedAccounts + .map { it[walletId] ?: initExpanded.map { id -> AccountExpandedState(id, true) } } + .stateIn(this) - fun expandedAccounts(walletId: UserWalletId): Flow> = 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 = singleAccountListSupplier(walletId) + @AssistedFactory + interface Factory { + fun create(accountsExpandedRepository: AccountsExpandedRepository): DefaultExpandedAccountsHolder + } + companion object { private const val DEBOUNCE_MILLIS = 200L } diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/MainExpandedAccountsHolder.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/MainExpandedAccountsHolder.kt new file mode 100644 index 0000000000..95fba2f575 --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/MainExpandedAccountsHolder.kt @@ -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> { + return default.expandedAccounts(walletId) + } + + override fun expandAccount(accountId: AccountId) { + default.expandAccount(accountId) + } + + override fun collapseAccount(accountId: AccountId) { + default.collapseAccount(accountId) + } +} \ No newline at end of file diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/choosetoken/impl/model/PortfolioListBlockDelegate.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/choosetoken/impl/model/PortfolioListBlockDelegate.kt index 64558e8d15..9e45ac853a 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/choosetoken/impl/model/PortfolioListBlockDelegate.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/choosetoken/impl/model/PortfolioListBlockDelegate.kt @@ -3,7 +3,7 @@ package com.tangem.feature.swap.choosetoken.impl.model import com.tangem.common.ui.tokens.TokenConverterParams import com.tangem.domain.account.models.AccountStatusList import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier -import com.tangem.domain.account.status.utils.ExpandedAccountsHolder +import com.tangem.domain.account.status.utils.ChooseTokenExpandedAccountsHolder import com.tangem.domain.models.account.Account import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.account.AccountStatus @@ -23,7 +23,7 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* internal class PortfolioListBlockDelegate @AssistedInject constructor( - private val expandedAccountsHolder: ExpandedAccountsHolder, + private val expandedAccountsHolder: ChooseTokenExpandedAccountsHolder, private val settingContext: SettingContextUseCase, private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier, private val getWalletsUseCase: GetWalletsUseCase, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/account/AccountDependencies.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/account/AccountDependencies.kt index d30b9a4165..b252ecce55 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/account/AccountDependencies.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/account/AccountDependencies.kt @@ -3,14 +3,14 @@ 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.status.utils.MainExpandedAccountsHolder import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase import javax.inject.Inject @ModelScoped internal class AccountDependencies @Inject constructor( val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, - val expandedAccountsHolder: ExpandedAccountsHolder, + val expandedAccountsHolder: MainExpandedAccountsHolder, val singleAccountStatusListSupplier: SingleAccountStatusListSupplier, val singleAccountStatusSupplier: SingleAccountStatusSupplier, ) \ No newline at end of file