Loading app/core/src/main/java/com/fsck/k9/helper/CollectionExtensions.kt 0 → 100644 +40 −0 Original line number Diff line number Diff line package com.fsck.k9.helper /** * Returns a [Set] containing the results of applying the given [transform] function to each element in the original * collection. * * If you know the size of the output or can make an educated guess, specify [expectedSize] as an optimization. * The initial capacity of the `Set` will be derived from this value. */ inline fun <T, R> Iterable<T>.mapToSet(expectedSize: Int? = null, transform: (T) -> R): Set<R> { return if (expectedSize != null) { mapTo(LinkedHashSet(setCapacity(expectedSize)), transform) } else { mapTo(mutableSetOf(), transform) } } /** * Returns a [Set] containing the results of applying the given [transform] function to each element in the original * collection. * * The size of the output is expected to be equal to the size of the input. If that's not the case, please use * [mapToSet] instead. */ inline fun <T, R> Collection<T>.mapCollectionToSet(transform: (T) -> R): Set<R> { return mapToSet(expectedSize = size, transform) } // A copy of Kotlin's internal mapCapacity() for the JVM fun setCapacity(expectedSize: Int): Int = when { // We are not coercing the value to a valid one and not throwing an exception. It is up to the caller to // properly handle negative values. expectedSize < 0 -> expectedSize expectedSize < 3 -> expectedSize + 1 expectedSize < INT_MAX_POWER_OF_TWO -> ((expectedSize / 0.75F) + 1.0F).toInt() // any large value else -> Int.MAX_VALUE } private const val INT_MAX_POWER_OF_TWO: Int = 1 shl (Int.SIZE_BITS - 2) app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListAdapter.kt +74 −0 Original line number Diff line number Diff line Loading @@ -56,6 +56,12 @@ class MessageListAdapter internal constructor( set(value) { field = value messagesMap = value.associateBy { it.uniqueId } if (selected.isNotEmpty()) { val uniqueIds = messagesMap.keys selected = selected.intersect(uniqueIds) } notifyDataSetChanged() } Loading @@ -64,6 +70,20 @@ class MessageListAdapter internal constructor( var activeMessage: MessageReference? = null var selected: Set<Long> = emptySet() private set(value) { field = value selectedCount = calculateSelectionCount() notifyDataSetChanged() } val selectedMessages: List<MessageListItem> get() = selected.map { messagesMap[it]!! } val isAllSelected: Boolean get() = selected.isNotEmpty() && selected.size == messages.size var selectedCount: Int = 0 private set private inline val subjectViewFontSize: Int get() = if (appearance.senderAboveSubject) { Loading Loading @@ -302,6 +322,60 @@ class MessageListAdapter internal constructor( item.folderId == activeMessage.folderId && item.messageUid == activeMessage.uid } fun toggleSelection(item: MessageListItem) { if (messagesMap[item.uniqueId] == null) { // MessageListItem is no longer in the list return } if (item.uniqueId in selected) { deselectMessage(item) } else { selectMessage(item) } } private fun selectMessage(item: MessageListItem) { selected = selected + item.uniqueId } private fun deselectMessage(item: MessageListItem) { selected = selected - item.uniqueId } fun selectAll() { val uniqueIds = messagesMap.keys.toSet() selected = uniqueIds } fun clearSelected() { selected = emptySet() } fun restoreSelected(selectedIds: Set<Long>) { if (selectedIds.isEmpty()) { clearSelected() } else { val uniqueIds = messagesMap.keys selected = selectedIds.intersect(uniqueIds) } } private fun calculateSelectionCount(): Int { if (selected.isEmpty()) { return 0 } if (!appearance.showingThreadedList) { return selected.size } return messages .asSequence() .filter { it.uniqueId in selected } .sumOf { it.threadCount.coerceAtLeast(1) } } } interface MessageListItemActionListener { Loading app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt +87 −175 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import com.fsck.k9.controller.SimpleMessagingListener import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener import com.fsck.k9.fragment.MessageListFragment.MessageListFragmentListener.Companion.MAX_PROGRESS import com.fsck.k9.helper.Utility import com.fsck.k9.helper.mapToSet import com.fsck.k9.mail.Flag import com.fsck.k9.mail.MessagingException import com.fsck.k9.search.LocalSearch Loading @@ -52,7 +53,6 @@ import com.fsck.k9.ui.messagelist.MessageListInfo import com.fsck.k9.ui.messagelist.MessageListItem import com.fsck.k9.ui.messagelist.MessageListViewModel import com.fsck.k9.ui.messagelist.MessageSortOverride import java.util.HashSet import java.util.concurrent.Future import net.jcip.annotations.GuardedBy import org.koin.android.ext.android.inject Loading Loading @@ -99,8 +99,6 @@ class MessageListFragment : private var sortType = SortType.SORT_DATE private var sortAscending = true private var sortDateAscending = false private var selectedCount = 0 private var selected: MutableSet<Long> = HashSet() private var actionMode: ActionMode? = null private var hasConnectivity: Boolean? = null Loading @@ -112,6 +110,7 @@ class MessageListFragment : private var showingThreadedList = false private var isThreadDisplay = false private var activeMessage: MessageReference? = null private var rememberedSelected: Set<Long>? = null lateinit var localSearch: LocalSearch private set Loading Loading @@ -180,10 +179,7 @@ class MessageListFragment : } private fun restoreSelectedMessages(savedInstanceState: Bundle) { val selectedIds = savedInstanceState.getLongArray(STATE_SELECTED_MESSAGES) ?: return for (id in selectedIds) { selected.add(id) } rememberedSelected = savedInstanceState.getLongArray(STATE_SELECTED_MESSAGES)?.toSet() } fun restoreListState(savedListState: Parcelable) { Loading Loading @@ -416,7 +412,7 @@ class MessageListFragment : } private fun handleListItemClick(position: Int) { if (selectedCount > 0) { if (adapter.selectedCount > 0) { toggleMessageSelect(position) } else { val adapterPosition = listViewToAdapterPosition(position) Loading Loading @@ -450,7 +446,7 @@ class MessageListFragment : super.onSaveInstanceState(outState) saveListState(outState) outState.putLongArray(STATE_SELECTED_MESSAGES, selected.toLongArray()) outState.putLongArray(STATE_SELECTED_MESSAGES, adapter.selected.toLongArray()) outState.putBoolean(STATE_REMOTE_SEARCH_PERFORMED, isRemoteSearch) outState.putStringArray( STATE_ACTIVE_MESSAGES, Loading Loading @@ -850,24 +846,13 @@ class MessageListFragment : holder.main.text = text } private fun setSelectionState(selected: Boolean) { if (selected) { if (adapter.count == 0) { private fun selectAll() { if (adapter.messages.isEmpty()) { // Nothing to do if there are no messages return } selectedCount = 0 for (i in 0 until adapter.count) { val messageListItem = adapter.getItem(i) this.selected.add(messageListItem.uniqueId) if (showingThreadedList) { selectedCount += messageListItem.threadCount.coerceAtLeast(1) } else { selectedCount++ } } adapter.selectAll() if (actionMode == null) { startAndPrepareActionMode() Loading @@ -875,16 +860,6 @@ class MessageListFragment : computeBatchDirection() updateActionMode() computeSelectAllVisibility() } else { this.selected.clear() selectedCount = 0 actionMode?.finish() actionMode = null } adapter.notifyDataSetChanged() } private fun toggleMessageSelect(listViewPosition: Int) { Loading @@ -896,43 +871,20 @@ class MessageListFragment : } private fun toggleMessageSelect(messageListItem: MessageListItem) { val uniqueId = messageListItem.uniqueId val selected = selected.contains(uniqueId) if (!selected) { this.selected.add(uniqueId) } else { this.selected.remove(uniqueId) } var selectedCountDelta = 1 if (showingThreadedList) { val threadCount = messageListItem.threadCount if (threadCount > 1) { selectedCountDelta = threadCount } } adapter.toggleSelection(messageListItem) if (actionMode != null) { if (selected && selectedCount - selectedCountDelta == 0) { if (adapter.selectedCount == 0) { actionMode?.finish() actionMode = null return } } else { startAndPrepareActionMode() } if (selected) { selectedCount -= selectedCountDelta } else { selectedCount += selectedCountDelta if (actionMode == null) { startAndPrepareActionMode() } computeBatchDirection() updateActionMode() computeSelectAllVisibility() adapter.notifyDataSetChanged() } override fun onToggleMessageSelection(item: MessageListItem) { Loading @@ -945,36 +897,19 @@ class MessageListFragment : private fun updateActionMode() { val actionMode = actionMode ?: error("actionMode == null") actionMode.title = getString(R.string.actionbar_selected, selectedCount) actionMode.invalidate() } actionMode.title = getString(R.string.actionbar_selected, adapter.selectedCount) actionModeCallback.showSelectAll(!adapter.isAllSelected) private fun computeSelectAllVisibility() { actionModeCallback.showSelectAll(selected.size != adapter.count) actionMode.invalidate() } private fun computeBatchDirection() { var isBatchFlag = false var isBatchRead = false for (i in 0 until adapter.count) { val messageListItem = adapter.getItem(i) if (selected.contains(messageListItem.uniqueId)) { if (!messageListItem.isStarred) { isBatchFlag = true } val selectedMessages = adapter.selectedMessages val notAllRead = !selectedMessages.all { it.isRead } val notAllStarred = !selectedMessages.all { it.isStarred } if (!messageListItem.isRead) { isBatchRead = true } if (isBatchFlag && isBatchRead) { break } } } actionModeCallback.showMarkAsRead(isBatchRead) actionModeCallback.showFlag(isBatchFlag) actionModeCallback.showMarkAsRead(notAllRead) actionModeCallback.showFlag(notAllStarred) } private fun setFlag(messageListItem: MessageListItem, flag: Flag, newState: Boolean) { Loading @@ -991,16 +926,14 @@ class MessageListFragment : } private fun setFlagForSelected(flag: Flag, newState: Boolean) { if (selected.isEmpty()) return if (adapter.selected.isEmpty()) return val messageMap: MutableMap<Account, MutableList<Long>> = mutableMapOf() val threadMap: MutableMap<Account, MutableList<Long>> = mutableMapOf() val accounts: MutableSet<Account> = mutableSetOf() val messageMap = mutableMapOf<Account, MutableList<Long>>() val threadMap = mutableMapOf<Account, MutableList<Long>>() val accounts = mutableSetOf<Account>() for (position in 0 until adapter.count) { val messageListItem = adapter.getItem(position) for (messageListItem in adapter.selectedMessages) { val account = messageListItem.account if (messageListItem.uniqueId in selected) { accounts.add(account) if (showingThreadedList && messageListItem.threadCount > 1) { Loading @@ -1011,7 +944,6 @@ class MessageListFragment : messageIdList.add(messageListItem.databaseId) } } } for (account in accounts) { messageMap[account]?.let { messageIds -> Loading Loading @@ -1276,10 +1208,6 @@ class MessageListFragment : super.onStop() } fun selectAll() { setSelectionState(true) } fun onMoveUp() { var currentPosition = listView.selectedItemPosition if (currentPosition == AdapterView.INVALID_POSITION || listView.isInTouchMode) { Loading Loading @@ -1346,14 +1274,8 @@ class MessageListFragment : return listViewToAdapterPosition(listViewPosition) } private val checkedMessages: List<MessageReference> get() { return adapter.messages .asSequence() .filter { it.uniqueId in selected } .map { MessageReference(it.account.uuid, it.folderId, it.messageUid) } .toList() } private val selectedMessages: List<MessageReference> get() = adapter.selectedMessages.map { it.messageReference } fun onDelete() { selectedMessage?.let { message -> Loading Loading @@ -1484,14 +1406,15 @@ class MessageListFragment : } } cleanupSelected(messageListItems) adapter.selected = selected adapter.messages = messageListItems rememberedSelected?.let { rememberedSelected = null adapter.restoreSelected(it) } resetActionMode() computeBatchDirection() computeSelectAllVisibility() if (savedListState != null) { handler.restoreListPosition(savedListState) Loading @@ -1506,19 +1429,10 @@ class MessageListFragment : } } private fun cleanupSelected(messageListItems: List<MessageListItem>) { if (selected.isEmpty()) return selected = messageListItems.asSequence() .map { it.uniqueId } .filter { it in selected } .toMutableSet() } private fun resetActionMode() { if (!isResumed) return if (!isActive || selected.isEmpty()) { if (!isActive || adapter.selected.isEmpty()) { actionMode?.finish() actionMode = null return Loading @@ -1528,7 +1442,6 @@ class MessageListFragment : startAndPrepareActionMode() } recalculateSelectionCount() updateActionMode() } Loading @@ -1537,18 +1450,6 @@ class MessageListFragment : actionMode?.invalidate() } private fun recalculateSelectionCount() { if (!showingThreadedList) { selectedCount = selected.size return } selectedCount = adapter.messages .asSequence() .filter { it.uniqueId in selected } .sumOf { it.threadCount.coerceAtLeast(1) } } fun remoteSearchFinished() { remoteSearchFuture = null } Loading Loading @@ -1846,12 +1747,7 @@ class MessageListFragment : } private val accountUuidsForSelected: Set<String> get() { return adapter.messages.asSequence() .filter { it.uniqueId in selected } .map { it.account.uuid } .toSet() } get() = adapter.selectedMessages.mapToSet { it.account.uuid } override fun onDestroyActionMode(mode: ActionMode) { actionMode = null Loading @@ -1861,7 +1757,7 @@ class MessageListFragment : flag = null unflag = null setSelectionState(false) adapter.clearSelected() } override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { Loading Loading @@ -1942,42 +1838,58 @@ class MessageListFragment : override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { // In the following we assume that we can't move or copy mails to the same folder. Also that spam isn't // available if we are in the spam folder, same for archive. when (item.itemId) { val endSelectionMode = when (item.itemId) { R.id.delete -> { val messages = checkedMessages onDelete(messages) selectedCount = 0 } R.id.mark_as_read -> setFlagForSelected(Flag.SEEN, true) R.id.mark_as_unread -> setFlagForSelected(Flag.SEEN, false) R.id.flag -> setFlagForSelected(Flag.FLAGGED, true) R.id.unflag -> setFlagForSelected(Flag.FLAGGED, false) R.id.select_all -> selectAll() onDelete(selectedMessages) true } R.id.mark_as_read -> { setFlagForSelected(Flag.SEEN, true) false } R.id.mark_as_unread -> { setFlagForSelected(Flag.SEEN, false) false } R.id.flag -> { setFlagForSelected(Flag.FLAGGED, true) false } R.id.unflag -> { setFlagForSelected(Flag.FLAGGED, false) false } R.id.select_all -> { selectAll() false } R.id.archive -> { onArchive(checkedMessages) onArchive(selectedMessages) // TODO: Only finish action mode if all messages have been moved. selectedCount = 0 true } R.id.spam -> { onSpam(checkedMessages) onSpam(selectedMessages) // TODO: Only finish action mode if all messages have been moved. selectedCount = 0 true } R.id.move -> { onMove(checkedMessages) selectedCount = 0 onMove(selectedMessages) true } R.id.move_to_drafts -> { onMoveToDraftsFolder(checkedMessages) selectedCount = 0 onMoveToDraftsFolder(selectedMessages) true } R.id.copy -> { onCopy(checkedMessages) selectedCount = 0 onCopy(selectedMessages) true } else -> return false } if (selectedCount == 0) { if (endSelectionMode) { mode.finish() } Loading Loading
app/core/src/main/java/com/fsck/k9/helper/CollectionExtensions.kt 0 → 100644 +40 −0 Original line number Diff line number Diff line package com.fsck.k9.helper /** * Returns a [Set] containing the results of applying the given [transform] function to each element in the original * collection. * * If you know the size of the output or can make an educated guess, specify [expectedSize] as an optimization. * The initial capacity of the `Set` will be derived from this value. */ inline fun <T, R> Iterable<T>.mapToSet(expectedSize: Int? = null, transform: (T) -> R): Set<R> { return if (expectedSize != null) { mapTo(LinkedHashSet(setCapacity(expectedSize)), transform) } else { mapTo(mutableSetOf(), transform) } } /** * Returns a [Set] containing the results of applying the given [transform] function to each element in the original * collection. * * The size of the output is expected to be equal to the size of the input. If that's not the case, please use * [mapToSet] instead. */ inline fun <T, R> Collection<T>.mapCollectionToSet(transform: (T) -> R): Set<R> { return mapToSet(expectedSize = size, transform) } // A copy of Kotlin's internal mapCapacity() for the JVM fun setCapacity(expectedSize: Int): Int = when { // We are not coercing the value to a valid one and not throwing an exception. It is up to the caller to // properly handle negative values. expectedSize < 0 -> expectedSize expectedSize < 3 -> expectedSize + 1 expectedSize < INT_MAX_POWER_OF_TWO -> ((expectedSize / 0.75F) + 1.0F).toInt() // any large value else -> Int.MAX_VALUE } private const val INT_MAX_POWER_OF_TWO: Int = 1 shl (Int.SIZE_BITS - 2)
app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListAdapter.kt +74 −0 Original line number Diff line number Diff line Loading @@ -56,6 +56,12 @@ class MessageListAdapter internal constructor( set(value) { field = value messagesMap = value.associateBy { it.uniqueId } if (selected.isNotEmpty()) { val uniqueIds = messagesMap.keys selected = selected.intersect(uniqueIds) } notifyDataSetChanged() } Loading @@ -64,6 +70,20 @@ class MessageListAdapter internal constructor( var activeMessage: MessageReference? = null var selected: Set<Long> = emptySet() private set(value) { field = value selectedCount = calculateSelectionCount() notifyDataSetChanged() } val selectedMessages: List<MessageListItem> get() = selected.map { messagesMap[it]!! } val isAllSelected: Boolean get() = selected.isNotEmpty() && selected.size == messages.size var selectedCount: Int = 0 private set private inline val subjectViewFontSize: Int get() = if (appearance.senderAboveSubject) { Loading Loading @@ -302,6 +322,60 @@ class MessageListAdapter internal constructor( item.folderId == activeMessage.folderId && item.messageUid == activeMessage.uid } fun toggleSelection(item: MessageListItem) { if (messagesMap[item.uniqueId] == null) { // MessageListItem is no longer in the list return } if (item.uniqueId in selected) { deselectMessage(item) } else { selectMessage(item) } } private fun selectMessage(item: MessageListItem) { selected = selected + item.uniqueId } private fun deselectMessage(item: MessageListItem) { selected = selected - item.uniqueId } fun selectAll() { val uniqueIds = messagesMap.keys.toSet() selected = uniqueIds } fun clearSelected() { selected = emptySet() } fun restoreSelected(selectedIds: Set<Long>) { if (selectedIds.isEmpty()) { clearSelected() } else { val uniqueIds = messagesMap.keys selected = selectedIds.intersect(uniqueIds) } } private fun calculateSelectionCount(): Int { if (selected.isEmpty()) { return 0 } if (!appearance.showingThreadedList) { return selected.size } return messages .asSequence() .filter { it.uniqueId in selected } .sumOf { it.threadCount.coerceAtLeast(1) } } } interface MessageListItemActionListener { Loading
app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt +87 −175 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import com.fsck.k9.controller.SimpleMessagingListener import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener import com.fsck.k9.fragment.MessageListFragment.MessageListFragmentListener.Companion.MAX_PROGRESS import com.fsck.k9.helper.Utility import com.fsck.k9.helper.mapToSet import com.fsck.k9.mail.Flag import com.fsck.k9.mail.MessagingException import com.fsck.k9.search.LocalSearch Loading @@ -52,7 +53,6 @@ import com.fsck.k9.ui.messagelist.MessageListInfo import com.fsck.k9.ui.messagelist.MessageListItem import com.fsck.k9.ui.messagelist.MessageListViewModel import com.fsck.k9.ui.messagelist.MessageSortOverride import java.util.HashSet import java.util.concurrent.Future import net.jcip.annotations.GuardedBy import org.koin.android.ext.android.inject Loading Loading @@ -99,8 +99,6 @@ class MessageListFragment : private var sortType = SortType.SORT_DATE private var sortAscending = true private var sortDateAscending = false private var selectedCount = 0 private var selected: MutableSet<Long> = HashSet() private var actionMode: ActionMode? = null private var hasConnectivity: Boolean? = null Loading @@ -112,6 +110,7 @@ class MessageListFragment : private var showingThreadedList = false private var isThreadDisplay = false private var activeMessage: MessageReference? = null private var rememberedSelected: Set<Long>? = null lateinit var localSearch: LocalSearch private set Loading Loading @@ -180,10 +179,7 @@ class MessageListFragment : } private fun restoreSelectedMessages(savedInstanceState: Bundle) { val selectedIds = savedInstanceState.getLongArray(STATE_SELECTED_MESSAGES) ?: return for (id in selectedIds) { selected.add(id) } rememberedSelected = savedInstanceState.getLongArray(STATE_SELECTED_MESSAGES)?.toSet() } fun restoreListState(savedListState: Parcelable) { Loading Loading @@ -416,7 +412,7 @@ class MessageListFragment : } private fun handleListItemClick(position: Int) { if (selectedCount > 0) { if (adapter.selectedCount > 0) { toggleMessageSelect(position) } else { val adapterPosition = listViewToAdapterPosition(position) Loading Loading @@ -450,7 +446,7 @@ class MessageListFragment : super.onSaveInstanceState(outState) saveListState(outState) outState.putLongArray(STATE_SELECTED_MESSAGES, selected.toLongArray()) outState.putLongArray(STATE_SELECTED_MESSAGES, adapter.selected.toLongArray()) outState.putBoolean(STATE_REMOTE_SEARCH_PERFORMED, isRemoteSearch) outState.putStringArray( STATE_ACTIVE_MESSAGES, Loading Loading @@ -850,24 +846,13 @@ class MessageListFragment : holder.main.text = text } private fun setSelectionState(selected: Boolean) { if (selected) { if (adapter.count == 0) { private fun selectAll() { if (adapter.messages.isEmpty()) { // Nothing to do if there are no messages return } selectedCount = 0 for (i in 0 until adapter.count) { val messageListItem = adapter.getItem(i) this.selected.add(messageListItem.uniqueId) if (showingThreadedList) { selectedCount += messageListItem.threadCount.coerceAtLeast(1) } else { selectedCount++ } } adapter.selectAll() if (actionMode == null) { startAndPrepareActionMode() Loading @@ -875,16 +860,6 @@ class MessageListFragment : computeBatchDirection() updateActionMode() computeSelectAllVisibility() } else { this.selected.clear() selectedCount = 0 actionMode?.finish() actionMode = null } adapter.notifyDataSetChanged() } private fun toggleMessageSelect(listViewPosition: Int) { Loading @@ -896,43 +871,20 @@ class MessageListFragment : } private fun toggleMessageSelect(messageListItem: MessageListItem) { val uniqueId = messageListItem.uniqueId val selected = selected.contains(uniqueId) if (!selected) { this.selected.add(uniqueId) } else { this.selected.remove(uniqueId) } var selectedCountDelta = 1 if (showingThreadedList) { val threadCount = messageListItem.threadCount if (threadCount > 1) { selectedCountDelta = threadCount } } adapter.toggleSelection(messageListItem) if (actionMode != null) { if (selected && selectedCount - selectedCountDelta == 0) { if (adapter.selectedCount == 0) { actionMode?.finish() actionMode = null return } } else { startAndPrepareActionMode() } if (selected) { selectedCount -= selectedCountDelta } else { selectedCount += selectedCountDelta if (actionMode == null) { startAndPrepareActionMode() } computeBatchDirection() updateActionMode() computeSelectAllVisibility() adapter.notifyDataSetChanged() } override fun onToggleMessageSelection(item: MessageListItem) { Loading @@ -945,36 +897,19 @@ class MessageListFragment : private fun updateActionMode() { val actionMode = actionMode ?: error("actionMode == null") actionMode.title = getString(R.string.actionbar_selected, selectedCount) actionMode.invalidate() } actionMode.title = getString(R.string.actionbar_selected, adapter.selectedCount) actionModeCallback.showSelectAll(!adapter.isAllSelected) private fun computeSelectAllVisibility() { actionModeCallback.showSelectAll(selected.size != adapter.count) actionMode.invalidate() } private fun computeBatchDirection() { var isBatchFlag = false var isBatchRead = false for (i in 0 until adapter.count) { val messageListItem = adapter.getItem(i) if (selected.contains(messageListItem.uniqueId)) { if (!messageListItem.isStarred) { isBatchFlag = true } val selectedMessages = adapter.selectedMessages val notAllRead = !selectedMessages.all { it.isRead } val notAllStarred = !selectedMessages.all { it.isStarred } if (!messageListItem.isRead) { isBatchRead = true } if (isBatchFlag && isBatchRead) { break } } } actionModeCallback.showMarkAsRead(isBatchRead) actionModeCallback.showFlag(isBatchFlag) actionModeCallback.showMarkAsRead(notAllRead) actionModeCallback.showFlag(notAllStarred) } private fun setFlag(messageListItem: MessageListItem, flag: Flag, newState: Boolean) { Loading @@ -991,16 +926,14 @@ class MessageListFragment : } private fun setFlagForSelected(flag: Flag, newState: Boolean) { if (selected.isEmpty()) return if (adapter.selected.isEmpty()) return val messageMap: MutableMap<Account, MutableList<Long>> = mutableMapOf() val threadMap: MutableMap<Account, MutableList<Long>> = mutableMapOf() val accounts: MutableSet<Account> = mutableSetOf() val messageMap = mutableMapOf<Account, MutableList<Long>>() val threadMap = mutableMapOf<Account, MutableList<Long>>() val accounts = mutableSetOf<Account>() for (position in 0 until adapter.count) { val messageListItem = adapter.getItem(position) for (messageListItem in adapter.selectedMessages) { val account = messageListItem.account if (messageListItem.uniqueId in selected) { accounts.add(account) if (showingThreadedList && messageListItem.threadCount > 1) { Loading @@ -1011,7 +944,6 @@ class MessageListFragment : messageIdList.add(messageListItem.databaseId) } } } for (account in accounts) { messageMap[account]?.let { messageIds -> Loading Loading @@ -1276,10 +1208,6 @@ class MessageListFragment : super.onStop() } fun selectAll() { setSelectionState(true) } fun onMoveUp() { var currentPosition = listView.selectedItemPosition if (currentPosition == AdapterView.INVALID_POSITION || listView.isInTouchMode) { Loading Loading @@ -1346,14 +1274,8 @@ class MessageListFragment : return listViewToAdapterPosition(listViewPosition) } private val checkedMessages: List<MessageReference> get() { return adapter.messages .asSequence() .filter { it.uniqueId in selected } .map { MessageReference(it.account.uuid, it.folderId, it.messageUid) } .toList() } private val selectedMessages: List<MessageReference> get() = adapter.selectedMessages.map { it.messageReference } fun onDelete() { selectedMessage?.let { message -> Loading Loading @@ -1484,14 +1406,15 @@ class MessageListFragment : } } cleanupSelected(messageListItems) adapter.selected = selected adapter.messages = messageListItems rememberedSelected?.let { rememberedSelected = null adapter.restoreSelected(it) } resetActionMode() computeBatchDirection() computeSelectAllVisibility() if (savedListState != null) { handler.restoreListPosition(savedListState) Loading @@ -1506,19 +1429,10 @@ class MessageListFragment : } } private fun cleanupSelected(messageListItems: List<MessageListItem>) { if (selected.isEmpty()) return selected = messageListItems.asSequence() .map { it.uniqueId } .filter { it in selected } .toMutableSet() } private fun resetActionMode() { if (!isResumed) return if (!isActive || selected.isEmpty()) { if (!isActive || adapter.selected.isEmpty()) { actionMode?.finish() actionMode = null return Loading @@ -1528,7 +1442,6 @@ class MessageListFragment : startAndPrepareActionMode() } recalculateSelectionCount() updateActionMode() } Loading @@ -1537,18 +1450,6 @@ class MessageListFragment : actionMode?.invalidate() } private fun recalculateSelectionCount() { if (!showingThreadedList) { selectedCount = selected.size return } selectedCount = adapter.messages .asSequence() .filter { it.uniqueId in selected } .sumOf { it.threadCount.coerceAtLeast(1) } } fun remoteSearchFinished() { remoteSearchFuture = null } Loading Loading @@ -1846,12 +1747,7 @@ class MessageListFragment : } private val accountUuidsForSelected: Set<String> get() { return adapter.messages.asSequence() .filter { it.uniqueId in selected } .map { it.account.uuid } .toSet() } get() = adapter.selectedMessages.mapToSet { it.account.uuid } override fun onDestroyActionMode(mode: ActionMode) { actionMode = null Loading @@ -1861,7 +1757,7 @@ class MessageListFragment : flag = null unflag = null setSelectionState(false) adapter.clearSelected() } override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { Loading Loading @@ -1942,42 +1838,58 @@ class MessageListFragment : override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { // In the following we assume that we can't move or copy mails to the same folder. Also that spam isn't // available if we are in the spam folder, same for archive. when (item.itemId) { val endSelectionMode = when (item.itemId) { R.id.delete -> { val messages = checkedMessages onDelete(messages) selectedCount = 0 } R.id.mark_as_read -> setFlagForSelected(Flag.SEEN, true) R.id.mark_as_unread -> setFlagForSelected(Flag.SEEN, false) R.id.flag -> setFlagForSelected(Flag.FLAGGED, true) R.id.unflag -> setFlagForSelected(Flag.FLAGGED, false) R.id.select_all -> selectAll() onDelete(selectedMessages) true } R.id.mark_as_read -> { setFlagForSelected(Flag.SEEN, true) false } R.id.mark_as_unread -> { setFlagForSelected(Flag.SEEN, false) false } R.id.flag -> { setFlagForSelected(Flag.FLAGGED, true) false } R.id.unflag -> { setFlagForSelected(Flag.FLAGGED, false) false } R.id.select_all -> { selectAll() false } R.id.archive -> { onArchive(checkedMessages) onArchive(selectedMessages) // TODO: Only finish action mode if all messages have been moved. selectedCount = 0 true } R.id.spam -> { onSpam(checkedMessages) onSpam(selectedMessages) // TODO: Only finish action mode if all messages have been moved. selectedCount = 0 true } R.id.move -> { onMove(checkedMessages) selectedCount = 0 onMove(selectedMessages) true } R.id.move_to_drafts -> { onMoveToDraftsFolder(checkedMessages) selectedCount = 0 onMoveToDraftsFolder(selectedMessages) true } R.id.copy -> { onCopy(checkedMessages) selectedCount = 0 onCopy(selectedMessages) true } else -> return false } if (selectedCount == 0) { if (endSelectionMode) { mode.finish() } Loading