Extract code to read from Cursor to MessageListExtractor

This commit is contained in:
cketti 2019-10-09 01:25:09 +02:00
parent aab4ca78fc
commit d4df9274bb
8 changed files with 225 additions and 177 deletions

View file

@ -4,4 +4,5 @@ import org.koin.dsl.module
val helperModule = module {
single { ClipboardManager(get()) }
single { MessageHelper.getInstance(get()) }
}

View file

@ -34,26 +34,26 @@ public final class MLFProjectionInfo {
SpecialColumns.THREAD_COUNT,
};
static final int ID_COLUMN = 0;
static final int UID_COLUMN = 1;
static final int INTERNAL_DATE_COLUMN = 2;
static final int SUBJECT_COLUMN = 3;
static final int DATE_COLUMN = 4;
static final int SENDER_LIST_COLUMN = 5;
static final int TO_LIST_COLUMN = 6;
static final int CC_LIST_COLUMN = 7;
static final int READ_COLUMN = 8;
static final int FLAGGED_COLUMN = 9;
static final int ANSWERED_COLUMN = 10;
static final int FORWARDED_COLUMN = 11;
static final int ATTACHMENT_COUNT_COLUMN = 12;
static final int FOLDER_ID_COLUMN = 13;
static final int PREVIEW_TYPE_COLUMN = 14;
static final int PREVIEW_COLUMN = 15;
static final int THREAD_ROOT_COLUMN = 16;
static final int ACCOUNT_UUID_COLUMN = 17;
static final int FOLDER_SERVER_ID_COLUMN = 18;
static final int THREAD_COUNT_COLUMN = 19;
public static final int ID_COLUMN = 0;
public static final int UID_COLUMN = 1;
public static final int INTERNAL_DATE_COLUMN = 2;
public static final int SUBJECT_COLUMN = 3;
public static final int DATE_COLUMN = 4;
public static final int SENDER_LIST_COLUMN = 5;
public static final int TO_LIST_COLUMN = 6;
public static final int CC_LIST_COLUMN = 7;
public static final int READ_COLUMN = 8;
public static final int FLAGGED_COLUMN = 9;
public static final int ANSWERED_COLUMN = 10;
public static final int FORWARDED_COLUMN = 11;
public static final int ATTACHMENT_COUNT_COLUMN = 12;
public static final int FOLDER_ID_COLUMN = 13;
public static final int PREVIEW_TYPE_COLUMN = 14;
public static final int PREVIEW_COLUMN = 15;
public static final int THREAD_ROOT_COLUMN = 16;
public static final int ACCOUNT_UUID_COLUMN = 17;
public static final int FOLDER_SERVER_ID_COLUMN = 18;
public static final int THREAD_COUNT_COLUMN = 19;
static final String[] PROJECTION = Arrays.copyOf(THREADED_PROJECTION,
THREAD_COUNT_COLUMN);

View file

