Skip to content

Commit

Permalink
Merge pull request #6618 from thundernest/message-view-redesign
Browse files Browse the repository at this point in the history
Add the redesigned message view screen
  • Loading branch information
cketti authored Feb 1, 2023
2 parents c93f5ea + 7779089 commit e826ed8
Show file tree
Hide file tree
Showing 127 changed files with 3,657 additions and 1,147 deletions.
1 change: 1 addition & 0 deletions app/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {
implementation libs.moshi
implementation libs.timber
implementation libs.mime4j.core
implementation libs.mime4j.dom

testImplementation project(':mail:testing')
testImplementation project(":backend:imap")
Expand Down
57 changes: 9 additions & 48 deletions app/core/src/main/java/com/fsck/k9/FontSizes.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ public class FontSizes {
private static final String MESSAGE_LIST_DATE = "fontSizeMessageListDate";
private static final String MESSAGE_LIST_PREVIEW = "fontSizeMessageListPreview";
private static final String MESSAGE_VIEW_SENDER = "fontSizeMessageViewSender";
private static final String MESSAGE_VIEW_TO = "fontSizeMessageViewTo";
private static final String MESSAGE_VIEW_CC = "fontSizeMessageViewCC";
private static final String MESSAGE_VIEW_BCC = "fontSizeMessageViewBCC";
private static final String MESSAGE_VIEW_ADDITIONAL_HEADERS = "fontSizeMessageViewAdditionalHeaders";
private static final String MESSAGE_VIEW_RECIPIENTS = "fontSizeMessageViewTo";
private static final String MESSAGE_VIEW_SUBJECT = "fontSizeMessageViewSubject";
private static final String MESSAGE_VIEW_DATE = "fontSizeMessageViewDate";
private static final String MESSAGE_VIEW_CONTENT_PERCENT = "fontSizeMessageViewContentPercent";
Expand All @@ -40,10 +37,7 @@ public class FontSizes {
private int messageListDate;
private int messageListPreview;
private int messageViewSender;
private int messageViewTo;
private int messageViewCC;
private int messageViewBCC;
private int messageViewAdditionalHeaders;
private int messageViewRecipients;
private int messageViewSubject;
private int messageViewDate;
private int messageViewContentPercent;
Expand All @@ -57,10 +51,7 @@ public FontSizes() {
messageListPreview = FONT_DEFAULT;

messageViewSender = FONT_DEFAULT;
messageViewTo = FONT_DEFAULT;
messageViewCC = FONT_DEFAULT;
messageViewBCC = FONT_DEFAULT;
messageViewAdditionalHeaders = FONT_DEFAULT;
messageViewRecipients = FONT_DEFAULT;
messageViewSubject = FONT_DEFAULT;
messageViewDate = FONT_DEFAULT;
messageViewContentPercent = 100;
Expand All @@ -75,10 +66,7 @@ public void save(StorageEditor editor) {
editor.putInt(MESSAGE_LIST_PREVIEW, messageListPreview);

editor.putInt(MESSAGE_VIEW_SENDER, messageViewSender);
editor.putInt(MESSAGE_VIEW_TO, messageViewTo);
editor.putInt(MESSAGE_VIEW_CC, messageViewCC);
editor.putInt(MESSAGE_VIEW_BCC, messageViewBCC);
editor.putInt(MESSAGE_VIEW_ADDITIONAL_HEADERS, messageViewAdditionalHeaders);
editor.putInt(MESSAGE_VIEW_RECIPIENTS, messageViewRecipients);
editor.putInt(MESSAGE_VIEW_SUBJECT, messageViewSubject);
editor.putInt(MESSAGE_VIEW_DATE, messageViewDate);
editor.putInt(MESSAGE_VIEW_CONTENT_PERCENT, getMessageViewContentAsPercent());
Expand All @@ -93,10 +81,7 @@ public void load(Storage storage) {
messageListPreview = storage.getInt(MESSAGE_LIST_PREVIEW, messageListPreview);

messageViewSender = storage.getInt(MESSAGE_VIEW_SENDER, messageViewSender);
messageViewTo = storage.getInt(MESSAGE_VIEW_TO, messageViewTo);
messageViewCC = storage.getInt(MESSAGE_VIEW_CC, messageViewCC);
messageViewBCC = storage.getInt(MESSAGE_VIEW_BCC, messageViewBCC);
messageViewAdditionalHeaders = storage.getInt(MESSAGE_VIEW_ADDITIONAL_HEADERS, messageViewAdditionalHeaders);
messageViewRecipients = storage.getInt(MESSAGE_VIEW_RECIPIENTS, messageViewRecipients);
messageViewSubject = storage.getInt(MESSAGE_VIEW_SUBJECT, messageViewSubject);
messageViewDate = storage.getInt(MESSAGE_VIEW_DATE, messageViewDate);

Expand Down Expand Up @@ -149,36 +134,12 @@ public void setMessageViewSender(int messageViewSender) {
this.messageViewSender = messageViewSender;
}

public int getMessageViewTo() {
return messageViewTo;
public int getMessageViewRecipients() {
return messageViewRecipients;
}

public void setMessageViewTo(int messageViewTo) {
this.messageViewTo = messageViewTo;
}

public int getMessageViewCC() {
return messageViewCC;
}

public void setMessageViewCC(int messageViewCC) {
this.messageViewCC = messageViewCC;
}

public int getMessageViewBCC() {
return messageViewBCC;
}

public void setMessageViewBCC(int messageViewBCC) {
this.messageViewBCC = messageViewBCC;
}

public int getMessageViewAdditionalHeaders() {
return messageViewAdditionalHeaders;
}

public void setMessageViewAdditionalHeaders(int messageViewAdditionalHeaders) {
this.messageViewAdditionalHeaders = messageViewAdditionalHeaders;
public void setMessageViewRecipients(int messageViewRecipients) {
this.messageViewRecipients = messageViewRecipients;
}

public int getMessageViewSubject() {
Expand Down
74 changes: 74 additions & 0 deletions app/core/src/main/java/com/fsck/k9/helper/AddressFormatter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.fsck.k9.helper

import android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import com.fsck.k9.Account
import com.fsck.k9.Identity
import com.fsck.k9.mail.Address

/**
* Get the display name for an email address.
*/
interface AddressFormatter {
fun getDisplayName(address: Address): CharSequence
}

class RealAddressFormatter(
private val contactNameProvider: ContactNameProvider,
private val account: Account,
private val showCorrespondentNames: Boolean,
private val showContactNames: Boolean,
private val contactNameColor: Int?,
private val meText: String
) : AddressFormatter {
override fun getDisplayName(address: Address): CharSequence {
val identity = account.findIdentity(address)
if (identity != null) {
return getIdentityName(identity)
}

return if (!showCorrespondentNames) {
address.address
} else if (showContactNames) {
getContactName(address)
} else {
buildDisplayName(address)
}
}

private fun getIdentityName(identity: Identity): String {
return if (account.identities.size == 1) {
meText
} else {
identity.description ?: identity.name ?: identity.email ?: meText
}
}

private fun getContactName(address: Address): CharSequence {
val contactName = contactNameProvider.getNameForAddress(address.address) ?: return buildDisplayName(address)

return if (contactNameColor != null) {
SpannableString(contactName).apply {
setSpan(ForegroundColorSpan(contactNameColor), 0, contactName.length, SPAN_EXCLUSIVE_EXCLUSIVE)
}
} else {
contactName
}
}

private fun buildDisplayName(address: Address): CharSequence {
return address.personal?.takeIf {
it.isNotBlank() && !it.equals(meText, ignoreCase = true) && !isSpoofAddress(it)
} ?: address.address
}

private fun isSpoofAddress(displayName: String): Boolean {
val atIndex = displayName.indexOf('@')
return if (atIndex > 0) {
displayName[atIndex - 1] != '('
} else {
false
}
}
}
11 changes: 11 additions & 0 deletions app/core/src/main/java/com/fsck/k9/helper/ContactNameProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.fsck.k9.helper

interface ContactNameProvider {
fun getNameForAddress(address: String): String?
}

class RealContactNameProvider(private val contacts: Contacts) : ContactNameProvider {
override fun getNameForAddress(address: String): String? {
return contacts.getNameForAddress(address)
}
}
32 changes: 27 additions & 5 deletions app/core/src/main/java/com/fsck/k9/helper/Contacts.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.fsck.k9.helper;


import java.util.HashMap;

import android.Manifest;
import android.content.ContentResolver;
import android.content.Context;
Expand All @@ -9,13 +11,12 @@
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import timber.log.Timber;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import androidx.core.content.ContextCompat;

import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.fsck.k9.mail.Address;

import java.util.HashMap;
import timber.log.Timber;

/**
* Helper class to access the contacts stored on the device.
Expand All @@ -37,7 +38,8 @@ public class Contacts {
ContactsContract.CommonDataKinds.Email._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Email.CONTACT_ID,
Photo.PHOTO_URI
Photo.PHOTO_URI,
ContactsContract.Contacts.LOOKUP_KEY
};

/**
Expand All @@ -52,6 +54,8 @@ public class Contacts {
*/
protected static final int CONTACT_ID_INDEX = 2;

protected static final int LOOKUP_KEY_INDEX = 4;


/**
* Get instance of the Contacts class.
Expand Down Expand Up @@ -169,6 +173,24 @@ public boolean isAnyInContacts(final Address[] addresses) {
return false;
}

@Nullable
public Uri getContactUri(String emailAddress) {
Cursor cursor = getContactByAddress(emailAddress);
if (cursor == null) {
return null;
}

try (cursor) {
if (!cursor.moveToFirst()) {
return null;
}

long contactId = cursor.getLong(CONTACT_ID_INDEX);
String lookupKey = cursor.getString(LOOKUP_KEY_INDEX);
return ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
}
}

/**
* Get the name of the contact an email address belongs to.
*
Expand Down
22 changes: 22 additions & 0 deletions app/core/src/main/java/com/fsck/k9/mailstore/MessageDetails.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.fsck.k9.mailstore

import com.fsck.k9.mail.Address
import java.util.Date

data class MessageDetails(
val date: MessageDate,
val from: List<Address>,
val sender: Address?,
val replyTo: List<Address>,
val to: List<Address>,
val cc: List<Address>,
val bcc: List<Address>
)

sealed interface MessageDate {
data class ValidDate(val date: Date) : MessageDate

data class InvalidDate(val dateHeader: String) : MessageDate

object MissingDate : MessageDate
}
58 changes: 58 additions & 0 deletions app/core/src/main/java/com/fsck/k9/mailstore/MessageRepository.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,69 @@
package com.fsck.k9.mailstore

import com.fsck.k9.controller.MessageReference
import com.fsck.k9.mail.Address
import com.fsck.k9.mail.Header
import com.fsck.k9.mail.internet.MimeUtility
import org.apache.james.mime4j.dom.field.DateTimeField
import org.apache.james.mime4j.field.DefaultFieldParser

class MessageRepository(private val messageStoreManager: MessageStoreManager) {
fun getHeaders(messageReference: MessageReference): List<Header> {
val messageStore = messageStoreManager.getMessageStore(messageReference.accountUuid)
return messageStore.getHeaders(messageReference.folderId, messageReference.uid)
}

fun getMessageDetails(messageReference: MessageReference): MessageDetails {
val messageStore = messageStoreManager.getMessageStore(messageReference.accountUuid)
val headers = messageStore.getHeaders(messageReference.folderId, messageReference.uid, MESSAGE_DETAILS_HEADERS)

val messageDate = headers.parseDate("date")
val fromAddresses = headers.parseAddresses("from")
val senderAddresses = headers.parseAddresses("sender")
val replyToAddresses = headers.parseAddresses("reply-to")
val toAddresses = headers.parseAddresses("to")
val ccAddresses = headers.parseAddresses("cc")
val bccAddresses = headers.parseAddresses("bcc")

return MessageDetails(
date = messageDate,
from = fromAddresses,
sender = senderAddresses.firstOrNull(),
replyTo = replyToAddresses,
to = toAddresses,
cc = ccAddresses,
bcc = bccAddresses
)
}

private fun List<Header>.firstHeaderOrNull(name: String): String? {
return firstOrNull { it.name.equals(name, ignoreCase = true) }?.value
}

private fun List<Header>.parseAddresses(headerName: String): List<Address> {
return Address.parse(MimeUtility.unfold(firstHeaderOrNull(headerName))).toList()
}

private fun List<Header>.parseDate(headerName: String): MessageDate {
val dateHeader = firstHeaderOrNull(headerName) ?: return MessageDate.MissingDate

return try {
val dateTimeField = DefaultFieldParser.parse("Date: $dateHeader") as DateTimeField
return MessageDate.ValidDate(date = dateTimeField.date)
} catch (e: Exception) {
MessageDate.InvalidDate(dateHeader)
}
}

companion object {
private val MESSAGE_DETAILS_HEADERS = setOf(
"date",
"from",
"sender",
"reply-to",
"to",
"cc",
"bcc",
)
}
}
5 changes: 5 additions & 0 deletions app/core/src/main/java/com/fsck/k9/mailstore/MessageStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ interface MessageStore {
*/
fun getHeaders(folderId: Long, messageServerId: String): List<Header>

/**
* Retrieve selected header fields of a message.
*/
fun getHeaders(folderId: Long, messageServerId: String, headerNames: Set<String>): List<Header>

/**
* Return the size of this message store in bytes.
*/
Expand Down
Loading

0 comments on commit e826ed8

Please sign in to comment.