Skip to content

Commit e010342

Browse files
authored
feat(account): decouple account management and storage from legacy Preferences (#11708)
2 parents faaab4d + 360ae2e commit e010342

505 files changed

Lines changed: 12098 additions & 7698 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎app-common/build.gradle.kts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ dependencies {
1818
api(projects.legacy.ui.legacy)
1919

2020
api(projects.feature.account.core)
21+
api(projects.feature.account.internal)
2122
api(projects.feature.launcher)
2223
api(projects.feature.navigation.drawer.api)
2324

@@ -68,10 +69,12 @@ dependencies {
6869

6970
implementation(libs.okio)
7071

72+
testImplementation(projects.core.android.account.fake)
7173
testImplementation(projects.feature.account.fake)
7274
testImplementation(projects.core.testing)
7375
testImplementation(projects.core.android.testing)
7476
testImplementation(projects.core.configstore.testing)
77+
testImplementation(projects.legacy.storage)
7578

7679
testImplementation(libs.mockito.kotlin)
7780
}
Lines changed: 112 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package net.thunderbird.app.common.account
22

33
import android.content.Context
4+
import androidx.annotation.VisibleForTesting
45
import app.k9mail.feature.account.common.domain.entity.Account
56
import app.k9mail.feature.account.common.domain.entity.SpecialFolderOption
67
import app.k9mail.feature.account.common.domain.entity.SpecialFolderSettings
@@ -9,8 +10,8 @@ import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCrea
910
import app.k9mail.legacy.mailstore.domain.GetFolderIdsForTypeUseCase
1011
import app.k9mail.legacy.mailstore.domain.SetPushForFolderUseCase
1112
import com.fsck.k9.Core
12-
import com.fsck.k9.Preferences
1313
import com.fsck.k9.account.DeletePolicyProvider
14+
import com.fsck.k9.backend.BackendManager
1415
import com.fsck.k9.controller.MessagingController
1516
import com.fsck.k9.mail.FolderType
1617
import com.fsck.k9.mail.ServerSettings
@@ -24,14 +25,17 @@ import com.fsck.k9.preferences.UnifiedInboxConfigurator
2425
import kotlinx.coroutines.CoroutineDispatcher
2526
import kotlinx.coroutines.Dispatchers
2627
import kotlinx.coroutines.withContext
27-
import net.thunderbird.core.android.account.LegacyAccountDto
28+
import net.thunderbird.core.android.account.AccountDefaultsProvider
29+
import net.thunderbird.core.android.account.LegacyAccount
30+
import net.thunderbird.core.android.account.LegacyAccountManager
2831
import net.thunderbird.core.common.mail.Protocols
2932
import net.thunderbird.core.featureflag.FeatureFlagProvider
3033
import net.thunderbird.core.featureflag.keys.GeneratedFeatureFlagKey
3134
import net.thunderbird.feature.account.AccountId
3235
import net.thunderbird.feature.account.avatar.AvatarMonogramCreator
3336
import net.thunderbird.feature.account.storage.profile.AvatarDto
3437
import net.thunderbird.feature.account.storage.profile.AvatarTypeDto
38+
import net.thunderbird.feature.account.storage.profile.ProfileDto
3539
import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
3640
import net.thunderbird.legacy.logging.Log
3741

@@ -42,12 +46,14 @@ private const val TAG = "AccountCreator"
4246
internal class AccountCreator(
4347
private val accountColorPicker: AccountColorPicker,
4448
private val localFoldersCreator: SpecialLocalFoldersCreator,
45-
private val preferences: Preferences,
49+
private val accountManager: LegacyAccountManager,
4650
private val context: Context,
4751
private val messagingController: MessagingController,
52+
private val backendManager: BackendManager,
4853
private val deletePolicyProvider: DeletePolicyProvider,
4954
private val avatarMonogramCreator: AvatarMonogramCreator,
5055
private val unifiedInboxConfigurator: UnifiedInboxConfigurator,
56+
private val accountDefaultsProvider: AccountDefaultsProvider,
5157
private val featureFlagProvider: FeatureFlagProvider,
5258
private val getFolderIdsForTypeUseCase: GetFolderIdsForTypeUseCase,
5359
private val setPushForFolderUseCase: SetPushForFolderUseCase,
@@ -65,54 +71,76 @@ internal class AccountCreator(
6571
}
6672
}
6773

68-
private suspend fun create(account: Account): AccountId {
69-
val newAccount = preferences.newAccount(account.id)
70-
71-
newAccount.email = account.emailAddress
72-
73-
newAccount.avatar = AvatarDto(
74-
avatarType = AvatarTypeDto.MONOGRAM,
75-
avatarMonogram = avatarMonogramCreator.create(
76-
name = account.options.accountName,
74+
internal suspend fun createLegacyAccount(account: Account): LegacyAccount {
75+
val accountId = account.id
76+
val profile = ProfileDto(
77+
id = accountId,
78+
name = account.options.accountName,
79+
color = accountColorPicker.pickColor(),
80+
avatar = AvatarDto(
81+
accountId = accountId,
82+
avatarType = AvatarTypeDto.MONOGRAM,
83+
avatarMonogram = avatarMonogramCreator.create(
84+
name = account.options.accountName,
85+
email = account.emailAddress,
86+
),
87+
avatarImageUri = null,
88+
avatarIconName = null,
89+
),
90+
)
91+
val defaultAccount = accountDefaultsProvider.applyDefaults(
92+
LegacyAccount(
93+
id = accountId,
94+
name = account.options.accountName.takeIf { it.isNotEmpty() },
7795
email = account.emailAddress,
96+
profile = profile,
97+
incomingServerSettings = account.incomingServerSettings,
98+
outgoingServerSettings = account.outgoingServerSettings,
99+
identities = emptyList(),
78100
),
79-
avatarImageUri = null,
80-
avatarIconName = null,
81101
)
102+
val identity = defaultAccount.identities.first().copy(
103+
name = account.options.displayName,
104+
email = account.emailAddress,
105+
signatureUse = account.options.emailSignature != null,
106+
signature = account.options.emailSignature,
107+
)
108+
return defaultAccount.copy(
109+
identities = listOf(identity),
110+
senderName = identity.name,
111+
signatureUse = identity.signatureUse,
112+
signature = identity.signature,
113+
oAuthState = account.authorizationState,
114+
isNotifyNewMail = account.options.showNotification,
115+
automaticCheckIntervalMinutes = account.options.checkFrequencyInMinutes,
116+
displayCount = account.options.messageDisplayCount,
117+
).setIncomingServerSettings(account.incomingServerSettings)
118+
}
82119

83-
newAccount.setIncomingServerSettings(account.incomingServerSettings)
84-
newAccount.outgoingServerSettings = account.outgoingServerSettings
85-
86-
newAccount.oAuthState = account.authorizationState
87-
88-
newAccount.name = account.options.accountName
89-
newAccount.senderName = account.options.displayName
90-
if (account.options.emailSignature != null) {
91-
newAccount.signatureUse = true
92-
newAccount.signature = account.options.emailSignature
93-
}
94-
newAccount.isNotifyNewMail = account.options.showNotification
95-
newAccount.automaticCheckIntervalMinutes = account.options.checkFrequencyInMinutes
96-
newAccount.displayCount = account.options.messageDisplayCount
97-
98-
newAccount.deletePolicy = deletePolicyProvider.getDeletePolicy(newAccount.incomingServerSettings.type)
99-
newAccount.chipColor = accountColorPicker.pickColor()
120+
private suspend fun create(account: Account): AccountId {
121+
var newAccount = createLegacyAccount(account)
100122

101-
localFoldersCreator.createSpecialLocalFolders(newAccount)
123+
// this needs the updated incoming server settings
124+
newAccount = newAccount.copy(
125+
deletePolicy = deletePolicyProvider.getDeletePolicy(newAccount.incomingServerSettings.type),
126+
)
102127

103-
account.specialFolderSettings?.let { specialFolderSettings ->
104-
newAccount.setSpecialFolders(specialFolderSettings)
105-
}
128+
accountManager.createSync(newAccount)
129+
localFoldersCreator.createSpecialLocalFolders(newAccount.id)
106130

107-
newAccount.markSetupFinished()
131+
newAccount = accountManager.updateSync(newAccount.id) { createdAccount ->
132+
val accountWithSpecialFolders = account.specialFolderSettings?.let { specialFolderSettings ->
133+
createdAccount.setSpecialFolders(specialFolderSettings)
134+
} ?: createdAccount
108135

109-
preferences.saveAccount(newAccount)
136+
accountWithSpecialFolders.copy(isFinishedSetup = true)
137+
} ?: error("Account not found after creating local folders")
110138

111139
unifiedInboxConfigurator.configureUnifiedInbox()
112140

113141
Core.setServicesEnabled(context)
114142

115-
messagingController.refreshFolderListBlocking(newAccount)
143+
refreshInitialFolderList(newAccount.id)
116144

117145
featureFlagProvider.provide(GeneratedFeatureFlagKey.PUSH_ENABLED_ON_INBOX_BY_DEFAULT)
118146
.onEnabled {
@@ -127,33 +155,54 @@ internal class AccountCreator(
127155
}
128156

129157
if (account.options.checkFrequencyInMinutes == -1) {
130-
messagingController.checkMail(newAccount, false, true, false, null)
158+
messagingController.checkMail(newAccount.id, false, true, false, null)
131159
}
132160

133161
return newAccount.id
134162
}
135163

164+
/**
165+
* Refreshes folders during setup without saving the account snapshot taken before the refresh.
166+
*/
167+
@VisibleForTesting
168+
internal fun refreshInitialFolderList(accountId: AccountId) {
169+
val folderPathDelimiter = backendManager.getBackend(accountId).refreshFolderList()
170+
val now = System.currentTimeMillis()
171+
accountManager.updateSync(accountId) { account ->
172+
val updatedAccount = if (
173+
!folderPathDelimiter.isNullOrEmpty() && folderPathDelimiter != account.folderPathDelimiter
174+
) {
175+
account.updateFolderDelimiter(folderPathDelimiter)
176+
} else {
177+
account
178+
}
179+
updatedAccount.updateLastFolderListRefreshTime(now)
180+
} ?: error("Account not found after refreshing folder list")
181+
}
182+
136183
/**
137184
* Set special folders by name.
138185
*
139186
* Since the folder list hasn't been synced yet, we don't have database IDs for the folders. So we use the same
140187
* mechanism that is used when importing settings. See [com.fsck.k9.mailstore.SpecialFolderUpdater] for details.
141188
*/
142-
private fun LegacyAccountDto.setSpecialFolders(specialFolders: SpecialFolderSettings) {
143-
importedArchiveFolder = specialFolders.archiveSpecialFolderOption.toFolderServerId()
144-
archiveFolderSelection = specialFolders.archiveSpecialFolderOption.toFolderSelection()
189+
private fun LegacyAccount.setSpecialFolders(specialFolders: SpecialFolderSettings): LegacyAccount {
190+
return copy(
191+
importedArchiveFolder = specialFolders.archiveSpecialFolderOption.toFolderServerId(),
192+
archiveFolderSelection = specialFolders.archiveSpecialFolderOption.toFolderSelection(),
145193

146-
importedDraftsFolder = specialFolders.draftsSpecialFolderOption.toFolderServerId()
147-
draftsFolderSelection = specialFolders.draftsSpecialFolderOption.toFolderSelection()
194+
importedDraftsFolder = specialFolders.draftsSpecialFolderOption.toFolderServerId(),
195+
draftsFolderSelection = specialFolders.draftsSpecialFolderOption.toFolderSelection(),
148196

149-
importedSentFolder = specialFolders.sentSpecialFolderOption.toFolderServerId()
150-
sentFolderSelection = specialFolders.sentSpecialFolderOption.toFolderSelection()
197+
importedSentFolder = specialFolders.sentSpecialFolderOption.toFolderServerId(),
198+
sentFolderSelection = specialFolders.sentSpecialFolderOption.toFolderSelection(),
151199

152-
importedSpamFolder = specialFolders.spamSpecialFolderOption.toFolderServerId()
153-
spamFolderSelection = specialFolders.spamSpecialFolderOption.toFolderSelection()
200+
importedSpamFolder = specialFolders.spamSpecialFolderOption.toFolderServerId(),
201+
spamFolderSelection = specialFolders.spamSpecialFolderOption.toFolderSelection(),
154202

155-
importedTrashFolder = specialFolders.trashSpecialFolderOption.toFolderServerId()
156-
trashFolderSelection = specialFolders.trashSpecialFolderOption.toFolderSelection()
203+
importedTrashFolder = specialFolders.trashSpecialFolderOption.toFolderServerId(),
204+
trashFolderSelection = specialFolders.trashSpecialFolderOption.toFolderSelection(),
205+
)
157206
}
158207

159208
private fun SpecialFolderOption.toFolderServerId(): String? {
@@ -181,17 +230,21 @@ internal class AccountCreator(
181230
}
182231
}
183232

184-
private fun LegacyAccountDto.setIncomingServerSettings(serverSettings: ServerSettings) {
185-
if (serverSettings.type == Protocols.IMAP) {
186-
useCompression = serverSettings.isUseCompression
187-
isSendClientInfoEnabled = serverSettings.isSendClientInfo
188-
incomingServerSettings = serverSettings.copy(
189-
extra = createExtra(
190-
autoDetectNamespace = serverSettings.autoDetectNamespace,
191-
pathPrefix = serverSettings.pathPrefix,
233+
private fun LegacyAccount.setIncomingServerSettings(serverSettings: ServerSettings): LegacyAccount {
234+
return if (serverSettings.type == Protocols.IMAP) {
235+
copy(
236+
useCompression = serverSettings.isUseCompression,
237+
isSendClientInfoEnabled = serverSettings.isSendClientInfo,
238+
incomingServerSettings = serverSettings.copy(
239+
extra = createExtra(
240+
autoDetectNamespace = serverSettings.autoDetectNamespace,
241+
pathPrefix = serverSettings.pathPrefix,
242+
),
192243
),
193244
)
194245
} else {
195-
incomingServerSettings = serverSettings
246+
copy(
247+
incomingServerSettings = serverSettings,
248+
)
196249
}
197250
}

‎app-common/src/main/kotlin/net/thunderbird/app/common/account/AppCommonAccountModule.kt‎

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@ import androidx.compose.ui.graphics.vector.ImageVector
44
import app.k9mail.feature.account.setup.AccountSetupExternalContract
55
import kotlinx.collections.immutable.ImmutableList
66
import kotlinx.collections.immutable.toImmutableList
7+
import net.thunderbird.app.common.account.data.AccountDisplayOrderManager
8+
import net.thunderbird.app.common.account.data.AccountFolderUpdater
9+
import net.thunderbird.app.common.account.data.AccountLocalDataSource
10+
import net.thunderbird.app.common.account.data.DefaultAccountDisplayOrderManager
711
import net.thunderbird.app.common.account.data.DefaultAccountProfileLocalDataSource
812
import net.thunderbird.app.common.account.data.DefaultLegacyAccountManager
913
import net.thunderbird.app.common.account.data.DefaultLegacyAccountRepository
14+
import net.thunderbird.app.common.account.data.LegacyAccountCache
15+
import net.thunderbird.app.common.account.data.LegacyAccountFolderUpdater
16+
import net.thunderbird.app.common.account.data.LegacyAccountLocalDataSource
17+
import net.thunderbird.app.common.account.data.LegacyInMemoryAccountCache
1018
import net.thunderbird.core.android.account.AccountDefaultsProvider
1119
import net.thunderbird.core.android.account.LegacyAccount
1220
import net.thunderbird.core.android.account.LegacyAccountManager
@@ -18,23 +26,49 @@ import net.thunderbird.feature.account.avatar.DefaultAvatarIconCatalog
1826
import net.thunderbird.feature.account.avatar.DefaultAvatarMonogramCreator
1927
import net.thunderbird.feature.account.core.AccountCoreExternalContract.AccountProfileLocalDataSource
2028
import net.thunderbird.feature.account.storage.legacy.featureAccountStorageLegacyModule
21-
import net.thunderbird.feature.mail.account.api.AccountManager
2229
import org.koin.android.ext.koin.androidApplication
2330
import org.koin.android.ext.koin.androidContext
2431
import org.koin.core.qualifier.named
25-
import org.koin.dsl.binds
32+
import org.koin.dsl.bind
2633
import org.koin.dsl.module
2734
import app.k9mail.core.ui.legacy.theme2.common.R as ThemeCommonR
2835

2936
internal val appCommonAccountModule = module {
3037
includes(featureAccountStorageLegacyModule)
3138

32-
single<AccountManager<LegacyAccount>> {
39+
single<AccountLocalDataSource> {
40+
LegacyAccountLocalDataSource(
41+
storageProvider = get(),
42+
accountStorageHandler = get(),
43+
accountDefaultsProvider = get(),
44+
)
45+
}
46+
47+
single<LegacyAccountCache> {
48+
LegacyInMemoryAccountCache()
49+
}
50+
51+
single<AccountDisplayOrderManager> {
52+
DefaultAccountDisplayOrderManager(
53+
storageProvider = get(),
54+
)
55+
}
56+
57+
single<AccountFolderUpdater> {
58+
LegacyAccountFolderUpdater(
59+
localStoreProvider = get(),
60+
logger = get(),
61+
)
62+
}
63+
64+
single {
3365
DefaultLegacyAccountManager(
34-
accountManager = get(),
35-
accountDataMapper = get(),
66+
accountStorage = get(),
67+
accountCache = get(),
68+
accountDisplayOrderManager = get(),
69+
accountFolderUpdater = get(),
3670
)
37-
} binds arrayOf(LegacyAccountManager::class)
71+
} bind LegacyAccountManager::class
3872

3973
single<AccountProfileLocalDataSource> {
4074
DefaultAccountProfileLocalDataSource(
@@ -81,12 +115,14 @@ internal val appCommonAccountModule = module {
81115
AccountCreator(
82116
accountColorPicker = get(),
83117
localFoldersCreator = get(),
84-
preferences = get(),
118+
accountManager = get(),
85119
context = androidApplication(),
86-
deletePolicyProvider = get(),
87120
messagingController = get(),
121+
backendManager = get(),
122+
deletePolicyProvider = get(),
88123
avatarMonogramCreator = get(),
89124
unifiedInboxConfigurator = get(),
125+
accountDefaultsProvider = get(),
90126
featureFlagProvider = get(),
91127
getFolderIdsForTypeUseCase = get(),
92128
setPushForFolderUseCase = get(),

0 commit comments

Comments
 (0)