Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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

/**
* Defines the contract between the View and the ViewModel for the message list screen.
*
* This contract follows the MVI (Model-View-Intent) pattern, specifying the structure for:
* - **State (`MessageListState`)**: Represents the UI state.
* - **Events (`MessageListEvent`)**: User actions or other events from the UI.
* - **Effects (`MessageListEffect`)**: One-time actions for the UI to perform (e.g., navigation, showing a toast).
*/
interface MessageListContract {
/**
* The view model for the message list screen.
*
* It is responsible for handling the business logic of the message list screen and for providing the
* [MessageListState] to the UI. It consumes [MessageListEvent]s and produces [MessageListEffect]s.
*
* @see BaseViewModel
* @see MessageListState
* @see MessageListEvent
* @see MessageListEffect
*/
abstract class ViewModel : BaseViewModel<MessageListState, MessageListEvent, MessageListEffect>(
initialState = MessageListState.WarmingUp(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import net.thunderbird.feature.mail.message.list.internal.domain.usecase.BuildSw
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.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.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 +49,8 @@ val featureMessageListModule = module {
) as SetupArchiveFolderDialogContract.ViewModel
}
factory<SetupArchiveFolderDialogFragmentFactory> { SetupArchiveFolderDialogFragment.Factory }

viewModel<MessageListContract.ViewModel> { parameters ->
MessageListViewModel()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.thunderbird.feature.mail.message.list.internal.ui

import net.thunderbird.feature.mail.message.list.ui.MessageListContract
import net.thunderbird.feature.mail.message.list.ui.event.MessageListEvent

class MessageListViewModel : MessageListContract.ViewModel() {
override fun event(event: MessageListEvent) {
// TODO(9497): Handle events.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ open class MainActivity :
fragmentManager.findFragmentByTag(FRAGMENT_TAG_MESSAGE_VIEW_CONTAINER) as? MessageViewContainerFragment

messageListFragment?.let { messageListFragment ->
messageViewContainerFragment?.setViewModel(messageListFragment.viewModel)
messageViewContainerFragment?.setViewModel(messageListFragment.legacyViewModel)
initializeFromLocalSearch(messageListFragment.localSearch)
}
}
Expand Down Expand Up @@ -1110,7 +1110,7 @@ open class MainActivity :
messageViewContainerFragment = fragment

messageListFragment?.let { messageListFragment ->
fragment.setViewModel(messageListFragment.viewModel)
fragment.setViewModel(messageListFragment.legacyViewModel)
}

if (displayMode == DisplayMode.SPLIT_VIEW) {
Expand Down
11 changes: 9 additions & 2 deletions legacy/ui/legacy/src/main/java/com/fsck/k9/ui/KoinModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import com.fsck.k9.ui.helper.DisplayHtmlUiFactory
import com.fsck.k9.ui.helper.SizeFormatter
import com.fsck.k9.ui.messagelist.AbstractMessageListFragment
import com.fsck.k9.ui.messagelist.LegacyMessageListFragment
import com.fsck.k9.ui.messagelist.MessageListFragment
import com.fsck.k9.ui.messageview.LinkTextHandler
import com.fsck.k9.ui.settings.AboutViewModel
import com.fsck.k9.ui.share.ShareIntentBuilder
import net.thunderbird.core.common.inject.getList
import net.thunderbird.core.featureflag.FeatureFlagProvider
import net.thunderbird.feature.mail.message.list.MessageListFeatureFlags
import org.koin.core.module.dsl.viewModel
import org.koin.core.qualifier.named
import org.koin.dsl.module
Expand All @@ -31,7 +34,11 @@ val uiModule = module {
factory { ShareIntentBuilder(resourceProvider = get(), textPartFinder = get(), quoteDateFormatter = get()) }
factory { LinkTextHandler(context = get(), clipboardManager = get()) }
factory<AbstractMessageListFragment.Factory> {
// TODO(9497): verify if EnableMessageListNewState is enabled. If so, use the new MessageListFragment instead.
LegacyMessageListFragment.Factory
val featureFlagProvider = get<FeatureFlagProvider>()
if (featureFlagProvider.provide(MessageListFeatureFlags.EnableMessageListNewState).isEnabled()) {
MessageListFragment.Factory
} else {
LegacyMessageListFragment.Factory
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ abstract class AbstractMessageListFragment :

abstract val logTag: String

val viewModel: MessageListViewModel by viewModel()
val legacyViewModel: MessageListViewModel by viewModel()
private val viewModel: MessageListViewModel get() = legacyViewModel
private val recentChangesViewModel: RecentChangesViewModel by viewModel()

private val generalSettingsManager: GeneralSettingsManager by inject()
Expand Down Expand Up @@ -201,7 +202,8 @@ abstract class AbstractMessageListFragment :

private lateinit var adapter: MessageListAdapter

private lateinit var accountUuids: Array<String>
protected lateinit var accountUuids: Array<String>
private set
private var accounts: List<LegacyAccount> = emptyList()

private var account: LegacyAccount? = null
Expand Down Expand Up @@ -333,7 +335,7 @@ abstract class AbstractMessageListFragment :
rememberedSelected = savedInstanceState.getLongArray(STATE_SELECTED_MESSAGES)?.toSet()
}

private fun decodeArguments(): Error? {
protected fun decodeArguments(): Error? {
val arguments = requireArguments()
showingThreadedList = arguments.getBoolean(ARG_THREADED_LIST, false)
isThreadDisplay = arguments.getBoolean(ARG_IS_THREAD_DISPLAY, false)
Expand Down Expand Up @@ -2590,7 +2592,7 @@ abstract class AbstractMessageListFragment :
}

@Suppress("detekt.UnnecessaryAnnotationUseSiteTarget") // https://github.com/detekt/detekt/issues/8212
private enum class Error(@param:StringRes val errorText: Int) {
protected enum class Error(@param:StringRes val errorText: Int) {
FolderNotFound(R.string.message_list_error_folder_not_found),
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.fsck.k9.ui.messagelist

import androidx.core.os.bundleOf
import net.thunderbird.feature.account.AccountIdFactory
import net.thunderbird.feature.mail.message.list.ui.MessageListContract
import net.thunderbird.feature.search.legacy.LocalMessageSearch
import net.thunderbird.feature.search.legacy.serialization.LocalMessageSearchSerializer
import org.koin.android.ext.android.inject
import org.koin.core.parameter.parameterSetOf

private const val TAG = "MessageListFragment"

// TODO(10322): Move this fragment to :feature:mail:message:list once all migration to the new
// MessageListFragment to MVI is done.
class MessageListFragment : AbstractMessageListFragment() {
override val logTag: String = TAG

// TODO(9497): Remove suppression once we start use the new view model.
@Suppress("UnusedPrivateProperty")
private val viewModel: MessageListContract.ViewModel by inject {
decodeArguments()
parameterSetOf(accountUuids.map { AccountIdFactory.of(it) }.toSet())
}

companion object Factory : AbstractMessageListFragment.Factory {
override fun newInstance(
search: LocalMessageSearch,
isThreadDisplay: Boolean,
threadedList: Boolean,
): MessageListFragment {
val searchBytes = LocalMessageSearchSerializer.serialize(search)

return MessageListFragment().apply {
arguments = bundleOf(
ARG_SEARCH to searchBytes,
ARG_IS_THREAD_DISPLAY to isThreadDisplay,
ARG_THREADED_LIST to threadedList,
)
}
}
}
}
Loading