Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app-common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies {

implementation(projects.feature.mail.message.export.api)
implementation(projects.feature.mail.message.export.implEml)
implementation(projects.feature.mail.message.list.api)
implementation(projects.feature.mail.message.reader.api)
implementation(projects.feature.mail.message.reader.impl)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.thunderbird.app.common

import com.fsck.k9.K9
import com.fsck.k9.legacyCommonAppModules
import com.fsck.k9.legacyCoreModules
import com.fsck.k9.legacyUiModules
Expand All @@ -8,8 +9,10 @@ import net.thunderbird.app.common.appConfig.AndroidPlatformConfigProvider
import net.thunderbird.app.common.core.appCommonCoreModule
import net.thunderbird.app.common.feature.appCommonFeatureModule
import net.thunderbird.core.common.appConfig.PlatformConfigProvider
import net.thunderbird.feature.mail.message.list.extension.toSortType
import org.koin.core.module.Module
import org.koin.dsl.module
import net.thunderbird.feature.mail.message.list.domain.DomainContract as MessageListDomainContract

val appCommonModule: Module = module {
includes(legacyCommonAppModules)
Expand All @@ -23,4 +26,10 @@ val appCommonModule: Module = module {
)

single<PlatformConfigProvider> { AndroidPlatformConfigProvider() }

single<MessageListDomainContract.UseCase.GetDefaultSortType> {
MessageListDomainContract.UseCase.GetDefaultSortType {
K9.sortType.toSortType(isAscending = K9.isSortAscending(K9.sortType))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import com.fsck.k9.view.K9WebViewClient
import com.fsck.k9.view.MessageWebView
import net.openid.appauth.AppAuthConfiguration
import net.thunderbird.core.common.mail.html.HtmlSettings
import net.thunderbird.core.logging.Logger
import net.thunderbird.core.preference.storage.Storage
import net.thunderbird.feature.account.AccountId
import net.thunderbird.feature.mail.message.list.internal.ui.state.machine.MessageListStateMachine
import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract
import net.thunderbird.feature.mail.message.reader.api.css.CssClassNameProvider
import org.junit.Test
Expand Down Expand Up @@ -68,6 +70,7 @@ class DependencyInjectionTest {
definition<SyncDebugWorker>(WorkerParameters::class),
definition<OpenPgpApiManager>(LifecycleOwner::class),
definition<SetupArchiveFolderDialogContract.ViewModel>(SetupArchiveFolderDialogContract.State::class),
definition<MessageListStateMachine.Factory>(Logger::class, List::class),
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import com.fsck.k9.view.K9WebViewClient
import com.fsck.k9.view.MessageWebView
import net.openid.appauth.AppAuthConfiguration
import net.thunderbird.core.common.mail.html.HtmlSettings
import net.thunderbird.core.logging.Logger
import net.thunderbird.core.preference.storage.Storage
import net.thunderbird.feature.account.AccountId
import net.thunderbird.feature.mail.message.list.internal.ui.state.machine.MessageListStateMachine
import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract
import net.thunderbird.feature.mail.message.reader.api.css.CssClassNameProvider
import org.junit.Test
Expand Down Expand Up @@ -68,6 +70,7 @@ class DependencyInjectionTest {
definition<SyncDebugWorker>(WorkerParameters::class),
definition<OpenPgpApiManager>(LifecycleOwner::class),
definition<SetupArchiveFolderDialogContract.ViewModel>(SetupArchiveFolderDialogContract.State::class),
definition<MessageListStateMachine.Factory>(Logger::class, List::class),
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package net.thunderbird.core.common.inject
import org.koin.core.definition.Definition
import org.koin.core.module.KoinDslMarker
import org.koin.core.module.Module
import org.koin.core.parameter.parametersOf
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
import org.koin.core.qualifier.named
import org.koin.core.scope.Scope
Expand All @@ -24,8 +24,8 @@ import org.koin.core.scope.Scope
*/
@KoinDslMarker
inline fun <reified T> Module.singleListOf(vararg items: Definition<T>, qualifier: Qualifier? = null) {
single(qualifier ?: defaultListQualifier<T>(), createdAtStart = true) {
items.map { definition -> definition(this, parametersOf()) }
single(qualifier ?: defaultListQualifier<T>(), createdAtStart = true) { parameters ->
items.map { definition -> definition(this, parameters) }
}
}

Expand All @@ -43,8 +43,8 @@ inline fun <reified T> Module.singleListOf(vararg items: Definition<T>, qualifie
*/
@KoinDslMarker
inline fun <reified T> Module.factoryListOf(vararg items: Definition<T>, qualifier: Qualifier? = null) {
factory(qualifier ?: defaultListQualifier<T>()) {
items.map { definition -> definition(this, parametersOf()) }
factory(qualifier ?: defaultListQualifier<T>()) { parametersHolder ->
items.map { definition -> definition(this, parametersHolder) }
}
}

Expand All @@ -58,8 +58,10 @@ inline fun <reified T> Module.factoryListOf(vararg items: Definition<T>, qualifi
* @param qualifier An optional [Qualifier] to distinguish between different lists of the same type.
* @return The resolved [MutableList] of instances of type [T].
*/
inline fun <reified T> Scope.getList(qualifier: Qualifier? = null) =
get<List<T>>(qualifier ?: defaultListQualifier<T>())
inline fun <reified T> Scope.getList(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null,
) = get<List<T>>(qualifier ?: defaultListQualifier<T>(), parameters = parameters)

/**
* Creates a qualifier for a set of a specific type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ class StateMachineTest {
stateMachine<State, Event>(scope = this) {
initialState(State.Init) {
onEnter { event, newState ->
println("New state: $newState, event: $event")
actualPreviousState = this
actualNewState = newState
actualEvent = event
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ abstract class BaseViewModel<STATE, EVENT, EFFECT>(
*
* @param update A function that takes the current [STATE] and produces a new [STATE].
*/
protected fun updateState(update: (STATE) -> STATE) {
protected open fun updateState(update: (STATE) -> STATE) {
_state.update(update)
}

Expand Down
1 change: 1 addition & 0 deletions feature/mail/message/list/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ android {
dependencies {
api(projects.core.outcome)

implementation(projects.core.android.account)
implementation(projects.core.common)
implementation(projects.core.featureflag)
implementation(projects.core.preference.api)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import net.thunderbird.core.outcome.Outcome
import net.thunderbird.feature.account.AccountId
import net.thunderbird.feature.mail.folder.api.FolderServerId
import net.thunderbird.feature.mail.folder.api.RemoteFolder
import net.thunderbird.feature.mail.message.list.preferences.MessageListPreferences
import net.thunderbird.feature.mail.message.list.ui.state.SortType

interface DomainContract {
interface UseCase {
Expand All @@ -31,6 +33,18 @@ interface DomainContract {
fun interface BuildSwipeActions {
operator fun invoke(): StateFlow<Map<AccountId, SwipeActions>>
}

fun interface GetMessageListPreferences {
operator fun invoke(): Flow<MessageListPreferences>
}

fun interface GetSortTypes {
suspend operator fun invoke(accountIds: Set<AccountId>): Map<AccountId?, SortType>
}

fun interface GetDefaultSortType {
suspend operator fun invoke(): SortType
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package net.thunderbird.feature.mail.message.list.extension

import net.thunderbird.feature.mail.message.list.ui.state.SortType
import net.thunderbird.core.android.account.SortType as DomainSortType

/**
* Maps a [DomainSortType] from the domain layer to a [SortType] in the UI layer.
*
* This extension function takes the domain-level sort criteria and a boolean indicating
* the sort direction to produce the corresponding specific UI sort type.
*
* @param isAscending `true` for ascending order, `false` for descending order.
* @return The corresponding [SortType] for the UI layer.
*/
@Suppress("ComplexMethod")
fun DomainSortType.toSortType(isAscending: Boolean): SortType = when (this) {
DomainSortType.SORT_DATE if isAscending -> SortType.DateAsc
DomainSortType.SORT_DATE -> SortType.DateDesc
DomainSortType.SORT_ARRIVAL if isAscending -> SortType.ArrivalAsc
DomainSortType.SORT_ARRIVAL -> SortType.ArrivalDesc
DomainSortType.SORT_SUBJECT if isAscending -> SortType.SubjectAsc
DomainSortType.SORT_SUBJECT -> SortType.SubjectDesc
DomainSortType.SORT_SENDER if isAscending -> SortType.SenderAsc
DomainSortType.SORT_SENDER -> SortType.SenderDesc
DomainSortType.SORT_UNREAD if isAscending -> SortType.UnreadAsc
DomainSortType.SORT_UNREAD -> SortType.UnreadDesc
DomainSortType.SORT_FLAGGED if isAscending -> SortType.FlaggedAsc
DomainSortType.SORT_FLAGGED -> SortType.FlaggedDesc
DomainSortType.SORT_ATTACHMENT if isAscending -> SortType.AttachmentAsc
DomainSortType.SORT_ATTACHMENT -> SortType.AttachmentDesc
}

/**
* Maps a [SortType] from the UI layer to a [DomainSortType] in the domain layer.
*
* This extension function takes a specific UI-level sort type and decomposes it into its
* domain-level sort criteria and a boolean indicating the sort direction.
*
* @return A [Pair] where the first element is the [DomainSortType] and the second
* is a [Boolean] indicating the sort order (`true` for ascending, `false` for descending).
*/
@Suppress("ComplexMethod")
fun SortType.toDomainSortType(): Pair<DomainSortType, Boolean> = when (this) {
SortType.DateAsc -> DomainSortType.SORT_DATE to true
SortType.DateDesc -> DomainSortType.SORT_DATE to false
SortType.ArrivalAsc -> DomainSortType.SORT_ARRIVAL to true
SortType.ArrivalDesc -> DomainSortType.SORT_ARRIVAL to false
SortType.SenderAsc -> DomainSortType.SORT_SENDER to true
SortType.SenderDesc -> DomainSortType.SORT_SENDER to false
SortType.UnreadAsc -> DomainSortType.SORT_UNREAD to true
SortType.UnreadDesc -> DomainSortType.SORT_UNREAD to false
SortType.FlaggedAsc -> DomainSortType.SORT_FLAGGED to true
SortType.FlaggedDesc -> DomainSortType.SORT_FLAGGED to false
SortType.AttachmentAsc -> DomainSortType.SORT_ATTACHMENT to true
SortType.AttachmentDesc -> DomainSortType.SORT_ATTACHMENT to false
SortType.SubjectAsc -> DomainSortType.SORT_SUBJECT to true
SortType.SubjectDesc -> DomainSortType.SORT_SUBJECT to false
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,27 @@ import net.thunderbird.core.preference.display.visualSettings.message.list.UiDen
* @property showCorrespondentNames Whether to display the names of correspondents.
* @property showMessageAvatar Whether to display the contact's avatar.
* @property showFavouriteButton Whether to display a button to mark a message as a favourite (starred).
* @property senderAboveSubject Whether to display sender information above the subject.
* @property excerptLines The number of lines to show for a message excerpt.
* @property dateTimeFormat The format for displaying the date and time of messages.
* @property useVolumeKeyNavigation Whether to enable navigating between messages using the volume keys.
* @property serverSearchLimit The maximum number of results to fetch when performing a server search.
* @property actionRequiringUserConfirmation A set of actions that require a confirmation dialog before execution.
* @property colorizeBackgroundWhenRead Whether to colorize the background of read messages.
*/
data class MessageListPreferences(
val density: UiDensity,
val groupConversations: Boolean,
val showCorrespondentNames: Boolean,
val showMessageAvatar: Boolean,
val showFavouriteButton: Boolean,
val senderAboveSubject: Boolean,
val excerptLines: Int,
val dateTimeFormat: MessageListDateTimeFormat,
val useVolumeKeyNavigation: Boolean,
val serverSearchLimit: Int,
val actionRequiringUserConfirmation: ImmutableSet<ActionRequiringUserConfirmation> = persistentSetOf(),
val colorizeBackgroundWhenRead: Boolean = false,
)

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package net.thunderbird.feature.mail.message.list.ui

import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
import net.thunderbird.feature.mail.message.list.ui.effect.MessageListEffect
import net.thunderbird.feature.mail.message.list.ui.event.MessageListEvent
import net.thunderbird.feature.mail.message.list.ui.state.MessageListState

interface MessageListContract {
abstract class ViewModel : BaseViewModel<MessageListState, MessageListEvent, MessageListEffect>(
initialState = MessageListState.WarmingUp(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import net.thunderbird.feature.account.AccountId
* or showing transient messages.
*/
sealed interface MessageListEffect {
/**
* Effect to trigger a refresh of the message list. This can be used to manually
* reload the list of messages from the data source, for instance, after a pull-to-refresh
* gesture or a programmatic trigger.
*/
data object RefreshMessageList : MessageListEffect

/**
* Effect to navigate back from the current screen.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ sealed interface MessageListEvent {
/**
* An event that is triggered when the user changes the sort order of the message list.
*
* @param accountId The [AccountId] of the account for which the sort order is being changed. When `null`,
* the sort type is for the Unified Inbox.
* @param sortType The new [SortType] to apply to the message list.
*/
data class ChangeSortType(val sortType: SortType) : UserEvent
data class ChangeSortType(val accountId: AccountId?, val sortType: SortType) : UserEvent
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
package net.thunderbird.feature.mail.message.list.internal

import net.thunderbird.core.common.inject.factoryListOf
import net.thunderbird.core.common.inject.getList
import net.thunderbird.feature.mail.message.list.domain.DomainContract
import net.thunderbird.feature.mail.message.list.internal.domain.usecase.BuildSwipeActions
import net.thunderbird.feature.mail.message.list.internal.domain.usecase.CreateArchiveFolder
import net.thunderbird.feature.mail.message.list.internal.domain.usecase.GetAccountFolders
import net.thunderbird.feature.mail.message.list.internal.domain.usecase.GetMessageListPreferences
import net.thunderbird.feature.mail.message.list.internal.domain.usecase.GetSortTypes
import net.thunderbird.feature.mail.message.list.internal.domain.usecase.SetArchiveFolder
import net.thunderbird.feature.mail.message.list.internal.ui.MessageListViewModel
import net.thunderbird.feature.mail.message.list.internal.ui.dialog.SetupArchiveFolderDialogFragment
import net.thunderbird.feature.mail.message.list.internal.ui.dialog.SetupArchiveFolderDialogViewModel
import net.thunderbird.feature.mail.message.list.internal.ui.state.machine.MessageListStateMachine
import net.thunderbird.feature.mail.message.list.internal.ui.state.sideeffect.LoadSortTypeStateSideEffectHandler
import net.thunderbird.feature.mail.message.list.internal.ui.state.sideeffect.LoadSwipeActionsStateSideEffectHandler
import net.thunderbird.feature.mail.message.list.internal.ui.state.sideeffect.LoadedConfigStateSideEffectHandler
import net.thunderbird.feature.mail.message.list.internal.ui.state.sideeffect.StateSideEffectHandler
import net.thunderbird.feature.mail.message.list.ui.MessageListContract
import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract
import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogFragmentFactory
import org.koin.core.module.dsl.viewModel
Expand Down Expand Up @@ -47,4 +58,45 @@ val featureMessageListModule = module {
) as SetupArchiveFolderDialogContract.ViewModel
}
factory<SetupArchiveFolderDialogFragmentFactory> { SetupArchiveFolderDialogFragment.Factory }
factory<DomainContract.UseCase.GetMessageListPreferences> {
GetMessageListPreferences(
displayPreferenceManager = get(),
interactionPreferenceManager = get(),
)
}
factory<DomainContract.UseCase.GetSortTypes> {
GetSortTypes(
accountManager = get(),
getDefaultSortType = get(),
)
}
factoryListOf<StateSideEffectHandler.Factory>(
{ parameters ->
LoadSwipeActionsStateSideEffectHandler.Factory(
logger = get(),
buildSwipeActions = get(),
)
},
{ parameters ->
LoadSortTypeStateSideEffectHandler.Factory(
accounts = parameters.get(),
logger = get(),
getSortTypes = get(),
)
},
{ LoadedConfigStateSideEffectHandler.Factory(logger = get()) },
)
factory { parameters ->
MessageListStateMachine.Factory(
logger = get(),
stateSideEffectHandlersFactories = getList(parameters = { parameters }),
)
}
viewModel<MessageListContract.ViewModel> { parameters ->
MessageListViewModel(
logger = get(),
messageListStateMachineFactory = get { parameters },
getMessageListPreferences = get(),
)
}
}
Loading
Loading