Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:56:56 +01:00
parent 75dc487a7a
commit 39c29d175b
6317 changed files with 388324 additions and 2 deletions

View file

@ -0,0 +1,26 @@
plugins {
id(ThunderbirdPlugins.Library.android)
alias(libs.plugins.kotlin.parcelize)
}
android {
namespace = "net.thunderbird.core.android.account"
}
dependencies {
api(projects.feature.account.api)
api(projects.feature.account.storage.api)
implementation(projects.feature.notification.api)
api(projects.mail.common)
implementation(projects.core.common)
implementation(projects.core.preference.api)
implementation(projects.feature.mail.account.api)
implementation(projects.feature.mail.folder.api)
implementation(projects.backend.api)
testImplementation(projects.feature.account.fake)
}

View file

@ -0,0 +1,60 @@
package net.thunderbird.core.android.account
import net.thunderbird.core.preference.storage.Storage
interface AccountDefaultsProvider {
/**
* Apply default values to the account.
*
* This method should only be called when creating a new account.
*/
fun applyDefaults(account: LegacyAccount)
/**
* Apply any additional default values to the account.
*
* This method should be called when updating an existing account.
*/
fun applyOverwrites(account: LegacyAccount, storage: Storage)
companion object {
const val DEFAULT_MAXIMUM_AUTO_DOWNLOAD_MESSAGE_SIZE = 131072
@JvmStatic
val DEFAULT_MESSAGE_FORMAT = MessageFormat.HTML
const val DEFAULT_MESSAGE_FORMAT_AUTO = false
const val DEFAULT_MESSAGE_READ_RECEIPT = false
const val DEFAULT_QUOTED_TEXT_SHOWN = true
const val DEFAULT_QUOTE_PREFIX = ">"
@JvmStatic
val DEFAULT_QUOTE_STYLE = QuoteStyle.PREFIX
const val DEFAULT_REMOTE_SEARCH_NUM_RESULTS = 25
const val DEFAULT_REPLY_AFTER_QUOTE = false
const val DEFAULT_RINGTONE_URI = "content://settings/system/notification_sound"
const val DEFAULT_SORT_ASCENDING = false
@JvmStatic
val DEFAULT_SORT_TYPE = SortType.SORT_DATE
const val DEFAULT_STRIP_SIGNATURE = true
const val DEFAULT_SYNC_INTERVAL = 60
/**
* Specifies how many messages will be shown in a folder by default. This number is set
* on each new folder and can be incremented with "Load more messages..." by the
* VISIBLE_LIMIT_INCREMENT
*/
const val DEFAULT_VISIBLE_LIMIT = 25
const val NO_OPENPGP_KEY: Long = 0
const val UNASSIGNED_ACCOUNT_NUMBER = -1
// TODO : Remove once storage is migrated to new format
const val COLOR = 0x0099CC
}
}

View file

@ -0,0 +1,24 @@
package net.thunderbird.core.android.account
import kotlinx.coroutines.flow.Flow
import net.thunderbird.feature.mail.account.api.AccountManager
@Deprecated(
message = "Use net.thunderbird.feature.mail.account.api.AccountManager<TAccount : BaseAccount> instead",
replaceWith = ReplaceWith(
expression = "AccountManager<LegacyAccount>",
"net.thunderbird.feature.mail.account.api.AccountManager",
"app.k9mail.legacy.account.LegacyAccount",
),
)
interface AccountManager : AccountManager<LegacyAccount> {
override fun getAccounts(): List<LegacyAccount>
override fun getAccountsFlow(): Flow<List<LegacyAccount>>
override fun getAccount(accountUuid: String): LegacyAccount?
override fun getAccountFlow(accountUuid: String): Flow<LegacyAccount?>
fun addAccountRemovedListener(listener: AccountRemovedListener)
override fun moveAccount(account: LegacyAccount, newPosition: Int)
fun addOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener)
fun removeOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener)
override fun saveAccount(account: LegacyAccount)
}

View file

@ -0,0 +1,5 @@
package net.thunderbird.core.android.account
fun interface AccountRemovedListener {
fun onAccountRemoved(account: LegacyAccount)
}

View file

@ -0,0 +1,5 @@
package net.thunderbird.core.android.account
fun interface AccountsChangeListener {
fun onAccountsChanged()
}

View file

