Loading legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListAdapter.kt +78 −68 Original line number Diff line number Diff line Loading @@ -11,7 +11,6 @@ import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.NO_POSITION import app.k9mail.feature.launcher.FeatureLauncherActivity import app.k9mail.feature.launcher.FeatureLauncherTarget import app.k9mail.legacy.message.controller.MessageReference Loading @@ -31,6 +30,7 @@ import net.thunderbird.core.ui.theme.api.FeatureThemeProvider import net.thunderbird.feature.notification.api.ui.action.NotificationAction private const val FOOTER_ID = 1L private const val IN_APP_NOTIFICATION_BANNER_INLINE_LIST_ID = -1L private const val TYPE_MESSAGE = 0 private const val TYPE_FOOTER = 1 Loading @@ -51,14 +51,15 @@ class MessageListAdapter internal constructor( val colors: MessageViewHolderColors = MessageViewHolderColors.resolveColors(theme) var messages: List<MessageListItem> = emptyList() var viewItems: List<MessageListViewItem> = emptyList() @SuppressLint("NotifyDataSetChanged") set(value) { val oldMessageList = field field = value accountUuids = value.map { it.account.uuid }.toSet() messagesMap = value.associateBy { it.uniqueId } val messages = value.filterMessageListItem() accountUuids = messages.map { it.account.uuid }.toSet() messagesMap = messages.associateBy { it.uniqueId } if (selected.isNotEmpty()) { val uniqueIds = messagesMap.keys Loading @@ -71,6 +72,7 @@ class MessageListAdapter internal constructor( diffResult.dispatchUpdatesTo(this) } private val messages get() = viewItems.filterMessageListItem() private var messagesMap = emptyMap<Long, MessageListItem>() private var accountUuids = emptySet<String>() Loading Loading @@ -122,34 +124,6 @@ class MessageListAdapter internal constructor( var selectedCount: Int = 0 private set var footerText: String? = null set(value) { if (field == value) return val hadFooterText = field != null val previousFooterPosition = footerPosition field = value if (hadFooterText) { if (value == null) { notifyItemRemoved(previousFooterPosition) } else { notifyItemChanged(footerPosition) } } else { notifyItemInserted(footerPosition) } } private val hasFooter: Boolean get() = footerText != null private val lastMessagePosition: Int get() = messages.lastIndex private val footerPosition: Int get() = if (hasFooter) lastMessagePosition + 1 else NO_POSITION private val messageClickedListener = OnClickListener { view: View -> val messageListItem = getItemFromView(view) ?: return@OnClickListener listItemListener.onMessageClicked(messageListItem) Loading Loading @@ -185,32 +159,26 @@ class MessageListAdapter internal constructor( setHasStableIds(true) } override fun getItemCount(): Int = messages.size + if (hasFooter) 1 else 0 override fun getItemCount(): Int = viewItems.size override fun getItemId(position: Int): Long { return if (position <= lastMessagePosition) { messages[position].uniqueId } else { FOOTER_ID } return viewItems[position].viewId } override fun getItemViewType(position: Int): Int { return when { position == 0 && isInAppNotificationEnabled -> TYPE_IN_APP_NOTIFICATION_BANNER_INLINE_LIST position <= lastMessagePosition -> TYPE_MESSAGE else -> TYPE_FOOTER } return viewItems[position].viewType } private fun getItem(position: Int): MessageListItem = messages[position] private fun getItem(position: Int): MessageListItem = (viewItems[position] as MessageListViewItem.Message).item fun getItemById(uniqueId: Long): MessageListItem? { return messagesMap[uniqueId] } fun getItem(messageReference: MessageReference): MessageListItem? { return messages.firstOrNull { return viewItems .filterMessageListItem() .firstOrNull { it.account.uuid == messageReference.accountUuid && it.folderId == messageReference.folderId && it.messageUid == messageReference.uid Loading @@ -218,15 +186,21 @@ class MessageListAdapter internal constructor( } fun getPosition(messageListItem: MessageListItem): Int? { return messages.indexOf(messageListItem).takeIf { it != -1 } return viewItems .map { (it as? MessageListViewItem.Message)?.item } .indexOf(messageListItem).takeIf { it != -1 } } private fun getPosition(messageReference: MessageReference?): Int? { if (messageReference == null) return null return messages.indexOfFirst { return viewItems .map { (it as? MessageListViewItem.Message)?.item } .indexOfFirst { it != null && messageReference.equals(it.account.uuid, it.folderId, it.messageUid) }.takeIf { it != -1 } } .takeIf { it != -1 } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageListViewHolder { Loading @@ -241,6 +215,7 @@ class MessageListAdapter internal constructor( } TYPE_FOOTER -> FooterViewHolder.create(layoutInflater, parent, footerClickListener) TYPE_IN_APP_NOTIFICATION_BANNER_INLINE_LIST if isInAppNotificationEnabled -> BannerInlineListInAppNotificationViewHolder( view = ComposeView(context = parent.context), Loading Loading @@ -325,7 +300,8 @@ class MessageListAdapter internal constructor( TYPE_FOOTER -> { val footerViewHolder = holder as FooterViewHolder footerViewHolder.bind(footerText) val footer = viewItems[position] as MessageListViewItem.Footer footerViewHolder.bind(footer.text) } else -> { Loading Loading @@ -386,19 +362,18 @@ class MessageListAdapter internal constructor( } private fun calculateSelectionCount(): Int { if (selected.isEmpty()) { return 0 } if (!appearance.showingThreadedList) { return selected.size } return messages return when { selected.isEmpty() -> 0 !appearance.showingThreadedList -> selected.size else -> viewItems .asSequence() .filterIsInstance<MessageListViewItem.Message>() .map { it.item } .filter { it.uniqueId in selected } .sumOf { it.threadCount.coerceAtLeast(1) } } } private fun getItemFromView(view: View): MessageListItem? { if (featureFlagProvider.provide(UseComposeForMessageListItems).isEnabled()) { Loading @@ -409,18 +384,33 @@ class MessageListAdapter internal constructor( return getItemById(messageViewHolder.uniqueId) } } private fun List<MessageListViewItem>.filterMessageListItem(): List<MessageListItem> = filterIsInstance<MessageListViewItem.Message>() .map { it.item } } private class MessageListDiffCallback( private val oldMessageList: List<MessageListItem>, private val newMessageList: List<MessageListItem>, private val oldMessageList: List<MessageListViewItem>, private val newMessageList: List<MessageListViewItem>, ) : DiffUtil.Callback() { override fun getOldListSize(): Int = oldMessageList.size override fun getNewListSize(): Int = newMessageList.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldMessageList[oldItemPosition].uniqueId == newMessageList[newItemPosition].uniqueId val oldItem = oldMessageList[oldItemPosition] val newItem = newMessageList[newItemPosition] return when (oldItem) { is MessageListViewItem.InAppNotificationBannerList if newItem is MessageListViewItem.InAppNotificationBannerList -> true is MessageListViewItem.Message if newItem is MessageListViewItem.Message -> oldItem.item.uniqueId == newItem.item.uniqueId is MessageListViewItem.Footer if newItem is MessageListViewItem.Footer -> true else -> false } } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { Loading @@ -434,3 +424,23 @@ interface MessageListItemActionListener { fun onToggleMessageFlag(item: MessageListItem) fun onFooterClicked() } sealed interface MessageListViewItem { val viewId: Long val viewType: Int data object InAppNotificationBannerList : MessageListViewItem { override val viewId: Long = IN_APP_NOTIFICATION_BANNER_INLINE_LIST_ID override val viewType: Int = TYPE_IN_APP_NOTIFICATION_BANNER_INLINE_LIST } data class Message(val item: MessageListItem) : MessageListViewItem { override val viewId: Long get() = item.uniqueId override val viewType: Int = TYPE_MESSAGE } data class Footer(val text: String) : MessageListViewItem { override val viewId: Long = FOOTER_ID override val viewType: Int = TYPE_FOOTER } } legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt +20 −3 Original line number Diff line number Diff line Loading @@ -1131,11 +1131,20 @@ class MessageListFragment : } fun updateFooterText(text: String?) { adapter.footerText = text val currentItems = adapter .viewItems .filter { it !is MessageListViewItem.Footer } .toMutableList() if (!text.isNullOrEmpty()) { currentItems.add(MessageListViewItem.Footer(text)) } adapter.viewItems = currentItems } private fun selectAll() { if (adapter.messages.isEmpty()) { if (adapter.viewItems.isEmpty()) { // Nothing to do if there are no messages return } Loading Loading @@ -1452,6 +1461,7 @@ class MessageListFragment : destinationFolderId, ) } FolderOperation.MOVE -> { messagingController.moveMessages( account.id, Loading @@ -1460,6 +1470,7 @@ class MessageListFragment : destinationFolderId, ) } FolderOperation.COPY if showingThreadedList -> { messagingController.copyMessagesInThread( account.id, Loading @@ -1468,6 +1479,7 @@ class MessageListFragment : destinationFolderId, ) } FolderOperation.COPY -> { messagingController.copyMessages( account.id, Loading Loading @@ -1735,7 +1747,12 @@ class MessageListFragment : } } adapter.messages = messageListItems adapter.viewItems = buildList { if (featureFlagProvider.provide(FeatureFlagKey.DisplayInAppNotifications).isEnabled()) { add(MessageListViewItem.InAppNotificationBannerList) } addAll(messageListItems.map { MessageListViewItem.Message(it) }) } rememberedSelected?.let { rememberedSelected = null Loading legacy/ui/legacy/src/test/java/com/fsck/k9/ui/messagelist/MessageListAdapterTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -531,7 +531,7 @@ class MessageListAdapterTest : RobolectricTest() { } fun MessageListAdapter.createAndBindView(item: MessageListItem = createMessageListItem()): View { messages = listOf(item) viewItems = listOf(MessageListViewItem.Message(item)) val holder = onCreateViewHolder(LinearLayout(context), 0) onBindViewHolder(holder, 0) return holder.itemView Loading Loading
legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListAdapter.kt +78 −68 Original line number Diff line number Diff line Loading @@ -11,7 +11,6 @@ import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.NO_POSITION import app.k9mail.feature.launcher.FeatureLauncherActivity import app.k9mail.feature.launcher.FeatureLauncherTarget import app.k9mail.legacy.message.controller.MessageReference Loading @@ -31,6 +30,7 @@ import net.thunderbird.core.ui.theme.api.FeatureThemeProvider import net.thunderbird.feature.notification.api.ui.action.NotificationAction private const val FOOTER_ID = 1L private const val IN_APP_NOTIFICATION_BANNER_INLINE_LIST_ID = -1L private const val TYPE_MESSAGE = 0 private const val TYPE_FOOTER = 1 Loading @@ -51,14 +51,15 @@ class MessageListAdapter internal constructor( val colors: MessageViewHolderColors = MessageViewHolderColors.resolveColors(theme) var messages: List<MessageListItem> = emptyList() var viewItems: List<MessageListViewItem> = emptyList() @SuppressLint("NotifyDataSetChanged") set(value) { val oldMessageList = field field = value accountUuids = value.map { it.account.uuid }.toSet() messagesMap = value.associateBy { it.uniqueId } val messages = value.filterMessageListItem() accountUuids = messages.map { it.account.uuid }.toSet() messagesMap = messages.associateBy { it.uniqueId } if (selected.isNotEmpty()) { val uniqueIds = messagesMap.keys Loading @@ -71,6 +72,7 @@ class MessageListAdapter internal constructor( diffResult.dispatchUpdatesTo(this) } private val messages get() = viewItems.filterMessageListItem() private var messagesMap = emptyMap<Long, MessageListItem>() private var accountUuids = emptySet<String>() Loading Loading @@ -122,34 +124,6 @@ class MessageListAdapter internal constructor( var selectedCount: Int = 0 private set var footerText: String? = null set(value) { if (field == value) return val hadFooterText = field != null val previousFooterPosition = footerPosition field = value if (hadFooterText) { if (value == null) { notifyItemRemoved(previousFooterPosition) } else { notifyItemChanged(footerPosition) } } else { notifyItemInserted(footerPosition) } } private val hasFooter: Boolean get() = footerText != null private val lastMessagePosition: Int get() = messages.lastIndex private val footerPosition: Int get() = if (hasFooter) lastMessagePosition + 1 else NO_POSITION private val messageClickedListener = OnClickListener { view: View -> val messageListItem = getItemFromView(view) ?: return@OnClickListener listItemListener.onMessageClicked(messageListItem) Loading Loading @@ -185,32 +159,26 @@ class MessageListAdapter internal constructor( setHasStableIds(true) } override fun getItemCount(): Int = messages.size + if (hasFooter) 1 else 0 override fun getItemCount(): Int = viewItems.size override fun getItemId(position: Int): Long { return if (position <= lastMessagePosition) { messages[position].uniqueId } else { FOOTER_ID } return viewItems[position].viewId } override fun getItemViewType(position: Int): Int { return when { position == 0 && isInAppNotificationEnabled -> TYPE_IN_APP_NOTIFICATION_BANNER_INLINE_LIST position <= lastMessagePosition -> TYPE_MESSAGE else -> TYPE_FOOTER } return viewItems[position].viewType } private fun getItem(position: Int): MessageListItem = messages[position] private fun getItem(position: Int): MessageListItem = (viewItems[position] as MessageListViewItem.Message).item fun getItemById(uniqueId: Long): MessageListItem? { return messagesMap[uniqueId] } fun getItem(messageReference: MessageReference): MessageListItem? { return messages.firstOrNull { return viewItems .filterMessageListItem() .firstOrNull { it.account.uuid == messageReference.accountUuid && it.folderId == messageReference.folderId && it.messageUid == messageReference.uid Loading @@ -218,15 +186,21 @@ class MessageListAdapter internal constructor( } fun getPosition(messageListItem: MessageListItem): Int? { return messages.indexOf(messageListItem).takeIf { it != -1 } return viewItems .map { (it as? MessageListViewItem.Message)?.item } .indexOf(messageListItem).takeIf { it != -1 } } private fun getPosition(messageReference: MessageReference?): Int? { if (messageReference == null) return null return messages.indexOfFirst { return viewItems .map { (it as? MessageListViewItem.Message)?.item } .indexOfFirst { it != null && messageReference.equals(it.account.uuid, it.folderId, it.messageUid) }.takeIf { it != -1 } } .takeIf { it != -1 } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageListViewHolder { Loading @@ -241,6 +215,7 @@ class MessageListAdapter internal constructor( } TYPE_FOOTER -> FooterViewHolder.create(layoutInflater, parent, footerClickListener) TYPE_IN_APP_NOTIFICATION_BANNER_INLINE_LIST if isInAppNotificationEnabled -> BannerInlineListInAppNotificationViewHolder( view = ComposeView(context = parent.context), Loading Loading @@ -325,7 +300,8 @@ class MessageListAdapter internal constructor( TYPE_FOOTER -> { val footerViewHolder = holder as FooterViewHolder footerViewHolder.bind(footerText) val footer = viewItems[position] as MessageListViewItem.Footer footerViewHolder.bind(footer.text) } else -> { Loading Loading @@ -386,19 +362,18 @@ class MessageListAdapter internal constructor( } private fun calculateSelectionCount(): Int { if (selected.isEmpty()) { return 0 } if (!appearance.showingThreadedList) { return selected.size } return messages return when { selected.isEmpty() -> 0 !appearance.showingThreadedList -> selected.size else -> viewItems .asSequence() .filterIsInstance<MessageListViewItem.Message>() .map { it.item } .filter { it.uniqueId in selected } .sumOf { it.threadCount.coerceAtLeast(1) } } } private fun getItemFromView(view: View): MessageListItem? { if (featureFlagProvider.provide(UseComposeForMessageListItems).isEnabled()) { Loading @@ -409,18 +384,33 @@ class MessageListAdapter internal constructor( return getItemById(messageViewHolder.uniqueId) } } private fun List<MessageListViewItem>.filterMessageListItem(): List<MessageListItem> = filterIsInstance<MessageListViewItem.Message>() .map { it.item } } private class MessageListDiffCallback( private val oldMessageList: List<MessageListItem>, private val newMessageList: List<MessageListItem>, private val oldMessageList: List<MessageListViewItem>, private val newMessageList: List<MessageListViewItem>, ) : DiffUtil.Callback() { override fun getOldListSize(): Int = oldMessageList.size override fun getNewListSize(): Int = newMessageList.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldMessageList[oldItemPosition].uniqueId == newMessageList[newItemPosition].uniqueId val oldItem = oldMessageList[oldItemPosition] val newItem = newMessageList[newItemPosition] return when (oldItem) { is MessageListViewItem.InAppNotificationBannerList if newItem is MessageListViewItem.InAppNotificationBannerList -> true is MessageListViewItem.Message if newItem is MessageListViewItem.Message -> oldItem.item.uniqueId == newItem.item.uniqueId is MessageListViewItem.Footer if newItem is MessageListViewItem.Footer -> true else -> false } } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { Loading @@ -434,3 +424,23 @@ interface MessageListItemActionListener { fun onToggleMessageFlag(item: MessageListItem) fun onFooterClicked() } sealed interface MessageListViewItem { val viewId: Long val viewType: Int data object InAppNotificationBannerList : MessageListViewItem { override val viewId: Long = IN_APP_NOTIFICATION_BANNER_INLINE_LIST_ID override val viewType: Int = TYPE_IN_APP_NOTIFICATION_BANNER_INLINE_LIST } data class Message(val item: MessageListItem) : MessageListViewItem { override val viewId: Long get() = item.uniqueId override val viewType: Int = TYPE_MESSAGE } data class Footer(val text: String) : MessageListViewItem { override val viewId: Long = FOOTER_ID override val viewType: Int = TYPE_FOOTER } }
legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt +20 −3 Original line number Diff line number Diff line Loading @@ -1131,11 +1131,20 @@ class MessageListFragment : } fun updateFooterText(text: String?) { adapter.footerText = text val currentItems = adapter .viewItems .filter { it !is MessageListViewItem.Footer } .toMutableList() if (!text.isNullOrEmpty()) { currentItems.add(MessageListViewItem.Footer(text)) } adapter.viewItems = currentItems } private fun selectAll() { if (adapter.messages.isEmpty()) { if (adapter.viewItems.isEmpty()) { // Nothing to do if there are no messages return } Loading Loading @@ -1452,6 +1461,7 @@ class MessageListFragment : destinationFolderId, ) } FolderOperation.MOVE -> { messagingController.moveMessages( account.id, Loading @@ -1460,6 +1470,7 @@ class MessageListFragment : destinationFolderId, ) } FolderOperation.COPY if showingThreadedList -> { messagingController.copyMessagesInThread( account.id, Loading @@ -1468,6 +1479,7 @@ class MessageListFragment : destinationFolderId, ) } FolderOperation.COPY -> { messagingController.copyMessages( account.id, Loading Loading @@ -1735,7 +1747,12 @@ class MessageListFragment : } } adapter.messages = messageListItems adapter.viewItems = buildList { if (featureFlagProvider.provide(FeatureFlagKey.DisplayInAppNotifications).isEnabled()) { add(MessageListViewItem.InAppNotificationBannerList) } addAll(messageListItems.map { MessageListViewItem.Message(it) }) } rememberedSelected?.let { rememberedSelected = null Loading
legacy/ui/legacy/src/test/java/com/fsck/k9/ui/messagelist/MessageListAdapterTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -531,7 +531,7 @@ class MessageListAdapterTest : RobolectricTest() { } fun MessageListAdapter.createAndBindView(item: MessageListItem = createMessageListItem()): View { messages = listOf(item) viewItems = listOf(MessageListViewItem.Message(item)) val holder = onCreateViewHolder(LinearLayout(context), 0) onBindViewHolder(holder, 0) return holder.itemView Loading