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
Expand Up @@ -42,9 +42,9 @@ import com.fsck.k9.ui.BuildConfig
import com.fsck.k9.ui.R
import com.fsck.k9.ui.base.BaseActivity
import com.fsck.k9.ui.managefolders.ManageFoldersActivity
import com.fsck.k9.ui.messagelist.AbstractMessageListFragment
import com.fsck.k9.ui.messagelist.AbstractMessageListFragment.MessageListFragmentListener
import com.fsck.k9.ui.messagelist.DefaultFolderProvider
import com.fsck.k9.ui.messagelist.MessageListFragment
import com.fsck.k9.ui.messagelist.MessageListFragment.MessageListFragmentListener
import com.fsck.k9.ui.messageview.MessageViewContainerFragment
import com.fsck.k9.ui.messageview.MessageViewContainerFragment.MessageViewContainerListener
import com.fsck.k9.ui.messageview.MessageViewFragment.MessageViewFragmentListener
Expand All @@ -53,7 +53,6 @@ import com.fsck.k9.ui.settings.SettingsActivity
import com.fsck.k9.view.ViewSwitcher
import com.fsck.k9.view.ViewSwitcher.OnSwitchCompleteListener
import com.google.android.material.textview.MaterialTextView
import kotlin.getValue
import net.thunderbird.core.android.account.LegacyAccount
import net.thunderbird.core.android.account.LegacyAccountDto
import net.thunderbird.core.android.account.LegacyAccountDtoManager
Expand Down Expand Up @@ -86,7 +85,7 @@ private const val TAG = "MainActivity"
* "View Message" notification.
*
* `MainActivity` manages the overall layout, including the navigation drawer and the main content area,
* which currently displays either a [MessageListFragment] or a [MessageViewContainerFragment]. It orchestrates
* which currently displays either a [AbstractMessageListFragment] or a [MessageViewContainerFragment]. It orchestrates
* the interactions between these fragments and handles the back stack. The responsibilities for managing the
* action bar, search functionality, and single-pane/split-view layout logic are currently handled here but
* are intended to be refactored into more dedicated components over time.
Expand Down Expand Up @@ -121,7 +120,8 @@ open class MainActivity :
private var openFolderTransaction: FragmentTransaction? = null
private var progressBar: ProgressBar? = null
private var messageViewPlaceHolder: PlaceholderFragment? = null
private var messageListFragment: MessageListFragment? = null
private val messageListFragmentFactory: AbstractMessageListFragment.Factory by inject()
private var messageListFragment: AbstractMessageListFragment? = null
private var messageViewContainerFragment: MessageViewContainerFragment? = null
private var account: LegacyAccountDto? = null
private var search: LocalMessageSearch? = null
Expand Down Expand Up @@ -266,7 +266,9 @@ open class MainActivity :