@ -0,0 +1,16 @@
package net.thunderbird.core.android.account
@Suppress("MagicNumber")
enum class DeletePolicy(@JvmField val setting: Int) {
NEVER(0),
SEVEN_DAYS(1),
ON_DELETE(2),
MARK_AS_READ(3),
;
companion object {
fun fromInt(initialSetting: Int): DeletePolicy {
return entries.find { it.setting == initialSetting } ?: error("DeletePolicy $initialSetting unknown")
}
}
}

View file

@ -0,0 +1,16 @@
package net.thunderbird.core.android.account
import com.fsck.k9.backend.api.SyncConfig.ExpungePolicy
enum class Expunge {
EXPUNGE_IMMEDIATELY,
EXPUNGE_MANUALLY,
EXPUNGE_ON_POLL,
;
fun toBackendExpungePolicy(): ExpungePolicy = when (this) {
EXPUNGE_IMMEDIATELY -> ExpungePolicy.IMMEDIATELY
EXPUNGE_MANUALLY -> ExpungePolicy.MANUALLY
EXPUNGE_ON_POLL -> ExpungePolicy.ON_POLL
}
}

View file

@ -0,0 +1,9 @@
package net.thunderbird.core.android.account
enum class FolderMode {
NONE,
ALL,
FIRST_CLASS,
FIRST_AND_SECOND_CLASS,
NOT_SECOND_CLASS,
}

View file

@ -0,0 +1,20 @@
package net.thunderbird.core.android.account
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class Identity(
val description: String? = null,
val name: String? = null,
val email: String? = null,
val signature: String? = null,
val signatureUse: Boolean = false,
val replyTo: String? = null,
) : Parcelable {
// TODO remove when callers are converted to Kotlin
fun withName(name: String?) = copy(name = name)
fun withSignature(signature: String?) = copy(signature = signature)
fun withSignatureUse(signatureUse: Boolean) = copy(signatureUse = signatureUse)
fun withEmail(email: String?) = copy(email = email)
}

View file