@ -19,33 +19,15 @@ import android.widget.CursorAdapter
import android.widget.TextView
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.isVisible
import com.fsck.k9.Account
import com.fsck.k9.FontSizes
import com.fsck.k9.Preferences
import com.fsck.k9.contacts.ContactPictureLoader
import com.fsck.k9.controller.MessageReference
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 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.ui.R
import com.fsck.k9.ui.messagelist.MessageListAppearance
import com.fsck.k9.ui.messagelist.MessageListExtractor
import com.fsck.k9.ui.messagelist.MessageListItem
import kotlin.math.max
class MessageListAdapter internal constructor(
@ -53,9 +35,8 @@ class MessageListAdapter internal constructor(
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 messageListExtractor: MessageListExtractor,
private val listItemListener: MessageListItemActionListener,
private val appearance: MessageListAppearance
) : CursorAdapter(context, null, 0) {
@ -98,15 +79,6 @@ class MessageListAdapter internal constructor(
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()
@ -154,103 +126,63 @@ class MessageListAdapter internal constructor(
}
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 counterPartyAddress = fetchCounterPartyAddress(fromMe, toAddrs, ccAddrs, fromAddrs)
val displayName = messageHelper.getDisplayName(account, fromAddrs, toAddrs)
val messageDate = cursor.getLong(DATE_COLUMN)
val threadCount = cursor.getInt(THREAD_COUNT_COLUMN)
val subjectText = cursor.getString(SUBJECT_COLUMN)
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 uniqueId = cursor.getLong(uniqueIdColumn)
val selected = selected.contains(uniqueId)
val uid = cursor.getString(UID_COLUMN)
val folderServerId = cursor.getString(FOLDER_SERVER_ID_COLUMN)
val active = account.uuid == activeAccountUuid && folderServerId == activeFolderServerId && uid == activeUid
val previewTypeString = cursor.getString(PREVIEW_TYPE_COLUMN)
val previewType = DatabasePreviewType.fromDatabaseValue(previewTypeString)
val isMessageEncrypted = previewType == DatabasePreviewType.ENCRYPTED
val previewText = if (previewType == DatabasePreviewType.TEXT) cursor.getString(PREVIEW_COLUMN) else ""
val position = cursor.position
val item = messageListExtractor.extractMessageListItem(cursor, uniqueIdColumn)
val isSelected = selected.contains(item.uniqueId)
val isActive = isActiveMessage(item)
val holder = view.tag as MessageViewHolder
val maybeBoldTypeface = if (read) Typeface.NORMAL else Typeface.BOLD
val displayDate = DateUtils.getRelativeTimeSpanString(context, messageDate)
val displayThreadCount = if (appearance.showingThreadedList) threadCount else 0
val subject = MlfUtils.buildSubject(subjectText, res.getString(R.string.general_no_subject), displayThreadCount)
with(item) {
val maybeBoldTypeface = if (isRead) Typeface.NORMAL else Typeface.BOLD
val displayDate = DateUtils.getRelativeTimeSpanString(context, messageDate)
val displayThreadCount = if (appearance.showingThreadedList) threadCount else 0
val subject = MlfUtils.buildSubject(subject, res.getString(R.string.general_no_subject), displayThreadCount)
if (appearance.showAccountChip) {
val accountChipDrawable = holder.chip.drawable.mutate()
DrawableCompat.setTint(accountChipDrawable, account.chipColor)
holder.chip.setImageDrawable(accountChipDrawable)
if (appearance.showAccountChip) {
val accountChipDrawable = holder.chip.drawable.mutate()
DrawableCompat.setTint(accountChipDrawable, account.chipColor)
holder.chip.setImageDrawable(accountChipDrawable)
}
if (appearance.stars) {
holder.flagged.isChecked = isStarred
}
holder.position = position
if (holder.contactBadge.isVisible) {
updateContactBadge(holder.contactBadge, counterPartyAddress)
}
setBackgroundColor(view, isSelected, isRead, isActive)
updateWithThreadCount(holder, displayThreadCount)
val beforePreviewText = if (appearance.senderAboveSubject) subject else displayName
val sigil = recipientSigil(toMe, ccMe)
val messageStringBuilder = SpannableStringBuilder(sigil)
.append(beforePreviewText)
if (appearance.previewLines > 0) {
val preview = getPreview(isMessageEncrypted, previewText)
messageStringBuilder.append(" ").append(preview)
}
holder.preview.setText(messageStringBuilder, TextView.BufferType.SPANNABLE)
formatPreviewText(holder.preview, beforePreviewText, sigil, isRead)
holder.subject.typeface = Typeface.create(holder.subject.typeface, maybeBoldTypeface)
if (appearance.senderAboveSubject) {
holder.subject.text = displayName
} else {
holder.subject.text = subject
}
holder.date.text = displayDate
holder.attachment.visibility = if (hasAttachments) View.VISIBLE else View.GONE
val statusHolder = buildStatusHolder(isForwarded, isAnswered)
if (statusHolder != null) {
holder.status.setImageDrawable(statusHolder)
holder.status.visibility = View.VISIBLE
} else {
holder.status.visibility = View.GONE
}
}
if (appearance.stars) {
holder.flagged.isChecked = flagged
}
holder.position = position
if (holder.contactBadge.isVisible) {
updateContactBadge(holder.contactBadge, counterPartyAddress)
}
setBackgroundColor(view, selected, read, active)
updateWithThreadCount(holder, displayThreadCount)
val beforePreviewText = if (appearance.senderAboveSubject) subject else displayName
val sigil = recipientSigil(toMe, ccMe)
val messageStringBuilder = SpannableStringBuilder(sigil)
.append(beforePreviewText)
if (appearance.previewLines > 0) {
val preview = getPreview(isMessageEncrypted, previewText)
messageStringBuilder.append(" ").append(preview)
}
holder.preview.setText(messageStringBuilder, TextView.BufferType.SPANNABLE)
formatPreviewText(holder.preview, beforePreviewText, sigil, read)
holder.subject.typeface = Typeface.create(holder.subject.typeface, maybeBoldTypeface)
if (appearance.senderAboveSubject) {
holder.subject.text = displayName
} else {
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(
@ -291,24 +223,6 @@ class MessageListAdapter internal constructor(
}
}
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(contactBadge: ContactBadge, counterpartyAddress: Address?) {
if (counterpartyAddress != null) {
contactBadge.setContact(counterpartyAddress)
@ -365,6 +279,14 @@ class MessageListAdapter internal constructor(
previewText
}
}
private fun isActiveMessage(item: MessageListItem): Boolean {
val activeMessage = this.activeMessage ?: return false
return item.account.uuid == activeMessage.accountUuid &&
item.folderServerId == activeMessage.folderServerId &&
item.messageUid == activeMessage.uid
}
}
interface MessageListItemActionListener {

View file

@ -23,16 +23,6 @@ import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import android.text.TextUtils;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
@ -51,6 +41,16 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.fsck.k9.Account;
import com.fsck.k9.Account.SortType;
import com.fsck.k9.DI;
@ -63,7 +63,6 @@ import com.fsck.k9.activity.misc.ContactPicture;
import com.fsck.k9.cache.EmailProviderCache;
import com.fsck.k9.controller.MessageReference;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.ui.R;
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
import com.fsck.k9.fragment.MessageListFragmentComparators.ArrivalComparator;
import com.fsck.k9.fragment.MessageListFragmentComparators.AttachmentComparator;
@ -76,7 +75,6 @@ import com.fsck.k9.fragment.MessageListFragmentComparators.SenderComparator;
import com.fsck.k9.fragment.MessageListFragmentComparators.SubjectComparator;
import com.fsck.k9.fragment.MessageListFragmentComparators.UnreadComparator;
import com.fsck.k9.helper.MergeCursorWithUniqueId;
import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
@ -92,7 +90,9 @@ import com.fsck.k9.search.SearchSpecification;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.fsck.k9.search.SearchSpecification.SearchField;
import com.fsck.k9.search.SqlQueryBuilder;
import com.fsck.k9.ui.R;
import com.fsck.k9.ui.messagelist.MessageListAppearance;
import com.fsck.k9.ui.messagelist.MessageListExtractor;
import timber.log.Timber;
import static com.fsck.k9.Account.Expunge.EXPUNGE_MANUALLY;
@ -158,6 +158,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
private final SortTypeToastProvider sortTypeToastProvider = DI.get(SortTypeToastProvider.class);
private final MessageListExtractor messageListExtractor = DI.get(MessageListExtractor.class);
ListView listView;
private SwipeRefreshLayout swipeRefreshLayout;
@ -584,9 +585,8 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
requireActivity().getTheme(),
getResources(),
layoutInflater,
MessageHelper.getInstance(getActivity()),
ContactPicture.getContactPictureLoader(),
preferences,
messageListExtractor,
this,
getMessageListAppearance()
);

View file

@ -5,6 +5,7 @@ import com.fsck.k9.ui.folders.FoldersLiveDataFactory
import com.fsck.k9.ui.helper.DisplayHtmlUiFactory
import com.fsck.k9.ui.helper.HtmlSettingsProvider
import com.fsck.k9.ui.helper.HtmlToSpanned
import com.fsck.k9.ui.messagelist.MessageListExtractor
import org.koin.dsl.module
val uiModule = module {
@ -14,4 +15,5 @@ val uiModule = module {
single { HtmlSettingsProvider(get()) }
single { DisplayHtmlUiFactory(get()) }
single { FoldersLiveDataFactory(get(), get(), get()) }
single { MessageListExtractor(get(), get()) }
}

View file

@ -0,0 +1,96 @@
package com.fsck.k9.ui.messagelist
import android.database.Cursor
import com.fsck.k9.Preferences
import com.fsck.k9.fragment.MLFProjectionInfo
import com.fsck.k9.helper.MessageHelper
import com.fsck.k9.mail.Address
import com.fsck.k9.mailstore.DatabasePreviewType
class MessageListExtractor(
private val preferences: Preferences,
private val messageHelper: MessageHelper
) {
fun extractMessageListItem(cursor: Cursor, uniqueIdColumn: Int): MessageListItem {
val position = cursor.position
val accountUuid = cursor.getString(MLFProjectionInfo.ACCOUNT_UUID_COLUMN)
val account = preferences.getAccount(accountUuid)
val fromList = cursor.getString(MLFProjectionInfo.SENDER_LIST_COLUMN)
val toList = cursor.getString(MLFProjectionInfo.TO_LIST_COLUMN)
val ccList = cursor.getString(MLFProjectionInfo.CC_LIST_COLUMN)
val fromAddresses = Address.unpack(fromList)
val toAddresses = Address.unpack(toList)
val ccAddresses = Address.unpack(ccList)
val fromMe = messageHelper.toMe(account, fromAddresses)
val toMe = messageHelper.toMe(account, toAddresses)
val ccMe = messageHelper.toMe(account, ccAddresses)
val counterPartyAddress = getCounterPartyAddress(fromMe, toAddresses, ccAddresses, fromAddresses)
val displayName = messageHelper.getDisplayName(account, fromAddresses, toAddresses)
val messageDate = cursor.getLong(MLFProjectionInfo.DATE_COLUMN)
val threadCount = cursor.getIntIfColumnPresent(MLFProjectionInfo.THREAD_COUNT_COLUMN) ?: 0
val subject = cursor.getString(MLFProjectionInfo.SUBJECT_COLUMN)
val isRead = cursor.getBoolean(MLFProjectionInfo.READ_COLUMN)
val isStarred = cursor.getBoolean(MLFProjectionInfo.FLAGGED_COLUMN)
val isAnswered = cursor.getBoolean(MLFProjectionInfo.ANSWERED_COLUMN)
val isForwarded = cursor.getBoolean(MLFProjectionInfo.FORWARDED_COLUMN)
val hasAttachments = cursor.getInt(MLFProjectionInfo.ATTACHMENT_COUNT_COLUMN) > 0
val previewTypeString = cursor.getString(MLFProjectionInfo.PREVIEW_TYPE_COLUMN)
val previewType = DatabasePreviewType.fromDatabaseValue(previewTypeString)
val isMessageEncrypted = previewType == DatabasePreviewType.ENCRYPTED
val previewText = getPreviewText(previewType, cursor)
val uniqueId = cursor.getLong(uniqueIdColumn)
val folderServerId = cursor.getString(MLFProjectionInfo.FOLDER_SERVER_ID_COLUMN)
val messageUid = cursor.getString(MLFProjectionInfo.UID_COLUMN)
return MessageListItem(
position,
account,
subject,
threadCount,
messageDate,
displayName,
counterPartyAddress,
fromMe,
toMe,
ccMe,
previewText,
isMessageEncrypted,
isRead,
isStarred,
isAnswered,
isForwarded,
hasAttachments,
uniqueId,
folderServerId,
messageUid
)
}
private fun getCounterPartyAddress(
fromMe: Boolean,
toAddresses: Array<Address>,
ccAddresses: Array<Address>,
fromAddresses: Array<Address>
): Address? {
return when {
fromMe && toAddresses.isNotEmpty() -> toAddresses[0]
fromMe && ccAddresses.isNotEmpty() -> ccAddresses[0]
fromAddresses.isNotEmpty() -> fromAddresses[0]
else -> null
}
}
private fun getPreviewText(previewType: DatabasePreviewType?, cursor: Cursor): String {
return if (previewType == DatabasePreviewType.TEXT) {
cursor.getString(MLFProjectionInfo.PREVIEW_COLUMN) ?: ""
} else {
""
}
}
private fun Cursor.getBoolean(columnIndex: Int): Boolean = getInt(columnIndex) == 1
private fun Cursor.getIntIfColumnPresent(columnIndex: Int): Int? {
return if (columnCount >= columnIndex + 1) getInt(columnIndex) else null
}
}

View file

@ -0,0 +1,27 @@
package com.fsck.k9.ui.messagelist
import com.fsck.k9.Account
import com.fsck.k9.mail.Address
data class MessageListItem(
val position: Int,
val account: Account,
val subject: String,
val threadCount: Int,
val messageDate: Long,
val displayName: CharSequence,
val counterPartyAddress: Address?,
val fromMe: Boolean,
val toMe: Boolean,
val ccMe: Boolean,
val previewText: String,
val isMessageEncrypted: Boolean,
val isRead: Boolean,
val isStarred: Boolean,
val isAnswered: Boolean,
val isForwarded: Boolean,
val hasAttachments: Boolean,
val uniqueId: Long,
val folderServerId: String,
val messageUid: String
)

View file

@ -29,6 +29,7 @@ import com.fsck.k9.textString
import com.fsck.k9.ui.ContactBadge
import com.fsck.k9.ui.R
import com.fsck.k9.ui.messagelist.MessageListAppearance
import com.fsck.k9.ui.messagelist.MessageListExtractor
import com.nhaarman.mockito_kotlin.anyArray
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.eq
@ -464,9 +465,8 @@ class MessageListAdapterTest : RobolectricTest() {
theme = context.theme,
res = context.resources,
layoutInflater = LayoutInflater.from(context),
messageHelper = messageHelper,
contactsPictureLoader = contactsPictureLoader,
preferences = preferences,
messageListExtractor = MessageListExtractor(preferences, messageHelper),
listItemListener = listItemListener,
appearance = appearance
)