package com.fsck.k9.fragment;
import android.content.Context;
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.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
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.R;
import com.fsck.k9.helper.Utility;
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_NAME_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 mAttachmentIcon;
private Drawable mForwardedIcon;
private Drawable mAnsweredIcon;
private Drawable mForwardedAnsweredIcon;
private FontSizes fontSizes = K9.getFontSizes();
MessageListAdapter(MessageListFragment fragment) {
super(fragment.getActivity(), null, 0);
this.fragment = fragment;
mAttachmentIcon = fragment.getResources().getDrawable(R.drawable.ic_email_attachment_small);
mAnsweredIcon = fragment.getResources().getDrawable(R.drawable.ic_email_answered_small);
mForwardedIcon = fragment.getResources().getDrawable(R.drawable.ic_email_forwarded_small);
mForwardedAnsweredIcon = fragment.getResources().getDrawable(R.drawable.ic_email_forwarded_answered_small);
}
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.getLayoutInflater().inflate(R.layout.message_list_item, parent, false);
MessageViewHolder holder = new MessageViewHolder(fragment);
holder.date = (TextView) view.findViewById(R.id.date);
holder.chip = view.findViewById(R.id.chip);
if (fragment.previewLines == 0 && fragment.contactsPictureLoader == null) {
view.findViewById(R.id.preview).setVisibility(View.GONE);
holder.preview = (TextView) view.findViewById(R.id.sender_compact);
holder.flagged = (CheckBox) view.findViewById(R.id.flagged_center_right);
view.findViewById(R.id.flagged_bottom_right).setVisibility(View.GONE);
} else {
view.findViewById(R.id.sender_compact).setVisibility(View.GONE);
holder.preview = (TextView) view.findViewById(R.id.preview);
holder.flagged = (CheckBox) view.findViewById(R.id.flagged_bottom_right);
view.findViewById(R.id.flagged_center_right).setVisibility(View.GONE);
}
ContactBadge 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 = (TextView) view.findViewById(R.id.subject);
fontSizes.setViewTextSize(holder.from, fontSizes.getMessageListSender());
} else {
holder.subject = (TextView) 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 = (TextView) 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 = (CheckBox) 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);
Drawable statusHolder = buildStatusHolder(forwarded, answered);
if (holder.from != null ) {
holder.from.setTypeface(Typeface.create(holder.from.getTypeface(), maybeBoldTypeface));
if (fragment.senderAboveSubject) {
holder.from.setCompoundDrawablesWithIntrinsicBounds(
statusHolder, // left
null, // top
hasAttachments ? mAttachmentIcon : null, // right
null); // bottom
holder.from.setText(displayName);
} else {
holder.from.setText(new SpannableStringBuilder(sigil).append(displayName));
}
}
if (holder.subject != null ) {
if (!fragment.senderAboveSubject) {
holder.subject.setCompoundDrawablesWithIntrinsicBounds(
statusHolder, // left
null, // top
hasAttachments ? mAttachmentIcon : null, // right
null); // bottom
}
holder.subject.setTypeface(Typeface.create(holder.subject.getTypeface(), maybeBoldTypeface));
holder.subject.setText(subject);
}
holder.date.setText(displayDate);
}
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);
int previewSpanColor = buildPreviewSpanColor();
// Set span (color) for preview message
previewText.setSpan(new ForegroundColorSpan(previewSpanColor), 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) {
Utility.setContactForBadge(holder.contactBadge, 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.loadContactPicture(counterpartyAddress, holder.contactBadge);
} 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 folderName = cursor.getString(FOLDER_NAME_COLUMN);
if (account.getUuid().equals(fragment.activeMessage.getAccountUuid()) &&
folderName.equals(fragment.activeMessage.getFolderName()) &&
uid.equals(fragment.activeMessage.getUid())) {
int res = R.attr.messageListActiveItemBackgroundColor;
TypedValue outValue = new TypedValue();
fragment.getActivity().getTheme().resolveAttribute(res, outValue, true);
view.setBackgroundColor(outValue.data);
}
}
private int buildPreviewSpanColor() {
//TODO: make this part of the theme
return (K9.getK9Theme() == K9.Theme.LIGHT) ?
Color.rgb(105, 105, 105) :
Color.rgb(160, 160, 160);
}
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.useBackgroundAsUnreadIndicator()) {
int res;
if (selected) {
res = R.attr.messageListSelectedBackgroundColor;
} else if (read) {
res = R.attr.messageListReadItemBackgroundColor;
} else {
res = R.attr.messageListUnreadItemBackgroundColor;
}
TypedValue outValue = new TypedValue();
fragment.getActivity().getTheme().resolveAttribute(res, outValue, true);
view.setBackgroundColor(outValue.data);
} 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);
}
}