@ -0,0 +1,636 @@
package net.thunderbird.core.android.account
import com.fsck.k9.mail.Address
import com.fsck.k9.mail.ServerSettings
import java.util.Calendar
import java.util.Date
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.NO_OPENPGP_KEY
import net.thunderbird.feature.account.Account
import net.thunderbird.feature.account.AccountId
import net.thunderbird.feature.account.AccountIdFactory
import net.thunderbird.feature.account.storage.profile.AvatarDto
import net.thunderbird.feature.account.storage.profile.AvatarTypeDto
import net.thunderbird.feature.mail.account.api.BaseAccount
import net.thunderbird.feature.mail.folder.api.FolderPathDelimiter
import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
import net.thunderbird.feature.notification.NotificationSettings
// This needs to be in sync with K9.DEFAULT_VISIBLE_LIMIT
const val DEFAULT_VISIBLE_LIMIT = 25
/**
* Account stores all of the settings for a single account defined by the user. Each account is defined by a UUID.
*/
@Deprecated("Use LegacyAccountWrapper instead")
@Suppress("TooManyFunctions")
open class LegacyAccount(
override val uuid: String,
val isSensitiveDebugLoggingEnabled: () -> Boolean = { false },
) : Account, BaseAccount {
// [Account]
override val id: AccountId = AccountIdFactory.of(uuid)
// [BaseAccount]
@get:Synchronized
@set:Synchronized
override var name: String? = null
set(value) {
field = value?.takeIf { it.isNotEmpty() }
}
@get:Synchronized
@set:Synchronized
override var email: String
get() = identities[0].email!!
set(email) {
val newIdentity = identities[0].copy(email = email)
identities[0] = newIdentity
}
// [AccountProfile]
val displayName: String
get() = name ?: email
@get:Synchronized
@set:Synchronized
var chipColor = 0
@get:Synchronized
@set:Synchronized
var avatar: AvatarDto = AvatarDto(
avatarType = AvatarTypeDto.MONOGRAM,
avatarMonogram = null,
avatarImageUri = null,
avatarIconName = null,
)
// Uncategorized
@get:Synchronized
@set:Synchronized
var deletePolicy = DeletePolicy.NEVER
@get:Synchronized
@set:Synchronized
private var internalIncomingServerSettings: ServerSettings? = null
@get:Synchronized
@set:Synchronized
private var internalOutgoingServerSettings: ServerSettings? = null
var incomingServerSettings: ServerSettings
get() = internalIncomingServerSettings ?: error("Incoming server settings not set yet")
set(value) {
internalIncomingServerSettings = value
}
var outgoingServerSettings: ServerSettings
get() = internalOutgoingServerSettings ?: error("Outgoing server settings not set yet")
set(value) {
internalOutgoingServerSettings = value
}
@get:Synchronized
@set:Synchronized
var oAuthState: String? = null
@get:Synchronized
@set:Synchronized
var alwaysBcc: String? = null
/**
* -1 for never.
*/
@get:Synchronized
@set:Synchronized
var automaticCheckIntervalMinutes = 0
@get:Synchronized
@set:Synchronized
var displayCount = 0
set(value) {
if (field != value) {
field = value.takeIf { it != -1 } ?: DEFAULT_VISIBLE_LIMIT
isChangedVisibleLimits = true
}
}
@get:Synchronized
@set:Synchronized
var isNotifyNewMail = false
@get:Synchronized
@set:Synchronized
var folderNotifyNewMailMode = FolderMode.ALL
@get:Synchronized
@set:Synchronized
var isNotifySelfNewMail = false
@get:Synchronized
@set:Synchronized
var isNotifyContactsMailOnly = false
@get:Synchronized
@set:Synchronized
var isIgnoreChatMessages = false
@get:Synchronized
@set:Synchronized
var legacyInboxFolder: String? = null
@get:Synchronized
@set:Synchronized
var importedDraftsFolder: String? = null
@get:Synchronized
@set:Synchronized
var importedSentFolder: String? = null
@get:Synchronized
@set:Synchronized
var importedTrashFolder: String? = null
@get:Synchronized
@set:Synchronized
var importedArchiveFolder: String? = null
@get:Synchronized
@set:Synchronized
var importedSpamFolder: String? = null
@get:Synchronized
@set:Synchronized
var inboxFolderId: Long? = null
@get:Synchronized
@set:Synchronized
var draftsFolderId: Long? = null
@get:Synchronized
@set:Synchronized
var sentFolderId: Long? = null
@get:Synchronized
@set:Synchronized
var trashFolderId: Long? = null
@get:Synchronized
@set:Synchronized
var archiveFolderId: Long? = null
@get:Synchronized
@set:Synchronized
var spamFolderId: Long? = null
@get:Synchronized
var draftsFolderSelection = SpecialFolderSelection.AUTOMATIC
@get:Synchronized
var sentFolderSelection = SpecialFolderSelection.AUTOMATIC
@get:Synchronized
var trashFolderSelection = SpecialFolderSelection.AUTOMATIC
@get:Synchronized
var archiveFolderSelection = SpecialFolderSelection.AUTOMATIC
@get:Synchronized
var spamFolderSelection = SpecialFolderSelection.AUTOMATIC
@get:Synchronized
@set:Synchronized
var importedAutoExpandFolder: String? = null
@get:Synchronized
@set:Synchronized
var autoExpandFolderId: Long? = null
@get:Synchronized
@set:Synchronized
var folderDisplayMode = FolderMode.NOT_SECOND_CLASS
@get:Synchronized
@set:Synchronized
var folderSyncMode = FolderMode.FIRST_CLASS
@get:Synchronized
@set:Synchronized
var folderPushMode = FolderMode.NONE
@get:Synchronized
@set:Synchronized
var accountNumber = 0
@get:Synchronized
@set:Synchronized
var isNotifySync = false
@get:Synchronized
@set:Synchronized
var sortType: SortType = SortType.SORT_DATE
var sortAscending: MutableMap<SortType, Boolean> = mutableMapOf()
@get:Synchronized
@set:Synchronized
var showPictures = ShowPictures.NEVER
@get:Synchronized
@set:Synchronized
var isSignatureBeforeQuotedText = false
@get:Synchronized
@set:Synchronized
var expungePolicy = Expunge.EXPUNGE_IMMEDIATELY
@get:Synchronized
@set:Synchronized
var maxPushFolders = 0
@get:Synchronized
@set:Synchronized
var idleRefreshMinutes = 0
@get:JvmName("useCompression")
@get:Synchronized
@set:Synchronized
var useCompression = true
@get:Synchronized
@set:Synchronized
var isSendClientInfoEnabled = true
@get:Synchronized
@set:Synchronized
var isSubscribedFoldersOnly = false
@get:Synchronized
@set:Synchronized
var maximumPolledMessageAge = 0
@get:Synchronized
@set:Synchronized
var maximumAutoDownloadMessageSize = 0
@get:Synchronized
@set:Synchronized
var messageFormat = MessageFormat.HTML
@get:Synchronized
@set:Synchronized
var isMessageFormatAuto = false
@get:Synchronized
@set:Synchronized
var isMessageReadReceipt = false
@get:Synchronized
@set:Synchronized
var quoteStyle = QuoteStyle.PREFIX
@get:Synchronized
@set:Synchronized
var quotePrefix: String? = null
@get:Synchronized
@set:Synchronized
var isDefaultQuotedTextShown = false
@get:Synchronized
@set:Synchronized
var isReplyAfterQuote = false
@get:Synchronized
@set:Synchronized
var isStripSignature = false
@get:Synchronized
@set:Synchronized
var isSyncRemoteDeletions = false
@get:Synchronized
@set:Synchronized
var openPgpProvider: String? = null
set(value) {
field = value?.takeIf { it.isNotEmpty() }
}
@get:Synchronized
@set:Synchronized
var openPgpKey: Long = 0
@get:Synchronized
@set:Synchronized
var autocryptPreferEncryptMutual = false
@get:Synchronized
@set:Synchronized
var isOpenPgpHideSignOnly = false
@get:Synchronized
@set:Synchronized
var isOpenPgpEncryptSubject = false
@get:Synchronized
@set:Synchronized
var isOpenPgpEncryptAllDrafts = false
@get:Synchronized
@set:Synchronized
var isMarkMessageAsReadOnView = false
@get:Synchronized
@set:Synchronized
var isMarkMessageAsReadOnDelete = false
@get:Synchronized
@set:Synchronized
var isAlwaysShowCcBcc = false
// Temporarily disabled
@get:Synchronized
@set:Synchronized
var isRemoteSearchFullText = false
get() = false
@get:Synchronized
@set:Synchronized
var remoteSearchNumResults = 0
set(value) {
field = value.coerceAtLeast(0)
}
@get:Synchronized
@set:Synchronized
var isUploadSentMessages = false
@get:Synchronized
@set:Synchronized
var lastSyncTime: Long = 0
@get:Synchronized
@set:Synchronized
var lastFolderListRefreshTime: Long = 0
@get:Synchronized
var isFinishedSetup = false
@get:Synchronized
@set:Synchronized
var messagesNotificationChannelVersion = 0
@get:Synchronized
@set:Synchronized
var isChangedVisibleLimits = false
/**
* Database ID of the folder that was last selected for a copy or move operation.
*
* Note: For now this value isn't persisted. So it will be reset when K-9 Mail is restarted.
*/
@get:Synchronized
var lastSelectedFolderId: Long? = null
@get:Synchronized
@set:Synchronized
var identities: MutableList<Identity> = mutableListOf()
set(value) {
field = value.toMutableList()
}
@get:Synchronized
var notificationSettings = NotificationSettings()
@get:Synchronized
@set:Synchronized
var senderName: String?
get() = identities[0].name
set(name) {
val newIdentity = identities[0].withName(name)
identities[0] = newIdentity
}
@get:Synchronized
@set:Synchronized
var signatureUse: Boolean
get() = identities[0].signatureUse
set(signatureUse) {
val newIdentity = identities[0].withSignatureUse(signatureUse)
identities[0] = newIdentity
}
@get:Synchronized
@set:Synchronized
var signature: String?
get() = identities[0].signature
set(signature) {
val newIdentity = identities[0].withSignature(signature)
identities[0] = newIdentity
}
@get:JvmName("shouldMigrateToOAuth")
@get:Synchronized
@set:Synchronized
var shouldMigrateToOAuth = false
@get:JvmName("folderPathDelimiter")
@get:Synchronized
@set:Synchronized
var folderPathDelimiter: FolderPathDelimiter = "/"
/**
* @param automaticCheckIntervalMinutes or -1 for never.
*/
@Synchronized
fun updateAutomaticCheckIntervalMinutes(automaticCheckIntervalMinutes: Int): Boolean {
val oldInterval = this.automaticCheckIntervalMinutes
this.automaticCheckIntervalMinutes = automaticCheckIntervalMinutes
return oldInterval != automaticCheckIntervalMinutes
}
@Synchronized
fun setDraftsFolderId(folderId: Long?, selection: SpecialFolderSelection) {
draftsFolderId = folderId
draftsFolderSelection = selection
}
@Deprecated("use AccountWrapper instead")
@Synchronized
fun hasDraftsFolder(): Boolean {
return draftsFolderId != null
}
@Synchronized
fun setSentFolderId(folderId: Long?, selection: SpecialFolderSelection) {
sentFolderId = folderId
sentFolderSelection = selection
}
@Deprecated("use AccountWrapper instead")
@Synchronized
fun hasSentFolder(): Boolean {
return sentFolderId != null
}
@Synchronized
fun setTrashFolderId(folderId: Long?, selection: SpecialFolderSelection) {
trashFolderId = folderId
trashFolderSelection = selection
}
@Deprecated("use AccountWrapper instead")
@Synchronized
fun hasTrashFolder(): Boolean {
return trashFolderId != null
}
@Synchronized
fun setArchiveFolderId(folderId: Long?, selection: SpecialFolderSelection) {
archiveFolderId = folderId
archiveFolderSelection = selection
}
@Deprecated("use AccountWrapper instead")
@Synchronized
fun hasArchiveFolder(): Boolean {
return archiveFolderId != null
}
@Synchronized
fun setSpamFolderId(folderId: Long?, selection: SpecialFolderSelection) {
spamFolderId = folderId
spamFolderSelection = selection
}
@Deprecated("use AccountWrapper instead")
@Synchronized
fun hasSpamFolder(): Boolean {
return spamFolderId != null
}
@Synchronized
fun isSortAscending(sortType: SortType): Boolean {
return sortAscending.getOrPut(sortType) { sortType.isDefaultAscending }
}
@Synchronized
fun setSortAscending(sortType: SortType, sortAscending: Boolean) {
this.sortAscending[sortType] = sortAscending
}
@Synchronized
fun replaceIdentities(identities: List<Identity>) {
this.identities = identities.toMutableList()
}
@Synchronized
fun getIdentity(index: Int): Identity {
if (index !in identities.indices) error("Identity with index $index not found")
return identities[index]
}
fun isAnIdentity(addresses: Array<Address>?): Boolean {
if (addresses == null) return false
return addresses.any { address -> isAnIdentity(address) }
}
fun isAnIdentity(address: Address): Boolean {
return findIdentity(address) != null
}
@Synchronized
fun findIdentity(address: Address): Identity? {
return identities.find { identity ->
identity.email.equals(address.address, ignoreCase = true)
}
}
@Suppress("MagicNumber")
val earliestPollDate: Date?
get() {
val age = maximumPolledMessageAge.takeIf { it >= 0 } ?: return null
val now = Calendar.getInstance()
now[Calendar.HOUR_OF_DAY] = 0
now[Calendar.MINUTE] = 0
now[Calendar.SECOND] = 0
now[Calendar.MILLISECOND] = 0
if (age < 28) {
now.add(Calendar.DATE, age * -1)
} else {
when (age) {
28 -> now.add(Calendar.MONTH, -1)
56 -> now.add(Calendar.MONTH, -2)
84 -> now.add(Calendar.MONTH, -3)
168 -> now.add(Calendar.MONTH, -6)
365 -> now.add(Calendar.YEAR, -1)
}
}
return now.time
}
@Deprecated("use AccountWrapper instead")
val isOpenPgpProviderConfigured: Boolean
get() = openPgpProvider != null
@Deprecated("use AccountWrapper instead")
@Synchronized
fun hasOpenPgpKey(): Boolean {
return openPgpKey != NO_OPENPGP_KEY
}
@Synchronized
fun setLastSelectedFolderId(folderId: Long) {
lastSelectedFolderId = folderId
}
@Synchronized
fun resetChangeMarkers() {
isChangedVisibleLimits = false
}
@Synchronized
fun markSetupFinished() {
isFinishedSetup = true
}
@Synchronized
fun updateNotificationSettings(
block: (
oldNotificationSettings: NotificationSettings,
) -> NotificationSettings,
) {
notificationSettings = block(notificationSettings)
}
override fun toString(): String {
return if (isSensitiveDebugLoggingEnabled()) displayName else uuid
}
override fun equals(other: Any?): Boolean {
return if (other is LegacyAccount) {
other.uuid == uuid
} else {
super.equals(other)
}
}
override fun hashCode(): Int {
return uuid.hashCode()
}
companion object {
/**
* Fixed name of outbox - not actually displayed.
*/
const val OUTBOX_NAME = "Outbox"
const val INTERVAL_MINUTES_NEVER = -1
}
}

