Merge pull request #4140 from k9mail/MessageListAdapter_refactoring

MessageListAdapter refactoring
This commit is contained in:
cketti 2019-08-05 14:30:14 +02:00 committed by GitHub
commit 2fe416b300
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 454 additions and 403 deletions

View file

@ -1,360 +0,0 @@
package com.fsck.k9.fragment;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.format.DateUtils;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.ForegroundColorSpan;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.TextView;
import com.fsck.k9.Account;
import com.fsck.k9.FontSizes;
import com.fsck.k9.K9;
import com.fsck.k9.ui.R;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mailstore.DatabasePreviewType;
import com.fsck.k9.ui.ContactBadge;
import static com.fsck.k9.fragment.MLFProjectionInfo.ANSWERED_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.ATTACHMENT_COUNT_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.CC_LIST_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.DATE_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.FLAGGED_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.FOLDER_SERVER_ID_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.FORWARDED_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.PREVIEW_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.PREVIEW_TYPE_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.READ_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.SENDER_LIST_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.SUBJECT_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.THREAD_COUNT_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.TO_LIST_COLUMN;
import static com.fsck.k9.fragment.MLFProjectionInfo.UID_COLUMN;
public class MessageListAdapter extends CursorAdapter {
private final MessageListFragment fragment;
private Drawable mForwardedIcon;
private Drawable mAnsweredIcon;
private Drawable mForwardedAnsweredIcon;
private int previewTextColor;
private int activeItemBackgroundColor;
private int selectedItemBackgroundColor;
private int readItemBackgroundColor;
private int unreadItemBackgroundColor;
private FontSizes fontSizes = K9.getFontSizes();
MessageListAdapter(MessageListFragment fragment) {
super(fragment.getActivity(), null, 0);
this.fragment = fragment;
int[] attributes = new int[] {
R.attr.messageListAnswered,
R.attr.messageListForwarded,
R.attr.messageListAnsweredForwarded,
R.attr.messageListPreviewTextColor,
R.attr.messageListActiveItemBackgroundColor,
R.attr.messageListSelectedBackgroundColor,
R.attr.messageListReadItemBackgroundColor,
R.attr.messageListUnreadItemBackgroundColor
};
Theme theme = fragment.requireActivity().getTheme();
TypedArray array = theme.obtainStyledAttributes(attributes);
Resources res = fragment.getResources();
mAnsweredIcon = res.getDrawable(array.getResourceId(0, R.drawable.ic_messagelist_answered_dark));
mForwardedIcon = res.getDrawable(array.getResourceId(1, R.drawable.ic_messagelist_forwarded_dark));
mForwardedAnsweredIcon = res.getDrawable(array.getResourceId(2, R.drawable.ic_messagelist_answered_forwarded_dark));
previewTextColor = array.getColor(3, Color.BLACK);
activeItemBackgroundColor = array.getColor(4, Color.BLACK);
selectedItemBackgroundColor = array.getColor(5, Color.BLACK);
readItemBackgroundColor = array.getColor(6, Color.BLACK);
unreadItemBackgroundColor = array.getColor(7, Color.BLACK);
array.recycle();
}
private String recipientSigil(boolean toMe, boolean ccMe) {
if (toMe) {
return fragment.getString(R.string.messagelist_sent_to_me_sigil);
} else if (ccMe) {
return fragment.getString(R.string.messagelist_sent_cc_me_sigil);
} else {
return "";
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = fragment.getK9LayoutInflater().inflate(R.layout.message_list_item, parent, false);
MessageViewHolder holder = new MessageViewHolder(fragment);
holder.date = view.findViewById(R.id.date);
holder.chip = view.findViewById(R.id.chip);
holder.attachment = view.findViewById(R.id.attachment);
holder.status = view.findViewById(R.id.status);
holder.preview = view.findViewById(R.id.preview);
holder.flagged = view.findViewById(R.id.star);
ContactBadge contactBadge = view.findViewById(R.id.contact_badge);
if (fragment.contactsPictureLoader != null) {
holder.contactBadge = contactBadge;
} else {
contactBadge.setVisibility(View.GONE);
}
if (fragment.senderAboveSubject) {
holder.from = view.findViewById(R.id.subject);
fontSizes.setViewTextSize(holder.from, fontSizes.getMessageListSender());
} else {
holder.subject = view.findViewById(R.id.subject);
fontSizes.setViewTextSize(holder.subject, fontSizes.getMessageListSubject());
}
fontSizes.setViewTextSize(holder.date, fontSizes.getMessageListDate());
// 1 preview line is needed even if it is set to 0, because subject is part of the same text view
holder.preview.setLines(Math.max(fragment.previewLines,1));
fontSizes.setViewTextSize(holder.preview, fontSizes.getMessageListPreview());
holder.threadCount = view.findViewById(R.id.thread_count);
fontSizes.setViewTextSize(holder.threadCount, fontSizes.getMessageListSubject()); // thread count is next to subject
view.findViewById(R.id.selected_checkbox_wrapper).setVisibility((fragment.checkboxes) ? View.VISIBLE : View.GONE);
holder.flagged.setVisibility(fragment.stars ? View.VISIBLE : View.GONE);
holder.flagged.setOnClickListener(holder);
holder.selected = view.findViewById(R.id.selected_checkbox);
holder.selected.setOnClickListener(holder);
view.setTag(holder);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
Account account = fragment.getAccountFromCursor(cursor);
String fromList = cursor.getString(SENDER_LIST_COLUMN);
String toList = cursor.getString(TO_LIST_COLUMN);
String ccList = cursor.getString(CC_LIST_COLUMN);
Address[] fromAddrs = Address.unpack(fromList);
Address[] toAddrs = Address.unpack(toList);
Address[] ccAddrs = Address.unpack(ccList);
boolean fromMe = fragment.messageHelper.toMe(account, fromAddrs);
boolean toMe = fragment.messageHelper.toMe(account, toAddrs);
boolean ccMe = fragment.messageHelper.toMe(account, ccAddrs);
CharSequence displayName = fragment.messageHelper.getDisplayName(account, fromAddrs, toAddrs);
CharSequence displayDate = DateUtils.getRelativeTimeSpanString(context, cursor.getLong(DATE_COLUMN));
Address counterpartyAddress = fetchCounterPartyAddress(fromMe, toAddrs, ccAddrs, fromAddrs);
int threadCount = (fragment.showingThreadedList) ? cursor.getInt(THREAD_COUNT_COLUMN) : 0;
String subject = MlfUtils.buildSubject(cursor.getString(SUBJECT_COLUMN),
fragment.getString(R.string.general_no_subject), threadCount);
boolean read = (cursor.getInt(READ_COLUMN) == 1);
boolean flagged = (cursor.getInt(FLAGGED_COLUMN) == 1);
boolean answered = (cursor.getInt(ANSWERED_COLUMN) == 1);
boolean forwarded = (cursor.getInt(FORWARDED_COLUMN) == 1);
boolean hasAttachments = (cursor.getInt(ATTACHMENT_COUNT_COLUMN) > 0);
MessageViewHolder holder = (MessageViewHolder) view.getTag();
int maybeBoldTypeface = (read) ? Typeface.NORMAL : Typeface.BOLD;
long uniqueId = cursor.getLong(fragment.uniqueIdColumn);
boolean selected = fragment.selected.contains(uniqueId);
holder.chip.setBackgroundColor(account.getChipColor());
if (fragment.checkboxes) {
holder.selected.setChecked(selected);
}
if (fragment.stars) {
holder.flagged.setChecked(flagged);
}
holder.position = cursor.getPosition();
if (holder.contactBadge != null) {
updateContactBadge(holder, counterpartyAddress);
}
setBackgroundColor(view, selected, read);
if (fragment.activeMessage != null) {
changeBackgroundColorIfActiveMessage(cursor, account, view);
}
updateWithThreadCount(holder, threadCount);
CharSequence beforePreviewText = (fragment.senderAboveSubject) ? subject : displayName;
String sigil = recipientSigil(toMe, ccMe);
SpannableStringBuilder messageStringBuilder = new SpannableStringBuilder(sigil)
.append(beforePreviewText);
if (fragment.previewLines > 0) {
String preview = getPreview(cursor);
messageStringBuilder.append(" ").append(preview);
}
holder.preview.setText(messageStringBuilder, TextView.BufferType.SPANNABLE);
formatPreviewText(holder.preview, beforePreviewText, sigil);
if (holder.from != null ) {
holder.from.setTypeface(Typeface.create(holder.from.getTypeface(), maybeBoldTypeface));
if (fragment.senderAboveSubject) {
holder.from.setText(displayName);
} else {
holder.from.setText(new SpannableStringBuilder(sigil).append(displayName));
}
}
if (holder.subject != null ) {
holder.subject.setTypeface(Typeface.create(holder.subject.getTypeface(), maybeBoldTypeface));
holder.subject.setText(subject);
}
holder.date.setText(displayDate);
holder.attachment.setVisibility(hasAttachments ? View.VISIBLE : View.GONE);
Drawable statusHolder = buildStatusHolder(forwarded, answered);
if (statusHolder != null) {
holder.status.setImageDrawable(statusHolder);
holder.status.setVisibility(View.VISIBLE);
} else {
holder.status.setVisibility(View.GONE);
}
}
private void formatPreviewText(TextView preview, CharSequence beforePreviewText, String sigil) {
Spannable previewText = (Spannable)preview.getText();
previewText.setSpan(buildSenderSpan(), 0, beforePreviewText.length() + sigil.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// Set span (color) for preview message
previewText.setSpan(new ForegroundColorSpan(previewTextColor), beforePreviewText.length() + sigil.length(),
previewText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
/**
* Create a span section for the sender, and assign the correct font size and weight
*/
private AbsoluteSizeSpan buildSenderSpan() {
int fontSize = (fragment.senderAboveSubject) ?
fontSizes.getMessageListSubject():
fontSizes.getMessageListSender();
return new AbsoluteSizeSpan(fontSize, true);
}
private Address fetchCounterPartyAddress(boolean fromMe, Address[] toAddrs, Address[] ccAddrs, Address[] fromAddrs) {
if (fromMe) {
if (toAddrs.length > 0) {
return toAddrs[0];
} else if (ccAddrs.length > 0) {
return ccAddrs[0];
}
} else if (fromAddrs.length > 0) {
return fromAddrs[0];
}
return null;
}
private void updateContactBadge(MessageViewHolder holder, Address counterpartyAddress) {
if (counterpartyAddress != null) {
holder.contactBadge.setContact(counterpartyAddress);
/*
* At least in Android 2.2 a different background + padding is used when no
* email address is available. ListView reuses the views but ContactBadge
* doesn't reset the padding, so we do it ourselves.
*/
holder.contactBadge.setPadding(0, 0, 0, 0);
fragment.contactsPictureLoader.setContactPicture(holder.contactBadge, counterpartyAddress);
} else {
holder.contactBadge.assignContactUri(null);
holder.contactBadge.setImageResource(R.drawable.ic_contact_picture);
}
}
private void changeBackgroundColorIfActiveMessage(Cursor cursor, Account account, View view) {
String uid = cursor.getString(UID_COLUMN);
String folderServerId = cursor.getString(FOLDER_SERVER_ID_COLUMN);
if (account.getUuid().equals(fragment.activeMessage.getAccountUuid()) &&
folderServerId.equals(fragment.activeMessage.getFolderServerId()) &&
uid.equals(fragment.activeMessage.getUid())) {
view.setBackgroundColor(activeItemBackgroundColor);
}
}
private Drawable buildStatusHolder(boolean forwarded, boolean answered) {
if (forwarded && answered) {
return mForwardedAnsweredIcon;
} else if (answered) {
return mAnsweredIcon;
} else if (forwarded) {
return mForwardedIcon;
}
return null;
}
private void setBackgroundColor(View view, boolean selected, boolean read) {
if (selected || K9.isUseBackgroundAsUnreadIndicator()) {
int color;
if (selected) {
color = selectedItemBackgroundColor;
} else if (read) {
color = readItemBackgroundColor;
} else {
color = unreadItemBackgroundColor;
}
view.setBackgroundColor(color);
} else {
view.setBackgroundColor(Color.TRANSPARENT);
}
}
private void updateWithThreadCount(MessageViewHolder holder, int threadCount) {
if (threadCount > 1) {
holder.threadCount.setText(String.format("%d", threadCount));
holder.threadCount.setVisibility(View.VISIBLE);
} else {
holder.threadCount.setVisibility(View.GONE);
}
}
private String getPreview(Cursor cursor) {
String previewTypeString = cursor.getString(PREVIEW_TYPE_COLUMN);
DatabasePreviewType previewType = DatabasePreviewType.fromDatabaseValue(previewTypeString);
switch (previewType) {
case NONE:
case ERROR: {
return "";
}
case ENCRYPTED: {
return fragment.getString(R.string.preview_encrypted);
}
case TEXT: {
return cursor.getString(PREVIEW_COLUMN);
}
}
throw new AssertionError("Unknown preview type: " + previewType);
}
}

View file

@ -0,0 +1,421 @@
package com.fsck.k9.fragment
import android.content.Context
import android.content.res.Resources
import android.database.Cursor
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.format.DateUtils
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CursorAdapter
import android.widget.TextView
import com.fsck.k9.Account
import com.fsck.k9.K9
import com.fsck.k9.ui.R
import com.fsck.k9.Preferences
import com.fsck.k9.contacts.ContactPictureLoader
import com.fsck.k9.controller.MessageReference
import com.fsck.k9.helper.MessageHelper
import com.fsck.k9.mail.Address
import com.fsck.k9.mailstore.DatabasePreviewType
import com.fsck.k9.ui.ContactBadge
import com.fsck.k9.fragment.MLFProjectionInfo.ACCOUNT_UUID_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.ANSWERED_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.ATTACHMENT_COUNT_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.CC_LIST_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.DATE_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.FLAGGED_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.FOLDER_SERVER_ID_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.FORWARDED_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.PREVIEW_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.PREVIEW_TYPE_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.READ_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.SENDER_LIST_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.SUBJECT_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.THREAD_COUNT_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.TO_LIST_COLUMN
import com.fsck.k9.fragment.MLFProjectionInfo.UID_COLUMN
import kotlin.math.max
class MessageListAdapter internal constructor(
context: Context,
theme: Resources.Theme,
private val res: Resources,
private val layoutInflater: LayoutInflater,
private val messageHelper: MessageHelper,
private val contactsPictureLoader: ContactPictureLoader,
private val preferences: Preferences,
private val listItemListener: MessageListItemActionListener,
private val showingThreadedList: Boolean = false
) : CursorAdapter(context, null, 0) {
private val forwardedIcon: Drawable
private val answeredIcon: Drawable
private val forwardedAnsweredIcon: Drawable
private val previewTextColor: Int
private val activeItemBackgroundColor: Int
private val selectedItemBackgroundColor: Int
private val readItemBackgroundColor: Int
private val unreadItemBackgroundColor: Int
private val fontSizes = K9.fontSizes
init {
val attributes = intArrayOf(
R.attr.messageListAnswered,
R.attr.messageListForwarded,
R.attr.messageListAnsweredForwarded,
R.attr.messageListPreviewTextColor,
R.attr.messageListActiveItemBackgroundColor,
R.attr.messageListSelectedBackgroundColor,
R.attr.messageListReadItemBackgroundColor,
R.attr.messageListUnreadItemBackgroundColor
)
val array = theme.obtainStyledAttributes(attributes)
answeredIcon = res.getDrawable(array.getResourceId(0, R.drawable.ic_messagelist_answered_dark))
forwardedIcon = res.getDrawable(array.getResourceId(1, R.drawable.ic_messagelist_forwarded_dark))
forwardedAnsweredIcon = res.getDrawable(array.getResourceId(2, R.drawable.ic_messagelist_answered_forwarded_dark))
previewTextColor = array.getColor(3, Color.BLACK)
activeItemBackgroundColor = array.getColor(4, Color.BLACK)
selectedItemBackgroundColor = array.getColor(5, Color.BLACK)
readItemBackgroundColor = array.getColor(6, Color.BLACK)
unreadItemBackgroundColor = array.getColor(7, Color.BLACK)
array.recycle()
}
private inline val previewLines: Int
get() = K9.messageListPreviewLines
private val senderAboveSubject: Boolean
get() = K9.isMessageListSenderAboveSubject
private val checkboxes: Boolean
get() = K9.isShowMessageListCheckboxes
private val stars: Boolean
get() = K9.isShowMessageListStars
private val showContactPicture: Boolean
get() = K9.isShowContactPicture
var activeMessage: MessageReference? = null
private val activeAccountUuid: String?
get() = activeMessage?.accountUuid
private val activeFolderServerId: String?
get() = activeMessage?.folderServerId
private val activeUid: String?
get() = activeMessage?.uid
var uniqueIdColumn: Int = 0
var selected: Set<Long> = emptySet()
private fun recipientSigil(toMe: Boolean, ccMe: Boolean): String {
return if (toMe) {
res.getString(R.string.messagelist_sent_to_me_sigil)
} else if (ccMe) {
res.getString(R.string.messagelist_sent_cc_me_sigil)
} else {
""
}
}
override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
val view = layoutInflater.inflate(R.layout.message_list_item, parent, false)
val holder = MessageViewHolder(listItemListener)
holder.date = view.findViewById(R.id.date)
holder.chip = view.findViewById(R.id.chip)
holder.attachment = view.findViewById(R.id.attachment)
holder.status = view.findViewById(R.id.status)
holder.preview = view.findViewById(R.id.preview)
holder.flagged = view.findViewById(R.id.star)
val contactBadge = view.findViewById<ContactBadge>(R.id.contact_badge)
if (showContactPicture) {
holder.contactBadge = contactBadge
} else {
contactBadge.visibility = View.GONE
}
if (senderAboveSubject) {
holder.from = view.findViewById(R.id.subject)
fontSizes.setViewTextSize(holder.from, fontSizes.messageListSender)
} else {
holder.subject = view.findViewById(R.id.subject)
fontSizes.setViewTextSize(holder.subject, fontSizes.messageListSubject)
}
fontSizes.setViewTextSize(holder.date, fontSizes.messageListDate)
// 1 preview line is needed even if it is set to 0, because subject is part of the same text view
holder.preview.setLines(max(previewLines, 1))
fontSizes.setViewTextSize(holder.preview, fontSizes.messageListPreview)
holder.threadCount = view.findViewById(R.id.thread_count)
fontSizes.setViewTextSize(holder.threadCount, fontSizes.messageListSubject) // thread count is next to subject
view.findViewById<View>(R.id.selected_checkbox_wrapper).visibility = if (checkboxes) View.VISIBLE else View.GONE
holder.flagged.visibility = if (stars) View.VISIBLE else View.GONE
holder.flagged.setOnClickListener(holder)
holder.selected = view.findViewById(R.id.selected_checkbox)
holder.selected.setOnClickListener(holder)
view.tag = holder
return view
}
override fun bindView(view: View, context: Context, cursor: Cursor) {
val account = getAccount(cursor)
val fromList = cursor.getString(SENDER_LIST_COLUMN)
val toList = cursor.getString(TO_LIST_COLUMN)
val ccList = cursor.getString(CC_LIST_COLUMN)
val fromAddrs = Address.unpack(fromList)
val toAddrs = Address.unpack(toList)
val ccAddrs = Address.unpack(ccList)
val fromMe = messageHelper.toMe(account, fromAddrs)
val toMe = messageHelper.toMe(account, toAddrs)
val ccMe = messageHelper.toMe(account, ccAddrs)
val displayName = messageHelper.getDisplayName(account, fromAddrs, toAddrs)
val displayDate = DateUtils.getRelativeTimeSpanString(context, cursor.getLong(DATE_COLUMN))
val counterpartyAddress = fetchCounterPartyAddress(fromMe, toAddrs, ccAddrs, fromAddrs)
val threadCount = if (showingThreadedList) cursor.getInt(THREAD_COUNT_COLUMN) else 0
val subject = MlfUtils.buildSubject(cursor.getString(SUBJECT_COLUMN),
res.getString(R.string.general_no_subject), threadCount)
val read = cursor.getInt(READ_COLUMN) == 1
val flagged = cursor.getInt(FLAGGED_COLUMN) == 1
val answered = cursor.getInt(ANSWERED_COLUMN) == 1
val forwarded = cursor.getInt(FORWARDED_COLUMN) == 1
val hasAttachments = cursor.getInt(ATTACHMENT_COUNT_COLUMN) > 0
val holder = view.tag as MessageViewHolder
val maybeBoldTypeface = if (read) Typeface.NORMAL else Typeface.BOLD
val uniqueId = cursor.getLong(uniqueIdColumn)
val selected = selected.contains(uniqueId)
holder.chip.setBackgroundColor(account.chipColor)
if (checkboxes) {
holder.selected.isChecked = selected
}
if (stars) {
holder.flagged.isChecked = flagged
}
holder.position = cursor.position
if (holder.contactBadge != null) {
updateContactBadge(holder, counterpartyAddress)
}
setBackgroundColor(view, selected, read)
if (activeMessage != null) {
changeBackgroundColorIfActiveMessage(cursor, account, view)
}
updateWithThreadCount(holder, threadCount)
val beforePreviewText = if (senderAboveSubject) subject else displayName
val sigil = recipientSigil(toMe, ccMe)
val messageStringBuilder = SpannableStringBuilder(sigil)
.append(beforePreviewText)
if (previewLines > 0) {
val preview = getPreview(cursor)
messageStringBuilder.append(" ").append(preview)
}
holder.preview.setText(messageStringBuilder, TextView.BufferType.SPANNABLE)
formatPreviewText(holder.preview, beforePreviewText, sigil)
if (holder.from != null) {
holder.from.typeface = Typeface.create(holder.from.typeface, maybeBoldTypeface)
if (senderAboveSubject) {
holder.from.text = displayName
} else {
holder.from.text = SpannableStringBuilder(sigil).append(displayName)
}
}
if (holder.subject != null) {
holder.subject.typeface = Typeface.create(holder.subject.typeface, maybeBoldTypeface)
holder.subject.text = subject
}
holder.date.text = displayDate
holder.attachment.visibility = if (hasAttachments) View.VISIBLE else View.GONE
val statusHolder = buildStatusHolder(forwarded, answered)
if (statusHolder != null) {
holder.status.setImageDrawable(statusHolder)
holder.status.visibility = View.VISIBLE
} else {
holder.status.visibility = View.GONE
}
}
private fun getAccount(cursor: Cursor): Account {
val accountUuid = cursor.getString(ACCOUNT_UUID_COLUMN)
return preferences.getAccount(accountUuid)
}
private fun formatPreviewText(
preview: TextView,
beforePreviewText: CharSequence,
sigil: String
) {
val previewText = preview.text as Spannable
previewText.setSpan(buildSenderSpan(), 0, beforePreviewText.length + sigil.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
// Set span (color) for preview message
previewText.setSpan(
ForegroundColorSpan(previewTextColor),
beforePreviewText.length + sigil.length,
previewText.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
/**
* Create a span section for the sender, and assign the correct font size and weight
*/
private fun buildSenderSpan(): AbsoluteSizeSpan {
val fontSize = if (senderAboveSubject)
fontSizes.messageListSubject
else
fontSizes.messageListSender
return AbsoluteSizeSpan(fontSize, true)
}
private fun fetchCounterPartyAddress(
fromMe: Boolean,
toAddrs: Array<Address>,
ccAddrs: Array<Address>,
fromAddrs: Array<Address>
): Address? {
if (fromMe) {
if (toAddrs.size > 0) {
return toAddrs[0]
} else if (ccAddrs.size > 0) {
return ccAddrs[0]
}
} else if (fromAddrs.size > 0) {
return fromAddrs[0]
}
return null
}
private fun updateContactBadge(holder: MessageViewHolder, counterpartyAddress: Address?) {
if (counterpartyAddress != null) {
holder.contactBadge.setContact(counterpartyAddress)
/*
* At least in Android 2.2 a different background + padding is used when no
* email address is available. ListView reuses the views but ContactBadge
* doesn't reset the padding, so we do it ourselves.
*/
holder.contactBadge.setPadding(0, 0, 0, 0)
contactsPictureLoader.setContactPicture(holder.contactBadge, counterpartyAddress)
} else {
holder.contactBadge.assignContactUri(null)
holder.contactBadge.setImageResource(R.drawable.ic_contact_picture)
}
}
private fun changeBackgroundColorIfActiveMessage(cursor: Cursor, account: Account, view: View) {
val uid = cursor.getString(UID_COLUMN)
val folderServerId = cursor.getString(FOLDER_SERVER_ID_COLUMN)
if (account.uuid == activeAccountUuid &&
folderServerId == activeFolderServerId &&
uid == activeUid) {
view.setBackgroundColor(activeItemBackgroundColor)
}
}
private fun buildStatusHolder(forwarded: Boolean, answered: Boolean): Drawable? {
if (forwarded && answered) {
return forwardedAnsweredIcon
} else if (answered) {
return answeredIcon
} else if (forwarded) {
return forwardedIcon
}
return null
}
private fun setBackgroundColor(view: View, selected: Boolean, read: Boolean) {
if (selected || K9.isUseBackgroundAsUnreadIndicator) {
val color: Int
if (selected) {
color = selectedItemBackgroundColor
} else if (read) {
color = readItemBackgroundColor
} else {
color = unreadItemBackgroundColor
}
view.setBackgroundColor(color)
} else {
view.setBackgroundColor(Color.TRANSPARENT)
}
}
private fun updateWithThreadCount(holder: MessageViewHolder, threadCount: Int) {
if (threadCount > 1) {
holder.threadCount.setText(String.format("%d", threadCount))
holder.threadCount.visibility = View.VISIBLE
} else {
holder.threadCount.visibility = View.GONE
}
}
private fun getPreview(cursor: Cursor): String {
val previewTypeString = cursor.getString(PREVIEW_TYPE_COLUMN)
val previewType = DatabasePreviewType.fromDatabaseValue(previewTypeString)
when (previewType) {
DatabasePreviewType.NONE, DatabasePreviewType.ERROR -> {
return ""
}
DatabasePreviewType.ENCRYPTED -> {
return res.getString(R.string.preview_encrypted)
}
DatabasePreviewType.TEXT -> {
return cursor.getString(PREVIEW_COLUMN)
}
}
throw AssertionError("Unknown preview type: $previewType")
}
}
interface MessageListItemActionListener {
fun toggleMessageSelectWithAdapterPosition(position: Int)
fun toggleMessageFlagWithAdapterPosition(position: Int)
}

View file

@ -60,11 +60,9 @@ import com.fsck.k9.activity.ActivityListener;
import com.fsck.k9.activity.ChooseFolder;
import com.fsck.k9.activity.FolderInfoHolder;
import com.fsck.k9.activity.misc.ContactPicture;
import com.fsck.k9.contacts.ContactPictureLoader;
import com.fsck.k9.cache.EmailProviderCache;
import com.fsck.k9.controller.MessageReference;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.core.BuildConfig;
import com.fsck.k9.ui.R;
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
import com.fsck.k9.fragment.MessageListFragmentComparators.ArrivalComparator;
@ -110,7 +108,7 @@ import static com.fsck.k9.fragment.MLFProjectionInfo.UID_COLUMN;
public class MessageListFragment extends Fragment implements OnItemClickListener,
ConfirmationDialogFragmentListener, LoaderCallbacks<Cursor> {
ConfirmationDialogFragmentListener, LoaderCallbacks<Cursor>, MessageListItemActionListener {
public static MessageListFragment newInstance(
LocalSearch search, boolean isThreadDisplay, boolean threadedList) {
@ -163,9 +161,6 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
private SwipeRefreshLayout swipeRefreshLayout;
Parcelable savedListState;
int previewLines = 0;
private MessageListAdapter adapter;
private View footerView;
private FolderInfoHolder currentFolder;
@ -177,7 +172,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
private Cursor[] cursors;
private boolean[] cursorValid;
int uniqueIdColumn;
private int uniqueIdColumn;
/**
* Stores the server ID of the folder that we want to open as soon as possible after load.
@ -199,12 +194,9 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
private SortType sortType = SortType.SORT_DATE;
private boolean sortAscending = true;
private boolean sortDateAscending = false;
boolean senderAboveSubject = false;
boolean checkboxes = true;
boolean stars = true;
private int selectedCount = 0;
Set<Long> selected = new HashSet<>();
private Set<Long> selected = new HashSet<>();
private ActionMode actionMode;
private Boolean hasConnectivity;
/**
@ -212,23 +204,20 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* between user interactions (e.g. selecting a folder for move operation).
*/
private List<MessageReference> activeMessages;
/* package visibility for faster inner class access */
MessageHelper messageHelper;
private final ActionModeCallback actionModeCallback = new ActionModeCallback();
MessageListFragmentListener fragmentListener;
boolean showingThreadedList;
private boolean showingThreadedList;
private boolean isThreadDisplay;
private Context context;
private final ActivityListener activityListener = new MessageListActivityListener();
private Preferences preferences;
private boolean loaderJustInitialized;
MessageReference activeMessage;
private MessageReference activeMessage;
/**
* {@code true} after {@link #onCreate(Bundle)} was executed. Used in {@link #updateTitle()} to
* make sure we don't access member variables before initialization is complete.
*/
private boolean initialized = false;
ContactPictureLoader contactsPictureLoader;
private LocalBroadcastManager localBroadcastManager;
private BroadcastReceiver cacheBroadcastReceiver;
private IntentFilter cacheIntentFilter;
@ -413,14 +402,6 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
preferences = Preferences.getPreferences(appContext);
messagingController = MessagingController.getInstance(getActivity().getApplication());
previewLines = K9.getMessageListPreviewLines();
checkboxes = K9.isShowMessageListCheckboxes();
stars = K9.isShowMessageListStars();
if (K9.isShowContactPicture()) {
contactsPictureLoader = ContactPicture.getContactPictureLoader();
}
restoreInstanceState(savedInstanceState);
decodeArguments();
@ -455,8 +436,6 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
messageHelper = MessageHelper.getInstance(getActivity());
initializeMessageList();
// This needs to be done before initializing the cursor loader below
@ -502,6 +481,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
savedListState = savedInstanceState.getParcelable(STATE_MESSAGE_LIST);
String messageReferenceString = savedInstanceState.getString(STATE_ACTIVE_MESSAGE);
activeMessage = MessageReference.parse(messageReferenceString);
if (adapter != null) adapter.setActiveMessage(activeMessage);
}
/**
@ -598,7 +578,17 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
private void initializeMessageList() {
adapter = new MessageListAdapter(this);
adapter = new MessageListAdapter(
requireContext(),
requireActivity().getTheme(),
getResources(),
layoutInflater,
MessageHelper.getInstance(getActivity()),
ContactPicture.getContactPictureLoader(),
preferences,
this,
showingThreadedList
);
if (folderServerId != null) {
currentFolder = getFolderInfoHolder(folderServerId, account);
@ -652,8 +642,6 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
public void onResume() {
super.onResume();
senderAboveSubject = K9.isMessageListSenderAboveSubject();
if (!loaderJustInitialized) {
restartLoader();
} else {
@ -1453,14 +1441,16 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
toggleMessageSelectWithAdapterPosition(adapterPosition);
}
void toggleMessageFlagWithAdapterPosition(int adapterPosition) {
@Override
public void toggleMessageFlagWithAdapterPosition(int adapterPosition) {
Cursor cursor = (Cursor) adapter.getItem(adapterPosition);
boolean flagged = (cursor.getInt(FLAGGED_COLUMN) == 1);
setFlag(adapterPosition,Flag.FLAGGED, !flagged);
}
void toggleMessageSelectWithAdapterPosition(int adapterPosition) {
@Override
public void toggleMessageSelectWithAdapterPosition(int adapterPosition) {
Cursor cursor = (Cursor) adapter.getItem(adapterPosition);
long uniqueId = cursor.getLong(uniqueIdColumn);
@ -2105,6 +2095,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
} else if (dialogId == R.id.dialog_confirm_delete) {
onDeleteConfirmed(activeMessages);
activeMessage = null;
if (adapter != null) adapter.setActiveMessage(null);
} else if (dialogId == R.id.dialog_confirm_mark_all_as_read) {
markAllAsRead();
} else if (dialogId == R.id.dialog_confirm_empty_trash) {
@ -2589,6 +2580,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
cursor = data;
uniqueIdColumn = ID_COLUMN;
}
adapter.setUniqueIdColumn(uniqueIdColumn);
if (isThreadDisplay) {
if (cursor.moveToFirst()) {
@ -2606,6 +2598,8 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
cleanupSelected(cursor);
adapter.setSelected(selected);
updateContextMenu(cursor);
adapter.swapCursor(cursor);
@ -2745,7 +2739,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
adapter.swapCursor(null);
}
Account getAccountFromCursor(Cursor cursor) {
private Account getAccountFromCursor(Cursor cursor) {
String accountUuid = cursor.getString(ACCOUNT_UUID_COLUMN);
return preferences.getAccount(accountUuid);
}
@ -2775,6 +2769,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
// Redraw list immediately
if (adapter != null) {
adapter.setActiveMessage(activeMessage);
adapter.notifyDataSetChanged();
}
}
@ -2822,10 +2817,6 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
return (isRemoteSearchAllowed() || isCheckMailAllowed());
}
LayoutInflater getK9LayoutInflater() {
return layoutInflater;
}
public LocalSearch getLocalSearch() {
return search;
}

View file

@ -5,13 +5,12 @@ import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import com.fsck.k9.ui.R;
import com.fsck.k9.ui.ContactBadge;
import com.fsck.k9.ui.R;
public class MessageViewHolder implements View.OnClickListener {
private final MessageListFragment fragment;
private final MessageListItemActionListener itemActionListener;
public TextView subject;
public TextView preview;
public TextView from;
@ -26,8 +25,8 @@ public class MessageViewHolder implements View.OnClickListener {
public ImageView attachment;
public ImageView status;
public MessageViewHolder(MessageListFragment fragment) {
this.fragment = fragment;
public MessageViewHolder(final MessageListItemActionListener itemActionListener) {
this.itemActionListener = itemActionListener;
}
@Override
@ -35,9 +34,9 @@ public class MessageViewHolder implements View.OnClickListener {
if (position != -1) {
int id = view.getId();
if (id == R.id.selected_checkbox) {
fragment.toggleMessageSelectWithAdapterPosition(position);
itemActionListener.toggleMessageSelectWithAdapterPosition(position);
} else if (id == R.id.star) {
fragment.toggleMessageFlagWithAdapterPosition(position);
itemActionListener.toggleMessageFlagWithAdapterPosition(position);
}
}
}