private fun findFragments() {
val fragmentManager = supportFragmentManager
messageListFragment = fragmentManager.findFragmentById(R.id.message_list_container) as? MessageListFragment
messageListFragment = fragmentManager.findFragmentById(
R.id.message_list_container,
) as? AbstractMessageListFragment
messageViewContainerFragment =
fragmentManager.findFragmentByTag(FRAGMENT_TAG_MESSAGE_VIEW_CONTAINER) as? MessageViewContainerFragment

Expand All @@ -283,10 +285,10 @@ open class MainActivity :
val hasMessageListFragment = messageListFragment != null
if (!hasMessageListFragment) {
val fragmentTransaction = fragmentManager.beginTransaction()
val messageListFragment = MessageListFragment.newInstance(
search!!,
false,
generalSettingsManager.getConfig()
val messageListFragment = messageListFragmentFactory.newInstance(
search = search!!,
isThreadDisplay = false,
threadedList = generalSettingsManager.getConfig()
.display
.inboxSettings
.isThreadedViewEnabled &&
Expand Down Expand Up @@ -741,10 +743,10 @@ open class MainActivity :
}

val openFolderTransaction = fragmentManager.beginTransaction()
val messageListFragment = MessageListFragment.newInstance(
search,
false,
generalSettingsManager.getConfig().display.inboxSettings.isThreadedViewEnabled,
val messageListFragment = messageListFragmentFactory.newInstance(
search = search,
isThreadDisplay = false,
threadedList = generalSettingsManager.getConfig().display.inboxSettings.isThreadedViewEnabled,
)
openFolderTransaction.replace(R.id.message_list_container, messageListFragment)

Expand Down Expand Up @@ -1159,7 +1161,7 @@ open class MainActivity :
}
}

private fun addMessageListFragment(fragment: MessageListFragment) {
private fun addMessageListFragment(fragment: AbstractMessageListFragment) {
messageListFragment?.isActive = false

supportFragmentManager.commit {
Expand Down Expand Up @@ -1229,7 +1231,11 @@ open class MainActivity :

initializeFromLocalSearch(tmpSearch)

val fragment = MessageListFragment.newInstance(tmpSearch, true, false)
val fragment = messageListFragmentFactory.newInstance(
search = tmpSearch,
isThreadDisplay = true,
threadedList = false,
)
addMessageListFragment(fragment)
}

Expand Down Expand Up @@ -1459,7 +1465,7 @@ open class MainActivity :
}
}

private fun MessageListFragment.setFullyActive() {
private fun AbstractMessageListFragment.setFullyActive() {
isActive = true
onFullyActive()
}
Expand Down
6 changes: 6 additions & 0 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 @@ -5,6 +5,8 @@ import app.k9mail.legacy.message.controller.MessagingControllerMailChecker
import com.fsck.k9.controller.MessagingController
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.messageview.LinkTextHandler
import com.fsck.k9.ui.share.ShareIntentBuilder
import net.thunderbird.core.common.inject.getList
Expand All @@ -25,4 +27,8 @@ val uiModule = module {
factory { (context: Context) -> SizeFormatter(context.resources) }
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.Discouraged
import androidx.annotation.StringRes
import androidx.appcompat.view.ActionMode
import androidx.compose.animation.animateContentSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.ComposeView
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.os.bundleOf
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type.systemBars
Expand Down Expand Up @@ -62,7 +62,7 @@ import com.fsck.k9.ui.changelog.RecentChangesViewModel
import com.fsck.k9.ui.choosefolder.ChooseFolderActivity
import com.fsck.k9.ui.choosefolder.ChooseFolderResultContract
import com.fsck.k9.ui.helper.RelativeDateTimeFormatter
import com.fsck.k9.ui.messagelist.MessageListFragment.MessageListFragmentListener.Companion.MAX_PROGRESS
import com.fsck.k9.ui.messagelist.AbstractMessageListFragment.MessageListFragmentListener.Companion.MAX_PROGRESS
import com.fsck.k9.ui.messagelist.debug.AuthDebugActions
import com.fsck.k9.ui.messagelist.item.MessageViewHolder
import com.google.android.material.floatingactionbutton.FloatingActionButton
Expand Down Expand Up @@ -94,14 +94,14 @@ import net.thunderbird.core.featureflag.FeatureFlagKey
import net.thunderbird.core.featureflag.FeatureFlagProvider
import net.thunderbird.core.featureflag.FeatureFlagResult
import net.thunderbird.core.logging.Logger
import net.thunderbird.core.logging.legacy.Log
import net.thunderbird.core.outcome.Outcome
import net.thunderbird.core.preference.GeneralSettingsManager
import net.thunderbird.core.preference.display.visualSettings.message.list.DisplayMessageListSettings
import net.thunderbird.core.preference.interaction.InteractionSettings
import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
import net.thunderbird.feature.account.avatar.AvatarMonogramCreator
import net.thunderbird.feature.mail.folder.api.OutboxFolderManager
import net.thunderbird.feature.mail.message.list.MessageListFeatureFlags
import net.thunderbird.feature.mail.message.list.domain.DomainContract
import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogFragmentFactory
import net.thunderbird.feature.notification.api.content.InAppNotification
Expand All @@ -124,15 +124,29 @@ private const val MAXIMUM_MESSAGE_SORT_OVERRIDES = 3
private const val MINIMUM_CLICK_INTERVAL = 200L
private const val RECENT_CHANGES_SNACKBAR_DURATION = 10 * 1000

private const val TAG = "MessageListFragment"

@Suppress("LargeClass", "TooManyFunctions")
class MessageListFragment :
@Suppress(
"LargeClass",
"TooManyFunctions",
"CyclomaticComplexMethod",
"TooGenericExceptionCaught",
"TooGenericExceptionThrown",
"SwallowedException",
"ReturnCount",
"ForbiddenComment",
)
@Discouraged(
message = "This class is in maintenance mode. DO NOT introduce any new features in this class. " +
"Only bugfixes are allowed. New features must be introduced in the new MessageListFragment, " +
"following the MVI principle.",
)
abstract class AbstractMessageListFragment :
Fragment(),
ConfirmationDialogFragmentListener,
MessageListItemActionListener,
ErrorNotificationsDialogFragmentActionListener {

abstract val logTag: String

val viewModel: MessageListViewModel by viewModel()
private val recentChangesViewModel: RecentChangesViewModel by viewModel()

Expand Down Expand Up @@ -373,7 +387,7 @@ class MessageListFragment :
contactRepository = contactRepository,
avatarMonogramCreator = avatarMonogramCreator,
).apply {
activeMessage = this@MessageListFragment.activeMessage
activeMessage = this@AbstractMessageListFragment.activeMessage
}
}

Expand All @@ -383,10 +397,10 @@ class MessageListFragment :
setFragmentResultListener(
SetupArchiveFolderDialogFragmentFactory.RESULT_CODE_DISMISS_REQUEST_KEY,
) { key, bundle ->
Log.d(
logger.debug(logTag) {
"SetupArchiveFolderDialogFragment fragment listener triggered with " +
"key: $key and bundle: $bundle",
)
"key: $key and bundle: $bundle"
}
loadMessageList(forceUpdate = true)
}
}
Expand Down Expand Up @@ -901,7 +915,7 @@ class MessageListFragment :
)
lifecycleScope.launch(Dispatchers.IO) {
accountManager.saveAccount(updatedAccount)
this@MessageListFragment.account = updatedAccount
this@AbstractMessageListFragment.account = updatedAccount
}
} else {
K9.sortType = this.sortType
Expand Down Expand Up @@ -1618,7 +1632,7 @@ class MessageListFragment :
for ((folderId, messagesInFolder) in folderMap) {
val account = accountManager.getAccount(messagesInFolder.first().accountUuid)
if (account == null) {
logger.debug(TAG) {
logger.debug(logTag) {
"Account for message ${messagesInFolder.first()} not found, skipping copy/move operation"
}
continue
Expand Down Expand Up @@ -1743,12 +1757,12 @@ class MessageListFragment :
// If we represent a remote search, then kill that before going back.
if (isRemoteSearch && remoteSearchFuture != null) {
try {
Log.i("Remote search in progress, attempting to abort...")
logger.info(logTag) { "Remote search in progress, attempting to abort..." }

// Canceling the future stops any message fetches in progress.
val cancelSuccess = remoteSearchFuture!!.cancel(true) // mayInterruptIfRunning = true
if (!cancelSuccess) {
Log.e("Could not cancel remote search future.")
logger.error(logTag) { "Could not cancel remote search future." }
}

// Closing the folder will kill off the connection if we're mid-search.
Expand All @@ -1763,7 +1777,7 @@ class MessageListFragment :
)
} catch (e: Exception) {
// Since the user is going back, log and squash any exceptions.
Log.e(e, "Could not abort remote search before going back")
logger.error(logTag, e) { "Could not abort remote search before going back" }
}
}

Expand Down Expand Up @@ -2591,36 +2605,47 @@ class MessageListFragment :
fun startSupportActionMode(callback: ActionMode.Callback): ActionMode?
fun goBack()

companion object {
companion object Companion {
const val MAX_PROGRESS = 10000
}
}

companion object {

private const val ARG_SEARCH = "searchObject"
private const val ARG_THREADED_LIST = "showingThreadedList"
private const val ARG_IS_THREAD_DISPLAY = "isThreadedDisplay"

private const val STATE_SELECTED_MESSAGES = "selectedMessages"
private const val STATE_ACTIVE_MESSAGES = "activeMessages"
private const val STATE_ACTIVE_MESSAGE = "activeMessage"
private const val STATE_REMOTE_SEARCH_PERFORMED = "remoteSearchPerformed"

/**
* A factory for creating instances of [AbstractMessageListFragment].
*
* This interface is a temporary solution to toggle between different fragment implementations
* based on a feature flag. It allows for the creation of either a modern [MessageListFragment] or a
* [LegacyMessageListFragment] depending on the state of [MessageListFeatureFlags.EnableMessageListNewState].
*/
interface Factory {
/**
* Creates a new instance of a class that inherits from [AbstractMessageListFragment].
*
* The specific implementation returned ([MessageListFragment] or [LegacyMessageListFragment]) is determined
* by the [MessageListFeatureFlags.EnableMessageListNewState] feature flag.
*
* @param search The search query that defines which messages to display.
* @param isThreadDisplay `true` if the fragment is used to display a single thread, `false` otherwise.
* @param threadedList `true` to display the message list in a threaded conversation view, `false` otherwise.
*
* @return An instance of [MessageListFragment] if the new state feature flag is enabled;
* otherwise, an instance of [LegacyMessageListFragment].
*/
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,
)
}
}
): AbstractMessageListFragment
}

companion object {
protected const val ARG_SEARCH = "searchObject"
protected const val ARG_THREADED_LIST = "showingThreadedList"
protected const val ARG_IS_THREAD_DISPLAY = "isThreadedDisplay"

protected const val STATE_SELECTED_MESSAGES = "selectedMessages"
protected const val STATE_ACTIVE_MESSAGES = "activeMessages"
protected const val STATE_ACTIVE_MESSAGE = "activeMessage"
protected const val STATE_REMOTE_SEARCH_PERFORMED = "remoteSearchPerformed"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.fsck.k9.ui.messagelist

import androidx.core.os.bundleOf
import net.thunderbird.feature.search.legacy.LocalMessageSearch
import net.thunderbird.feature.search.legacy.serialization.LocalMessageSearchSerializer

private const val TAG = "LegacyMessageListFragment"

@Deprecated(
message = "DO NOT introduce any new features in this class. " +
"This will be replaced by the new MessageListFragment and deleted in the future.",
)
class LegacyMessageListFragment : AbstractMessageListFragment() {
override val logTag: String = TAG

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

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