View file

@ -0,0 +1,152 @@
package net.thunderbird.core.android.account
import com.fsck.k9.mail.ServerSettings
import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.NO_OPENPGP_KEY
import net.thunderbird.core.common.mail.Protocols
import net.thunderbird.feature.account.Account
import net.thunderbird.feature.account.AccountId
import net.thunderbird.feature.account.storage.profile.ProfileDto
import net.thunderbird.feature.mail.account.api.BaseAccount
import net.thunderbird.feature.mail.folder.api.FolderPathDelimiter
import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
import net.thunderbird.feature.notification.NotificationSettings
/**
* A immutable wrapper for the [LegacyAccount] class.
*
* This class is used to store the account data in a way that is safe to pass between threads.
*
* Use LegacyAccountWrapper.from(account) to create a wrapper from an account.
* Use LegacyAccountWrapper.to(wrapper) to create an account from a wrapper.
*/
data class LegacyAccountWrapper(
val isSensitiveDebugLoggingEnabled: () -> Boolean = { false },
// [Account]
override val id: AccountId,
// [BaseAccount]
override val name: String?,
override val email: String,
// [AccountProfile]
val profile: ProfileDto,
// Uncategorized
val deletePolicy: DeletePolicy = DeletePolicy.NEVER,
val incomingServerSettings: ServerSettings,
val outgoingServerSettings: ServerSettings,
val oAuthState: String? = null,
val alwaysBcc: String? = null,
val automaticCheckIntervalMinutes: Int = 0,
val displayCount: Int = 0,
val isNotifyNewMail: Boolean = false,
val folderNotifyNewMailMode: FolderMode = FolderMode.ALL,
val isNotifySelfNewMail: Boolean = false,
val isNotifyContactsMailOnly: Boolean = false,
val isIgnoreChatMessages: Boolean = false,
val legacyInboxFolder: String? = null,
val importedDraftsFolder: String? = null,
val importedSentFolder: String? = null,
val importedTrashFolder: String? = null,
val importedArchiveFolder: String? = null,
val importedSpamFolder: String? = null,
val inboxFolderId: Long? = null,
val draftsFolderId: Long? = null,
val sentFolderId: Long? = null,
val trashFolderId: Long? = null,
val archiveFolderId: Long? = null,
val spamFolderId: Long? = null,
val draftsFolderSelection: SpecialFolderSelection = SpecialFolderSelection.AUTOMATIC,
val sentFolderSelection: SpecialFolderSelection = SpecialFolderSelection.AUTOMATIC,
val trashFolderSelection: SpecialFolderSelection = SpecialFolderSelection.AUTOMATIC,
val archiveFolderSelection: SpecialFolderSelection = SpecialFolderSelection.AUTOMATIC,
val spamFolderSelection: SpecialFolderSelection = SpecialFolderSelection.AUTOMATIC,
val importedAutoExpandFolder: String? = null,
val autoExpandFolderId: Long? = null,
val folderDisplayMode: FolderMode = FolderMode.NOT_SECOND_CLASS,
val folderSyncMode: FolderMode = FolderMode.FIRST_CLASS,
val folderPushMode: FolderMode = FolderMode.NONE,
val accountNumber: Int = 0,
val isNotifySync: Boolean = false,
val sortType: SortType = SortType.SORT_DATE,
val sortAscending: Map<SortType, Boolean> = emptyMap(),
val showPictures: ShowPictures = ShowPictures.NEVER,
val isSignatureBeforeQuotedText: Boolean = false,
val expungePolicy: Expunge = Expunge.EXPUNGE_IMMEDIATELY,
val maxPushFolders: Int = 0,
val idleRefreshMinutes: Int = 0,
val useCompression: Boolean = true,
val isSendClientInfoEnabled: Boolean = true,
val isSubscribedFoldersOnly: Boolean = false,
val maximumPolledMessageAge: Int = 0,
val maximumAutoDownloadMessageSize: Int = 0,
val messageFormat: MessageFormat = MessageFormat.HTML,
val isMessageFormatAuto: Boolean = false,
val isMessageReadReceipt: Boolean = false,
val quoteStyle: QuoteStyle = QuoteStyle.PREFIX,
val quotePrefix: String? = null,
val isDefaultQuotedTextShown: Boolean = false,
val isReplyAfterQuote: Boolean = false,
val isStripSignature: Boolean = false,
val isSyncRemoteDeletions: Boolean = false,
val openPgpProvider: String? = null,
val openPgpKey: Long = 0,
val autocryptPreferEncryptMutual: Boolean = false,
val isOpenPgpHideSignOnly: Boolean = false,
val isOpenPgpEncryptSubject: Boolean = false,
val isOpenPgpEncryptAllDrafts: Boolean = false,
val isMarkMessageAsReadOnView: Boolean = false,
val isMarkMessageAsReadOnDelete: Boolean = false,
val isAlwaysShowCcBcc: Boolean = false,
val isRemoteSearchFullText: Boolean = false,
val remoteSearchNumResults: Int = 0,
val isUploadSentMessages: Boolean = false,
val lastSyncTime: Long = 0,
val lastFolderListRefreshTime: Long = 0,
val isFinishedSetup: Boolean = false,
val messagesNotificationChannelVersion: Int = 0,
val isChangedVisibleLimits: Boolean = false,
val lastSelectedFolderId: Long? = null,
val identities: List<Identity>,
val notificationSettings: NotificationSettings = NotificationSettings(),
val senderName: String? = identities[0].name,
val signatureUse: Boolean = identities[0].signatureUse,
val signature: String? = identities[0].signature,
val shouldMigrateToOAuth: Boolean = false,
val folderPathDelimiter: FolderPathDelimiter = "/",
) : Account, BaseAccount {
override val uuid: String = id.asRaw()
fun hasDraftsFolder(): Boolean {
return draftsFolderId != null
}
fun hasSentFolder(): Boolean {
return sentFolderId != null
}
fun hasTrashFolder(): Boolean {
return trashFolderId != null
}
fun hasArchiveFolder(): Boolean {
return archiveFolderId != null
}
fun hasSpamFolder(): Boolean {
return spamFolderId != null
}
fun isOpenPgpProviderConfigured(): Boolean {
return openPgpProvider != null
}
fun hasOpenPgpKey(): Boolean {
return openPgpKey != NO_OPENPGP_KEY
}
fun isIncomingServerPop3(): Boolean =
incomingServerSettings.type == Protocols.POP3
}

