Updated on 2026-08-14
This commit is contained in:
parent
c425d8640d
commit
a5b29a4449
8 changed files with 321 additions and 35 deletions
|
|
@ -70,5 +70,6 @@ dependencies {
|
|||
testImplementation(projects.common.test)
|
||||
testImplementation(projects.test.core)
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(deps.test.turbine)
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -1,11 +1,16 @@
|
|||
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.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.data.account.converter.AccountConverterFactoryContainer
|
||||
import com.tangem.data.account.featuretoggle.DefaultAccountsFeatureToggles
|
||||
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
|
||||
|
|
@ -13,11 +18,16 @@ import com.tangem.data.common.account.WalletAccountsFetcher
|
|||
import com.tangem.data.common.account.WalletAccountsSaver
|
||||
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.local.userwallet.UserWalletsStore
|
||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||
import com.tangem.datasource.utils.mapWithStringKeyTypes
|
||||
import com.tangem.datasource.utils.setTypes
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
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.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -25,6 +35,8 @@ import dagger.Provides
|
|||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
|
@ -63,6 +75,28 @@ internal object AccountDataModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAccountsExpandedRepository(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): AccountsExpandedRepository {
|
||||
val store = DataStoreFactory.create<Map<String, Set<AccountsExpandedDTO>>>(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = mapWithStringKeyTypes(valueTypes = setTypes<AccountsExpandedDTO>()),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "account_expanded_store") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
)
|
||||
|
||||
return DefaultAccountsExpandedRepository(
|
||||
store = store,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletAccountsFetcher(impl: DefaultWalletAccountsFetcher): WalletAccountsFetcher = impl
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
package com.tangem.data.account.repository
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.data.account.converter.toAccountId
|
||||
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 kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
internal class DefaultAccountsExpandedRepository(
|
||||
private val store: DataStore<Map<String, Set<AccountsExpandedDTO>>>,
|
||||
) : AccountsExpandedRepository {
|
||||
|
||||
override val expandedAccounts: Flow<Map<UserWalletId, Set<AccountExpandedState>>> = store.data
|
||||
.map { stored ->
|
||||
stored.map { (rawWalletId, setOfDto) ->
|
||||
val walletId = UserWalletId(rawWalletId)
|
||||
val setOfState = setOfDto.mapTo(mutableSetOf()) { dto ->
|
||||
AccountExpandedState(
|
||||
accountId = dto.accountId.toAccountId(walletId),
|
||||
isExpanded = dto.isExpanded,
|
||||
)
|
||||
}
|
||||
walletId to setOfState
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
override suspend fun syncStore(walletId: UserWalletId, existAccounts: Set<AccountId>) {
|
||||
store.updateData { map ->
|
||||
val setOfDto = map[walletId.stringValue] ?: return@updateData map
|
||||
val existingAccountIds = existAccounts.map { it.value }.toSet()
|
||||
val syncedSet = setOfDto
|
||||
.filterTo(mutableSetOf()) { (accountId, _) -> existingAccountIds.contains(accountId) }
|
||||
|
||||
map.plus(walletId.stringValue to syncedSet)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun update(accountState: AccountExpandedState) {
|
||||
store.updateData { map ->
|
||||
val walletId = accountState.accountId.userWalletId
|
||||
val setOfDto = map[walletId.stringValue].orEmpty()
|
||||
val newDto = AccountsExpandedDTO(
|
||||
accountId = accountState.accountId.value,
|
||||
isExpanded = accountState.isExpanded,
|
||||
)
|
||||
val updatedSet = setOfDto
|
||||
.filterTo(mutableSetOf()) { it.accountId != accountState.accountId.value }
|
||||
.plus(newDto)
|
||||
|
||||
map.plus(walletId.stringValue to updatedSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AccountsExpandedDTO(
|
||||
@Json(name = "accountId")
|
||||
val accountId: String,
|
||||
@Json(name = "isExpanded")
|
||||
val isExpanded: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package com.tangem.data.account.repository
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.datastore.MockStateDataStore
|
||||
import com.tangem.domain.account.models.AccountExpandedState
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultAccountsExpandedRepositoryTest {
|
||||
|
||||
private val walletId = UserWalletId("011")
|
||||
private val mainAccountId = AccountId.forMainCryptoPortfolio(walletId)
|
||||
private val secondAccountId = AccountId.forCryptoPortfolio(walletId, DerivationIndex(1).getOrNull()!!)
|
||||
|
||||
@Test
|
||||
fun `expandedAccounts emits updated state when store changes`() = runTest {
|
||||
val dataStore = MockStateDataStore<Map<String, Set<AccountsExpandedDTO>>>(
|
||||
default = emptyMap()
|
||||
)
|
||||
|
||||
val repository = DefaultAccountsExpandedRepository(dataStore)
|
||||
|
||||
repository.expandedAccounts.test {
|
||||
// initial emission
|
||||
val initial = awaitItem()
|
||||
Truth.assertThat(initial.isEmpty()).isTrue()
|
||||
|
||||
// update store
|
||||
dataStore.updateData {
|
||||
mapOf(
|
||||
walletId.stringValue to setOf(
|
||||
AccountsExpandedDTO(
|
||||
accountId = mainAccountId.value,
|
||||
isExpanded = true
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// next emission
|
||||
val updated = awaitItem()
|
||||
val states = updated[walletId]!!
|
||||
|
||||
Truth.assertThat(states.size).isEqualTo(1)
|
||||
val state = states.first()
|
||||
|
||||
Truth.assertThat(state.accountId).isEqualTo(mainAccountId)
|
||||
Truth.assertThat(state.isExpanded).isTrue()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `expandedAccounts emits when update is called`() = runTest {
|
||||
val dataStore = MockStateDataStore<Map<String, Set<AccountsExpandedDTO>>>(
|
||||
default = emptyMap()
|
||||
)
|
||||
|
||||
val repository = DefaultAccountsExpandedRepository(dataStore)
|
||||
|
||||
val state = AccountExpandedState(
|
||||
accountId = mainAccountId,
|
||||
isExpanded = true
|
||||
)
|
||||
|
||||
repository.expandedAccounts.test {
|
||||
// initial
|
||||
awaitItem()
|
||||
|
||||
// when
|
||||
repository.update(state)
|
||||
|
||||
// then
|
||||
val updated = awaitItem()
|
||||
val states = updated[walletId]!!
|
||||
|
||||
Truth.assertThat(states.size).isEqualTo(1)
|
||||
Truth.assertThat(states.first().isExpanded).isTrue()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `expandedAccounts emits synced state after syncStore`() = runTest {
|
||||
val dataStore = MockStateDataStore(
|
||||
mapOf(
|
||||
walletId.stringValue to setOf(
|
||||
AccountsExpandedDTO(mainAccountId.value, true),
|
||||
AccountsExpandedDTO(secondAccountId.value, false)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val repository = DefaultAccountsExpandedRepository(dataStore)
|
||||
|
||||
repository.expandedAccounts.test {
|
||||
// initial
|
||||
val initial = awaitItem()
|
||||
Truth.assertThat(initial[walletId]!!.size).isEqualTo(2)
|
||||
|
||||
// when
|
||||
repository.syncStore(
|
||||
walletId = walletId,
|
||||
existAccounts = setOf(mainAccountId) // without secondAccountId
|
||||
)
|
||||
|
||||
// then
|
||||
val synced = awaitItem()
|
||||
val states = synced[walletId]!!
|
||||
|
||||
Truth.assertThat(states.size).isEqualTo(1)
|
||||
Truth.assertThat(states.first().accountId.value).isEqualTo(mainAccountId.value)
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue