Switch message list from ListView to RecyclerView

This commit is contained in:
cketti 2022-09-12 20:41:34 +02:00
parent fbe8eca814
commit 2edf42f9f8
12 changed files with 256 additions and 188 deletions

View file

@ -1,9 +1,8 @@
package com.fsck.k9.fragment
import android.content.Context
import android.annotation.SuppressLint
import android.content.res.Resources
import android.content.res.Resources.Theme
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.text.Spannable
@ -14,12 +13,15 @@ import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.view.View
import android.view.View.OnClickListener
import android.view.View.OnLongClickListener
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ImageView
import android.widget.TextView
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
import com.fsck.k9.FontSizes
import com.fsck.k9.contacts.ContactPictureLoader
import com.fsck.k9.controller.MessageReference
@ -32,8 +34,12 @@ import com.fsck.k9.ui.resolveColorAttribute
import com.fsck.k9.ui.resolveDrawableAttribute
import kotlin.math.max
private const val FOOTER_ID = 1L
private const val TYPE_MESSAGE = 0
private const val TYPE_FOOTER = 1
class MessageListAdapter internal constructor(
private val context: Context,
theme: Theme,
private val res: Resources,
private val layoutInflater: LayoutInflater,
@ -41,7 +47,7 @@ class MessageListAdapter internal constructor(
private val listItemListener: MessageListItemActionListener,
private val appearance: MessageListAppearance,
private val relativeDateTimeFormatter: RelativeDateTimeFormatter
) : BaseAdapter() {
) : RecyclerView.Adapter<MessageListViewHolder>() {
private val forwardedIcon: Drawable = theme.resolveDrawableAttribute(R.attr.messageListForwarded)
private val answeredIcon: Drawable = theme.resolveDrawableAttribute(R.attr.messageListAnswered)
@ -49,11 +55,15 @@ class MessageListAdapter internal constructor(
private val previewTextColor: Int = theme.resolveColorAttribute(R.attr.messageListPreviewTextColor)
private val activeItemBackgroundColor: Int = theme.resolveColorAttribute(R.attr.messageListActiveItemBackgroundColor)
private val selectedItemBackgroundColor: Int = theme.resolveColorAttribute(R.attr.messageListSelectedBackgroundColor)
private val regularItemBackgroundColor: Int = theme.resolveColorAttribute(R.attr.messageListUnreadItemBackgroundColor)
private val readItemBackgroundColor: Int = theme.resolveColorAttribute(R.attr.messageListReadItemBackgroundColor)
private val unreadItemBackgroundColor: Int = theme.resolveColorAttribute(R.attr.messageListUnreadItemBackgroundColor)
var messages: List<MessageListItem> = emptyList()
@SuppressLint("NotifyDataSetChanged")
set(value) {
val oldMessageList = field
field = value
messagesMap = value.associateBy { it.uniqueId }
@ -62,18 +72,57 @@ class MessageListAdapter internal constructor(
selected = selected.intersect(uniqueIds)
}
notifyDataSetChanged()
if (oldMessageList.isEmpty()) {
// While loading, only the footer view is showing. If we used DiffUtil, the footer view would be used as
// anchor element and the updated list would be scrolled all the way down.
notifyDataSetChanged()
} else {
val diffResult = DiffUtil.calculateDiff(
MessageListDiffCallback(oldMessageList = oldMessageList, newMessageList = value)
)
diffResult.dispatchUpdatesTo(this)
}
}
private var messagesMap = emptyMap<Long, MessageListItem>()
var activeMessage: MessageReference? = null
set(value) {
if (value == field) return
val oldPosition = getPosition(field)
val newPosition = getPosition(value)
field = value
oldPosition?.let { position -> notifyItemChanged(position) }
newPosition?.let { position -> notifyItemChanged(position) }
}
var selected: Set<Long> = emptySet()
private set(value) {
if (value == field) return
// Selection removed
field.asSequence()
.filter { uniqueId -> uniqueId !in value }
.mapNotNull { uniqueId -> messagesMap[uniqueId] }
.mapNotNull { messageListItem -> getPosition(messageListItem) }
.forEach { position ->
notifyItemChanged(position)
}
// Selection added
value.asSequence()
.filter { uniqueId -> uniqueId !in field }
.mapNotNull { uniqueId -> messagesMap[uniqueId] }
.mapNotNull { messageListItem -> getPosition(messageListItem) }
.forEach { position ->
notifyItemChanged(position)
}
field = value
selectedCount = calculateSelectionCount()
notifyDataSetChanged()
}
val selectedMessages: List<MessageListItem>
@ -85,6 +134,29 @@ class MessageListAdapter internal constructor(
var selectedCount: Int = 0
private set
var footerText: String? = null
set(value) {
if (field == value) return
val hadFooterText = field != null
field = value
if (hadFooterText) {
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 inline val subjectViewFontSize: Int
get() = if (appearance.senderAboveSubject) {
appearance.fontSizes.messageListSender
@ -92,34 +164,59 @@ class MessageListAdapter internal constructor(
appearance.fontSizes.messageListSubject
}
private val messageClickedListener = OnClickListener { view: View ->
val messageListItem = getItemFromView(view)
listItemListener.onMessageClicked(messageListItem)
}
private val messageLongClickedListener = OnLongClickListener { view: View ->
val messageListItem = getItemFromView(view)
listItemListener.onToggleMessageSelection(messageListItem)
true
}
private val footerClickListener = OnClickListener {
listItemListener.onFooterClicked()
}
private val flagClickListener = OnClickListener { view: View ->
val messageViewHolder = view.tag as MessageViewHolder
val messageListItem = getItemById(messageViewHolder.uniqueId)
val messageListItem = getItemFromView(view)
listItemListener.onToggleMessageFlag(messageListItem)
}
private val contactPictureClickListener = OnClickListener { view: View ->
val parentView = view.parent.parent as View
val messageViewHolder = parentView.tag as MessageViewHolder
val messageListItem = getItemById(messageViewHolder.uniqueId)
val messageListItem = getItemFromView(parentView)
listItemListener.onToggleMessageSelection(messageListItem)
}
init {
setHasStableIds(true)
}
private fun recipientSigil(toMe: Boolean, ccMe: Boolean) = when {
toMe -> res.getString(R.string.messagelist_sent_to_me_sigil) + " "
ccMe -> res.getString(R.string.messagelist_sent_cc_me_sigil) + " "
else -> ""
}
override fun hasStableIds(): Boolean = true
override fun getItemCount(): Int = messages.size + if (hasFooter) 1 else 0
override fun getCount(): Int = messages.size
override fun getItemId(position: Int): Long {
return if (position <= lastMessagePosition) {
messages[position].uniqueId
} else {
FOOTER_ID
}
}
override fun getItemId(position: Int): Long = messages[position].uniqueId
override fun getItemViewType(position: Int): Int {
return if (position <= lastMessagePosition) TYPE_MESSAGE else TYPE_FOOTER
}
override fun getItem(position: Int): MessageListItem = messages[position]
private fun getItem(position: Int): MessageListItem = messages[position]
private fun getItemById(uniqueId: Long): MessageListItem {
fun getItemById(uniqueId: Long): MessageListItem {
return messagesMap[uniqueId]!!
}
@ -135,16 +232,26 @@ class MessageListAdapter internal constructor(
return messages.indexOf(messageListItem).takeIf { it != -1 }
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val message = getItem(position)
val view: View = convertView ?: newView(parent)
bindView(view, context, message)
private fun getPosition(messageReference: MessageReference?): Int? {
if (messageReference == null) return null
return view
return messages.indexOfFirst {
messageReference.equals(it.account.uuid, it.folderId, it.messageUid)
}.takeIf { it != -1 }
}
private fun newView(parent: ViewGroup?): View {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageListViewHolder {
return when (viewType) {
TYPE_MESSAGE -> createMessageViewHolder(parent)
TYPE_FOOTER -> createFooterViewHolder(parent)
else -> error("Unsupported type: $viewType")
}
}
private fun createMessageViewHolder(parent: ViewGroup?): MessageViewHolder {
val view = layoutInflater.inflate(R.layout.message_list_item, parent, false)
view.setOnClickListener(messageClickedListener)
view.setOnLongClickListener(messageLongClickedListener)
val holder = MessageViewHolder(view)
@ -168,14 +275,33 @@ class MessageListAdapter internal constructor(
view.tag = holder
return view
return holder
}
private fun bindView(view: View, context: Context, message: MessageListItem) {
val isSelected = selected.contains(message.uniqueId)
val isActive = isActiveMessage(message)
private fun createFooterViewHolder(parent: ViewGroup): MessageListViewHolder {
val view = layoutInflater.inflate(R.layout.message_list_item_footer, parent, false)
view.setOnClickListener(footerClickListener)
return FooterViewHolder(view)
}
val holder = view.tag as MessageViewHolder
override fun onBindViewHolder(holder: MessageListViewHolder, position: Int) {
when (val viewType = getItemViewType(position)) {
TYPE_MESSAGE -> {
val messageListItem = getItem(position)
bindMessageViewHolder(holder as MessageViewHolder, messageListItem)
}
TYPE_FOOTER -> {
bindFooterViewHolder(holder as FooterViewHolder)
}
else -> {
error("Unsupported type: $viewType")
}
}
}
private fun bindMessageViewHolder(holder: MessageViewHolder, messageListItem: MessageListItem) {
val isSelected = selected.contains(messageListItem.uniqueId)
val isActive = isActiveMessage(messageListItem)
if (appearance.showContactPicture) {
if (isSelected) {
@ -187,7 +313,7 @@ class MessageListAdapter internal constructor(
}
}
with(message) {
with(messageListItem) {
val maybeBoldTypeface = if (isRead) Typeface.NORMAL else Typeface.BOLD
val displayDate = relativeDateTimeFormatter.formatDate(messageDate)
val displayThreadCount = if (appearance.showingThreadedList) threadCount else 0
@ -206,7 +332,7 @@ class MessageListAdapter internal constructor(
if (appearance.showContactPicture && holder.contactPicture.isVisible) {
setContactPicture(holder.contactPicture, displayAddress)
}
setBackgroundColor(view, isSelected, isRead, isActive)
setBackgroundColor(holder.itemView, isSelected, isRead, isActive)
updateWithThreadCount(holder, displayThreadCount)
val beforePreviewText = if (appearance.senderAboveSubject) subject else displayName
val sigil = recipientSigil(toMe, ccMe)
@ -240,6 +366,10 @@ class MessageListAdapter internal constructor(
}
}
private fun bindFooterViewHolder(holder: FooterViewHolder) {
holder.text.text = footerText
}
private fun formatPreviewText(
preview: TextView,
beforePreviewText: CharSequence,
@ -304,7 +434,7 @@ class MessageListAdapter internal constructor(
selected -> selectedItemBackgroundColor
backGroundAsReadIndicator && read -> readItemBackgroundColor
backGroundAsReadIndicator && !read -> unreadItemBackgroundColor
else -> Color.TRANSPARENT
else -> regularItemBackgroundColor
}
view.setBackgroundColor(backgroundColor)
@ -388,9 +518,33 @@ class MessageListAdapter internal constructor(
.filter { it.uniqueId in selected }
.sumOf { it.threadCount.coerceAtLeast(1) }
}
private fun getItemFromView(view: View): MessageListItem {
val messageViewHolder = view.tag as MessageViewHolder
return getItemById(messageViewHolder.uniqueId)
}
}
private class MessageListDiffCallback(
private val oldMessageList: List<MessageListItem>,
private val newMessageList: List<MessageListItem>
) : 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
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldMessageList[oldItemPosition] == newMessageList[newItemPosition]
}
}
interface MessageListItemActionListener {
fun onMessageClicked(messageListItem: MessageListItem)
fun onToggleMessageSelection(item: MessageListItem)
fun onToggleMessageFlag(item: MessageListItem)
fun onFooterClicked()
}

View file

@ -11,15 +11,14 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.AdapterView.OnItemClickListener
import android.widget.AdapterView.OnItemLongClickListener
import android.widget.ListView
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.view.ActionMode
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.fsck.k9.Account
import com.fsck.k9.Account.Expunge
@ -63,8 +62,6 @@ private const val MAXIMUM_MESSAGE_SORT_OVERRIDES = 3
class MessageListFragment :
Fragment(),
OnItemClickListener,
OnItemLongClickListener,
ConfirmationDialogFragmentListener,
MessageListItemActionListener {
@ -82,12 +79,9 @@ class MessageListFragment :
private lateinit var fragmentListener: MessageListFragmentListener
private lateinit var listView: ListView
private lateinit var recyclerView: RecyclerView
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private lateinit var adapter: MessageListAdapter
private var footerView: View? = null
private var savedListState: Parcelable? = null
private lateinit var accountUuids: Array<String>
private var account: Account? = null
@ -118,6 +112,7 @@ class MessageListFragment :
private set
private var isSingleFolderMode = false
private var isRemoteSearch = false
private var initialMessageListLoad = true
private val isUnifiedInbox: Boolean
get() = localSearch.id == SearchAccount.UNIFIED_INBOX
@ -173,7 +168,6 @@ class MessageListFragment :
activeMessages = savedInstanceState.getStringArray(STATE_ACTIVE_MESSAGES)?.map { MessageReference.parse(it)!! }
restoreSelectedMessages(savedInstanceState)
isRemoteSearch = savedInstanceState.getBoolean(STATE_REMOTE_SEARCH_PERFORMED)
savedListState = savedInstanceState.getParcelable(STATE_MESSAGE_LIST)
val messageReferenceString = savedInstanceState.getString(STATE_ACTIVE_MESSAGE)
activeMessage = MessageReference.parse(messageReferenceString)
}
@ -183,7 +177,7 @@ class MessageListFragment :
}
fun restoreListState(savedListState: Parcelable) {
listView.onRestoreInstanceState(savedListState)
recyclerView.layoutManager?.onRestoreInstanceState(savedListState)
}
private fun decodeArguments(): MessageListFragment? {
@ -223,7 +217,7 @@ class MessageListFragment :
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.message_list_fragment, container, false).apply {
initializeSwipeRefreshLayout(this)
initializeListView(this)
initializeRecyclerView(this)
}
}
@ -240,16 +234,13 @@ class MessageListFragment :
swipeRefreshLayout.isEnabled = false
}
private fun initializeListView(view: View) {
listView = view.findViewById(R.id.message_list)
with(listView) {
scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY
isLongClickable = true
isFastScrollEnabled = true
isVerticalFadingEdgeEnabled = false
isScrollingCacheEnabled = false
onItemClickListener = this@MessageListFragment
onItemLongClickListener = this@MessageListFragment
private fun initializeRecyclerView(view: View) {
recyclerView = view.findViewById(R.id.message_list)
val itemDecoration = DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)
recyclerView.addItemDecoration(itemDecoration)
recyclerView.itemAnimator = DefaultItemAnimator().apply {
supportsChangeAnimations = false
}
}
@ -265,7 +256,6 @@ class MessageListFragment :
private fun initializeMessageList() {
adapter = MessageListAdapter(
context = requireContext(),
theme = requireActivity().theme,
res = resources,
layoutInflater = layoutInflater,
@ -277,12 +267,7 @@ class MessageListFragment :
adapter.activeMessage = activeMessage
if (isSingleFolderMode) {
listView.addFooterView(getFooterView(listView))
updateFooter(null)
}
listView.adapter = adapter
recyclerView.adapter = adapter
}
private fun initializeSortSettings() {
@ -315,10 +300,9 @@ class MessageListFragment :
currentFolder?.let {
if (it.databaseId == folderId) {
it.loading = loading
updateFooterText()
}
}
updateFooterView()
}
fun updateTitle() {
@ -372,16 +356,7 @@ class MessageListFragment :
fragmentListener.setMessageListProgressEnabled(progress)
}
override fun onItemClick(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
if (view === footerView) {
handleFooterClick()
} else {
val messageListItem = adapter.getItem(position)
handleListItemClick(messageListItem)
}
}
private fun handleFooterClick() {
override fun onFooterClicked() {
val currentFolder = this.currentFolder ?: return
if (currentFolder.moreMessages && !localSearch.isManualSearch) {
@ -400,7 +375,7 @@ class MessageListFragment :
} else {
extraSearchResults = null
loadSearchResults = additionalSearchResults
updateFooter(null)
updateFooterText(null)
}
messagingController.loadSearchResults(
@ -412,7 +387,7 @@ class MessageListFragment :
}
}
private fun handleListItemClick(messageListItem: MessageListItem) {
override fun onMessageClicked(messageListItem: MessageListItem) {
if (adapter.selectedCount > 0) {
toggleMessageSelect(messageListItem)
} else {
@ -424,28 +399,17 @@ class MessageListFragment :
}
}
override fun onItemLongClick(parent: AdapterView<*>?, view: View, position: Int, id: Long): Boolean {
if (view === footerView) return false
val messageListItem = adapter.getItem(position)
toggleMessageSelect(messageListItem)
return true
}
override fun onDestroyView() {
if (isNewMessagesView && !requireActivity().isChangingConfigurations) {
messagingController.clearNewMessages(account)
}
savedListState = listView.onSaveInstanceState()
super.onDestroyView()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
saveListState(outState)
outState.putLongArray(STATE_SELECTED_MESSAGES, adapter.selected.toLongArray())
outState.putBoolean(STATE_REMOTE_SEARCH_PERFORMED, isRemoteSearch)
outState.putStringArray(
@ -457,15 +421,6 @@ class MessageListFragment :
}
}
private fun saveListState(outState: Bundle) {
if (savedListState != null) {
// The previously saved state was never restored, so just use that.
outState.putParcelable(STATE_MESSAGE_LIST, savedListState)
} else {
outState.putParcelable(STATE_MESSAGE_LIST, listView.onSaveInstanceState())
}
}
private val messageListAppearance: MessageListAppearance
get() = MessageListAppearance(
fontSizes = K9.fontSizes,
@ -789,26 +744,15 @@ class MessageListFragment :
messagingController.sendPendingMessages(account, null)
}
private fun getFooterView(parent: ViewGroup?): View? {
return footerView ?: createFooterView(parent).also { footerView = it }
}
private fun createFooterView(parent: ViewGroup?): View {
return layoutInflater.inflate(R.layout.message_list_item_footer, parent, false).apply {
tag = FooterViewHolder(this)
}
}
private fun updateFooterView() {
private fun updateFooterText() {
val currentFolder = this.currentFolder
val account = this.account
if (localSearch.isManualSearch || currentFolder == null || account == null) {
updateFooter(null)
return
}
val footerText = if (currentFolder.loading) {
val footerText = if (initialMessageListLoad) {
null
} else if (localSearch.isManualSearch || currentFolder == null || account == null) {
null
} else if (currentFolder.loading) {
getString(R.string.status_loading_more)
} else if (!currentFolder.moreMessages) {
null
@ -818,24 +762,11 @@ class MessageListFragment :
getString(R.string.load_more_messages_fmt, account.displayCount)
}
updateFooter(footerText)
updateFooterText(footerText)
}
fun updateFooter(text: String?) {
val footerView = this.footerView ?: return
val shouldHideFooter = text == null
if (shouldHideFooter) {
listView.removeFooterView(footerView)
} else {
val isFooterViewAddedToListView = listView.footerViewsCount > 0
if (!isFooterViewAddedToListView) {
listView.addFooterView(footerView)
}
}
val holder = footerView.tag as FooterViewHolder
holder.main.text = text
fun updateFooterText(text: String?) {
adapter.footerText = text
}
private fun selectAll() {
@ -1193,25 +1124,11 @@ class MessageListFragment :
}
fun onMoveUp() {
var currentPosition = listView.selectedItemPosition
if (currentPosition == AdapterView.INVALID_POSITION || listView.isInTouchMode) {
currentPosition = listView.firstVisiblePosition
}
if (currentPosition > 0) {
listView.setSelection(currentPosition - 1)
}
// FIXME
}
fun onMoveDown() {
var currentPosition = listView.selectedItemPosition
if (currentPosition == AdapterView.INVALID_POSITION || listView.isInTouchMode) {
currentPosition = listView.firstVisiblePosition
}
if (currentPosition < listView.count) {
listView.setSelection(currentPosition + 1)
}
// FIXME
}
fun openMessage(messageReference: MessageReference) {
@ -1227,8 +1144,9 @@ class MessageListFragment :
private val selectedMessageListItem: MessageListItem?
get() {
val position = listView.selectedItemPosition
return if (position !in 0 until adapter.count) null else adapter.getItem(position)
val focusedView = recyclerView.focusedChild ?: return null
val viewHolder = recyclerView.findContainingViewHolder(focusedView) as? MessageViewHolder ?: return null
return adapter.getItemById(viewHolder.uniqueId)
}
private val selectedMessages: List<MessageReference>
@ -1365,16 +1283,13 @@ class MessageListFragment :
resetActionMode()
computeBatchDirection()
if (savedListState != null) {
handler.restoreListPosition(savedListState)
savedListState = null
}
invalidateMenu()
initialMessageListLoad = false
currentFolder?.let { currentFolder ->
currentFolder.moreMessages = messageListInfo.hasMoreMessages
updateFooterView()
updateFooterText()
}
}
@ -1416,7 +1331,6 @@ class MessageListFragment :
// Redraw list immediately
if (::adapter.isInitialized) {
adapter.activeMessage = activeMessage
adapter.notifyDataSetChanged()
if (messageReference != null) {
scrollToMessage(messageReference)
@ -1459,8 +1373,11 @@ class MessageListFragment :
val messageListItem = adapter.getItem(messageReference) ?: return
val position = adapter.getPosition(messageListItem) ?: return
if (position <= listView.firstVisiblePosition || position >= listView.lastVisiblePosition) {
listView.smoothScrollToPosition(position)
val linearLayoutManager = recyclerView.layoutManager as LinearLayoutManager
val firstVisiblePosition = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
val lastVisiblePosition = linearLayoutManager.findLastCompletelyVisibleItemPosition()
if (position !in firstVisiblePosition..lastVisiblePosition) {
recyclerView.smoothScrollToPosition(position)
}
}
@ -1836,10 +1753,6 @@ class MessageListFragment :
}
}
internal class FooterViewHolder(view: View) {
val main: TextView = view.findViewById(R.id.main_text)
}
private enum class FolderOperation {
COPY, MOVE
}
@ -1873,7 +1786,6 @@ class MessageListFragment :
private const val STATE_ACTIVE_MESSAGES = "activeMessages"
private const val STATE_ACTIVE_MESSAGE = "activeMessage"
private const val STATE_REMOTE_SEARCH_PERFORMED = "remoteSearchPerformed"
private const val STATE_MESSAGE_LIST = "listState"
fun newInstance(search: LocalSearch, isThreadDisplay: Boolean, threadedList: Boolean): MessageListFragment {
return MessageListFragment().apply {

View file

@ -57,7 +57,7 @@ public class MessageListHandler extends Handler {
public void run() {
MessageListFragment fragment = mFragment.get();
if (fragment != null) {
fragment.updateFooter(message);
fragment.updateFooterText(message);
}
}
});

View file

@ -4,9 +4,12 @@ import android.view.View
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.fsck.k9.ui.R
class MessageViewHolder(view: View) {
sealed class MessageListViewHolder(view: View) : ViewHolder(view)
class MessageViewHolder(view: View) : MessageListViewHolder(view) {
var uniqueId: Long = -1L
val selected: View = view.findViewById(R.id.selected)
@ -20,3 +23,7 @@ class MessageViewHolder(view: View) {
val attachment: ImageView = view.findViewById(R.id.attachment)
val status: ImageView = view.findViewById(R.id.status)
}
class FooterViewHolder(view: View) : MessageListViewHolder(view) {
val text: TextView = view.findViewById(R.id.main_text)
}

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false" android:state_selected="true"
android:drawable="@color/message_list_item_footer_background" />
<item android:state_selected="true"
android:drawable="@android:color/transparent" />
<item android:state_pressed="true" android:state_selected="false"
android:drawable="@android:color/transparent" />
<item android:state_selected="false"
android:drawable="@android:color/transparent"
/>
</selector>

View file

@ -1,18 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swiperefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.fsck.k9.fragment.MessageListFragment">
<ListView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/message_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="5"
android:scrollbars="vertical"
android:scrollbarStyle="insideOverlay"
android:fadingEdge="none"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/message_list_item"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View file

@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?attr/selectableItemBackground"
android:orientation="horizontal"
android:layout_gravity="center_vertical"
tools:layout_height="?android:attr/listPreferredItemHeight"

View file

@ -4,7 +4,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:background="@drawable/message_list_item_footer_background"
android:background="?attr/messageListRegularItemBackgroundColor"
android:foreground="?attr/selectableItemBackground"
android:gravity="center"
android:orientation="horizontal">
<TextView

View file

@ -65,6 +65,7 @@
<attr name="textColorSecondaryRecipientDropdown" format="reference" />
<attr name="backgroundColorChooseAccountHeader" format="color" />
<attr name="messageListSelectedBackgroundColor" format="reference|color"/>
<attr name="messageListRegularItemBackgroundColor" format="reference|color"/>
<attr name="messageListReadItemBackgroundColor" format="reference|color"/>
<attr name="messageListUnreadItemBackgroundColor" format="reference|color"/>
<attr name="messageListThreadCountForegroundColor" format="reference|color"/>

View file

@ -4,8 +4,6 @@
<color name="light_black">#444444</color>
<color name="message_list_item_footer_background">#eeeeee</color>
<color name="status_todo_chevron">#888</color>
<color name="status_error_cross">#f44336</color>
<color name="status_ok_checkmark">#7bad45</color>

View file

@ -77,9 +77,10 @@
<item name="iconSettingsImportStatus">@drawable/ic_import_status</item>
<item name="textColorPrimaryRecipientDropdown">@android:color/primary_text_light</item>
<item name="textColorSecondaryRecipientDropdown">@android:color/secondary_text_light</item>
<item name="messageListSelectedBackgroundColor">#8038B8E2</item>
<item name="messageListReadItemBackgroundColor">#c0cdcdcd</item>
<item name="messageListUnreadItemBackgroundColor">#00ffffff</item>
<item name="messageListSelectedBackgroundColor">#ff99d9ee</item>
<item name="messageListRegularItemBackgroundColor">?android:attr/windowBackground</item>
<item name="messageListReadItemBackgroundColor">#ffd8d8d8</item>
<item name="messageListUnreadItemBackgroundColor">?attr/messageListRegularItemBackgroundColor</item>
<item name="messageListThreadCountForegroundColor">?android:attr/colorBackground</item>
<item name="messageListThreadCountBackground">@drawable/thread_count_box_light</item>
<item name="messageListActiveItemBackgroundColor">#ff2ea7d1</item>
@ -196,9 +197,10 @@
<item name="iconSettingsImportStatus">@drawable/ic_import_status</item>
<item name="textColorPrimaryRecipientDropdown">@android:color/primary_text_dark</item>
<item name="textColorSecondaryRecipientDropdown">@android:color/secondary_text_dark</item>
<item name="messageListSelectedBackgroundColor">#8038B8E2</item>
<item name="messageListReadItemBackgroundColor">#00000000</item>
<item name="messageListUnreadItemBackgroundColor">#c05a5a5a</item>
<item name="messageListSelectedBackgroundColor">#ff347489</item>
<item name="messageListRegularItemBackgroundColor">?android:attr/windowBackground</item>
<item name="messageListReadItemBackgroundColor">?attr/messageListRegularItemBackgroundColor</item>
<item name="messageListUnreadItemBackgroundColor">#ff505050</item>
<item name="messageListThreadCountForegroundColor">?android:attr/colorBackground</item>
<item name="messageListThreadCountBackground">@drawable/thread_count_box_dark</item>
<item name="messageListActiveItemBackgroundColor">#ff33b5e5</item>

View file

@ -457,7 +457,6 @@ class MessageListAdapterTest : RobolectricTest() {
)
return MessageListAdapter(
context = context,
theme = context.theme,
res = context.resources,
layoutInflater = LayoutInflater.from(context),
@ -518,7 +517,9 @@ class MessageListAdapterTest : RobolectricTest() {
fun MessageListAdapter.createAndBindView(item: MessageListItem = createMessageListItem()): View {
messages = listOf(item)
return getView(0, null, LinearLayout(context))
val holder = onCreateViewHolder(LinearLayout(context), 0)
onBindViewHolder(holder, 0)
return holder.itemView
}
fun secondLine(senderOrSubject: String, preview: String) = "$senderOrSubject $preview"