View file

@ -0,0 +1,12 @@
package net.thunderbird.core.android.account
import kotlinx.coroutines.flow.Flow
import net.thunderbird.feature.account.AccountId
interface LegacyAccountWrapperManager {
fun getAll(): Flow<List<LegacyAccountWrapper>>
fun getById(id: AccountId): Flow<LegacyAccountWrapper?>
suspend fun update(account: LegacyAccountWrapper)
}

View file

@ -0,0 +1,7 @@
package net.thunderbird.core.android.account
enum class MessageFormat {
TEXT,
HTML,
AUTO,
}

View file

@ -0,0 +1,6 @@
package net.thunderbird.core.android.account
enum class QuoteStyle {
PREFIX,
HEADER,
}

View file

@ -0,0 +1,7 @@
package net.thunderbird.core.android.account
enum class ShowPictures {
NEVER,
ALWAYS,
ONLY_FROM_CONTACTS,
}

View file

@ -0,0 +1,11 @@
package net.thunderbird.core.android.account
enum class SortType(val isDefaultAscending: Boolean) {
SORT_DATE(false),
SORT_ARRIVAL(false),
SORT_SUBJECT(true),
SORT_SENDER(true),
SORT_UNREAD(true),
SORT_FLAGGED(true),
SORT_ATTACHMENT(true),
}

View file

@ -0,0 +1,195 @@
package net.thunderbird.core.android.account
import assertk.assertThat
import assertk.assertions.isEqualTo
import com.fsck.k9.mail.AuthType
import com.fsck.k9.mail.ConnectionSecurity
import com.fsck.k9.mail.ServerSettings
import kotlin.test.Test
import net.thunderbird.account.fake.FakeAccountData.ACCOUNT_ID
import net.thunderbird.account.fake.FakeAccountProfileData.PROFILE_COLOR
import net.thunderbird.account.fake.FakeAccountProfileData.PROFILE_NAME
import net.thunderbird.feature.account.storage.profile.AvatarDto
import net.thunderbird.feature.account.storage.profile.AvatarTypeDto
import net.thunderbird.feature.account.storage.profile.ProfileDto
import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
import net.thunderbird.feature.notification.NotificationSettings
class LegacyAccountWrapperTest {
@Suppress("LongMethod")
@Test
fun `should set defaults`() {
// arrange
val expected = createAccountWrapper()
// act
val result = LegacyAccountWrapper(
isSensitiveDebugLoggingEnabled = isSensitiveDebugLoggingEnabled,
id = ACCOUNT_ID,
name = PROFILE_NAME,
email = email,
profile = profile,
incomingServerSettings = incomingServerSettings,
outgoingServerSettings = outgoingServerSettings,
identities = identities,
)
// assert
assertThat(expected).isEqualTo(result)
}
private companion object {
val isSensitiveDebugLoggingEnabled = { true }
const val email = "demo@example.com"
val avatar = AvatarDto(
avatarType = AvatarTypeDto.MONOGRAM,
avatarMonogram = null,
avatarImageUri = null,
avatarIconName = null,
)
val profile = ProfileDto(
id = ACCOUNT_ID,
name = PROFILE_NAME,
color = PROFILE_COLOR,
avatar = avatar,
)
val incomingServerSettings = ServerSettings(
type = "imap",
host = "imap.example.com",
port = 993,
connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED,
authenticationType = AuthType.PLAIN,
username = "test",
password = "password",
clientCertificateAlias = null,
)
val outgoingServerSettings = ServerSettings(
type = "smtp",
host = "smtp.example.com",
port = 465,
connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED,
authenticationType = AuthType.PLAIN,
username = "test",
password = "password",
clientCertificateAlias = null,
)
val identities = mutableListOf(
Identity(
email = "demo@example.com",
name = "identityName",
signatureUse = true,
signature = "signature",
description = "Demo User",
),
)
val notificationSettings = NotificationSettings()
@Suppress("LongMethod")
fun createAccountWrapper(): LegacyAccountWrapper {
return LegacyAccountWrapper(
isSensitiveDebugLoggingEnabled = isSensitiveDebugLoggingEnabled,
// [Account]
id = ACCOUNT_ID,
// [BaseAccount]
name = PROFILE_NAME,
email = email,
// [AccountProfile]
profile = profile,
// Uncategorized
deletePolicy = DeletePolicy.NEVER,
incomingServerSettings = incomingServerSettings,
outgoingServerSettings = outgoingServerSettings,
oAuthState = null,
alwaysBcc = null,
automaticCheckIntervalMinutes = 0,
displayCount = 0,
isNotifyNewMail = false,
folderNotifyNewMailMode = FolderMode.ALL,
isNotifySelfNewMail = false,
isNotifyContactsMailOnly = false,
isIgnoreChatMessages = false,
legacyInboxFolder = null,
importedDraftsFolder = null,
importedSentFolder = null,
importedTrashFolder = null,
importedArchiveFolder = null,
importedSpamFolder = null,
inboxFolderId = null,
draftsFolderId = null,
sentFolderId = null,
trashFolderId = null,
archiveFolderId = null,
spamFolderId = null,
draftsFolderSelection = SpecialFolderSelection.AUTOMATIC,
sentFolderSelection = SpecialFolderSelection.AUTOMATIC,
trashFolderSelection = SpecialFolderSelection.AUTOMATIC,
archiveFolderSelection = SpecialFolderSelection.AUTOMATIC,
spamFolderSelection = SpecialFolderSelection.AUTOMATIC,
importedAutoExpandFolder = null,
autoExpandFolderId = null,
folderDisplayMode = FolderMode.NOT_SECOND_CLASS,
folderSyncMode = FolderMode.FIRST_CLASS,
folderPushMode = FolderMode.NONE,
accountNumber = 0,
isNotifySync = false,
sortType = SortType.SORT_DATE,
sortAscending = emptyMap(),
showPictures = ShowPictures.NEVER,
isSignatureBeforeQuotedText = false,
expungePolicy = Expunge.EXPUNGE_IMMEDIATELY,
maxPushFolders = 0,
idleRefreshMinutes = 0,
useCompression = true,
isSendClientInfoEnabled = true,
isSubscribedFoldersOnly = false,
maximumPolledMessageAge = 0,
maximumAutoDownloadMessageSize = 0,
messageFormat = MessageFormat.HTML,
isMessageFormatAuto = false,
isMessageReadReceipt = false,
quoteStyle = QuoteStyle.PREFIX,
quotePrefix = null,
isDefaultQuotedTextShown = false,
isReplyAfterQuote = false,
isStripSignature = false,
isSyncRemoteDeletions = false,
openPgpProvider = null,
openPgpKey = 0,
autocryptPreferEncryptMutual = false,
isOpenPgpHideSignOnly = false,
isOpenPgpEncryptSubject = false,
isOpenPgpEncryptAllDrafts = false,
isMarkMessageAsReadOnView = false,
isMarkMessageAsReadOnDelete = false,
isAlwaysShowCcBcc = false,
isRemoteSearchFullText = false,
remoteSearchNumResults = 0,
isUploadSentMessages = false,
lastSyncTime = 0,
lastFolderListRefreshTime = 0,
isFinishedSetup = false,
messagesNotificationChannelVersion = 0,
isChangedVisibleLimits = false,
lastSelectedFolderId = null,
identities = identities,
notificationSettings = notificationSettings,
senderName = identities[0].name,
signatureUse = identities[0].signatureUse,
signature = identities[0].signature,
shouldMigrateToOAuth = false,
)
}
}
}